> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sellauth.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Theme Assets and Styling

> Tailwind builds, the design token bridge, upgrade-safe custom CSS, and the libraries SellAuth hosts for themes.

## The assets folder

| File | Purpose |
| - | - |
| `style.css` | Tailwind source. This is the file you edit. |
| `built.css` | Compiled output, loaded by the layout. Generated, never edit it by hand. |
| `script.js` | The theme's JavaScript. |
| `custom.css` | Merchant CSS. Preserved across theme updates. |
| `custom.js` | Merchant JavaScript. Preserved across theme updates. |

Everything in `assets/` is served publicly. Keep files flat, subfolders are not supported.

## Tailwind

Most official themes are Tailwind-based. If your theme has a `tailwind.config.js` at its root, the [Theme CLI](/developers/theme-cli) compiles `assets/style.css` into `assets/built.css` on every change, with no setup on your side.

```js tailwind.config.js theme={null}
module.exports = {
  content: ['./**/*.njk'],
  darkMode: ['selector', '[data-scheme="dark"]'],
  theme: { extend: { } },
  plugins: []
}
```

Because `content` scans your `.njk` files, a class only survives the build if it appears literally in a template. Dynamically assembled class names get purged:

```njk theme={null}
{# purged, Tailwind never sees "text-red-500" #}
<div class="text-{{ properties.color }}-500">

{# kept, both class names appear literally #}
<div class="{{ 'text-red-500' if properties.danger else 'text-t-primary' }}">
```

<Note>
  The `pro` theme is the exception. It is built on Bootstrap and has no `tailwind.config.js`, so nothing is compiled for it.
</Note>

## Design tokens

This is the part worth understanding before you restyle anything.

Theme settings do not get compiled into CSS. The layout emits them as CSS custom properties at render time, and `tailwind.config.js` maps utility class names onto those properties. The result is that changing a color in the visual editor updates the page without rebuilding any CSS.

The chain, using the accent color:

<Steps>
  <Step title="A setting in schema.json">
    ```json theme={null}
    "accent_color": { "label": "Accent Color", "type": "color", "default": "#2B5FE3" }
    ```
  </Step>

  <Step title="The layout emits it as a custom property">
    `hex_to_rgb` converts the hex value into channels so it can be used with an alpha component.

    ```njk layouts/master.njk theme={null}
    <style id="theme-styles">
      :root {
        --cl-accent: {{ (global.properties.accent_color if global.properties.accent_color else '#2B5FE3') | hex_to_rgb }};
      }
    </style>
    ```
  </Step>

  <Step title="Tailwind maps a color name onto it">
    ```js tailwind.config.js theme={null}
    colors: {
      accent: {
        500: 'rgba(var(--cl-accent), <alpha-value>)',
        600: 'color-mix(in srgb, rgba(var(--cl-accent), <alpha-value>), black 12%)'
      }
    }
    ```
  </Step>

  <Step title="Templates use the utility class">
    ```njk theme={null}
    <button class="bg-accent-500 text-on-accent hover:bg-accent-600">Add to cart</button>
    ```
  </Step>
</Steps>

The Canvas theme wires up tokens for colors (`--cl-*`), radii (`--radius-*`), shadows (`--shadow-*`), fonts (`--ff-*`, `--fw-*`), spacing (`--section-py`, `--card-pad`, `--grid-gap`), the container width (`--container-max`), and the transition duration (`--dur`). Follow the same pattern when you add a setting that should affect styling: emit a custom property in the layout, map it in `tailwind.config.js`, use the utility in your templates.

### Color schemes

Light and dark are driven by a `data-scheme` attribute on `<html>`, with `darkMode: ['selector', '[data-scheme="dark"]']` in the Tailwind config. Each scheme redefines the same custom properties, so components do not need per-scheme classes:

```css theme={null}
[data-scheme="light"] { --cl-background: 247,248,250; }
[data-scheme="dark"]  { --cl-background: 12,14,18; }
```

## Custom CSS and JS

`assets/custom.css` and `assets/custom.js` are the only asset files preserved when a theme is updated from its official base. They are the right place for merchant-specific tweaks.

To make that practical, the newer official themes put a stable `sa-` class on every section and key element. These classes carry no styles of their own, they exist purely as selectors:

| Scope | Classes |
| - | - |
| Page | `body.sa-tpl-<template>`, for example `.sa-tpl-product` |
| Chrome | `.sa-header`, `.sa-navbar`, `.sa-footer`, `.sa-navbar-link`, `.sa-navbar-cart`, `.sa-footer-link` |
| Sections | `.sa-<component>` plus `.sa-<component>--<variant>`, for example `.sa-hero--split-media` |
| Section internals | `.sa-<component>-title`, `-subtitle`, `-eyebrow`, `-media`, `-items`, `-item`, `-actions`, `-link`, `-price`, `-badge`, `-icon` |
| Primitives | `.sa-btn` (plus `.sa-btn--<variant>`), `.sa-section-header`, `.sa-product-card` (plus `.sa-product-card--<style>`), `.sa-product-form`, `.sa-add-to-cart`, `.sa-product-gallery`, `.sa-cart-panel`, `.sa-cart-item`, `.sa-searchbar`, `.sa-pagination`, `.sa-breadcrumbs`, `.sa-toasts`, `.sa-feedback-card` |

```css assets/custom.css theme={null}
/* Bigger hero heading, product page only */
.sa-tpl-product .sa-hero-title {
  font-size: 3rem;
}
```

<Tip>
  If you are building a theme from scratch, adopt the same convention. It is what lets merchants restyle without touching Tailwind, and it survives your theme updates.
</Tip>

## Referencing assets

Use `assetUrl` for files in your own theme. It appends a version parameter so browsers pick up changes immediately:

```njk theme={null}
<link href="{{ 'built.css' | assetUrl }}" rel="stylesheet" />
<script src="{{ 'script.js' | assetUrl }}"></script>
```

Use `staticAsset` for libraries SellAuth hosts. Loading them this way is faster and avoids a third-party dependency:

```njk theme={null}
<script src="{{ 'alpinejs@3.15.12.min.js' | staticAsset }}"></script>
<link href="{{ 'splide@4.1.4.min.css' | staticAsset }}" rel="stylesheet" />
```

Available libraries:

| Library | Files |
| - | - |
| Alpine.js | `alpinejs@3.15.12.min.js` |
| Splide | `splide@4.1.4.min.js`, `splide@4.1.4.min.css` |
| js-cookie | `js.cookie@3.0.8.min.js` |
| Altcha | `altcha@1.5.1.min.js` |
| lite-youtube-embed | `lite-yt-embed@0.3.4.min.js`, `lite-yt-embed@0.3.4.min.css` |
| Masonry | `masonry@1.0.16.min.js` |
| Zoom | `zoom@1.5.0.min.js` |
| ScrollReveal | `scrollreveal@1.3.0.min.js` |
| AOS | `aos@2.3.4.min.js`, `aos@2.3.4.min.css` |
| Choices | `choices@10.2.0.min.js`, `choices@10.2.0.min.css` |
| Particles | `particles@2.2.3.min.js` |
| Bootstrap | `bootstrap@5.3.8.min.css`, `bootstrap.bundle@5.3.8.min.js` |
| jQuery | `jquery@3.7.1.min.js` |

## JavaScript

The official themes use [Alpine.js](https://alpinejs.dev/) for interactivity. The layout puts an `x-data` root on the app container and the theme registers its component in `script.js`:

```js assets/script.js theme={null}
document.addEventListener('alpine:init', () => {
  Alpine.data('app', () => ({
    // cart state, modals, currency switching, and so on
  }));
});
```

Server-rendered values are handed to the client through an inline script in the layout, using the `json` filter:

```njk theme={null}
<script>
  window.shopId = {{ shop.id | json }};
  window.defaultCurrency = {{ currency | json }};
</script>
```

## Upload rules

The CLI, the code editor, and the visual editor accept these file types:

```txt theme={null}
njk, js, json, css, svg, jpg, png, gif, webp, avif,
webm, mp4, mpeg, avi, mp3, ogg, woff, woff2, ttf, otf, glb
```

Per-file size limits depend on the shop's plan.

<Note>
  In watch mode the CLI only pushes text files (`njk`, `html`, `css`, `js`, `json`, `txt`, `md`, `xml`, `svg`). Add images and fonts with a full `push`, or upload them from the dashboard.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.