A site is HTML + Liquid files styled with Tailwind classes that read the palette and spacing tokens in theme.css.
Palette variables (set with the set_theme palette)
| Variable | Slot | Typical Use |
|---|---|---|
| var(--primary) | 0 | Main brand color (buttons, links, hero backgrounds) |
| var(--primary-content) | 1 | Text on primary backgrounds |
| var(--secondary) | 2 | Supporting color (cards, badges, secondary buttons) |
| var(--secondary-content) | 3 | Text on secondary backgrounds |
| var(--accent) | 4 | CTA/highlight color (call-to-action buttons, emphasis) |
| var(--accent-content) | 5 | Text on accent backgrounds |
| var(--neutral) | 6 | Muted color (borders, disabled, subtle backgrounds) |
| var(--neutral-content) | 7 | Text on neutral backgrounds |
| var(--base-100) | 8 | Page/site background |
| var(--base-content) | 9 | Default body text |
| var(--base-200) | 10 | Alternate section backgrounds, cards, dividers |
| var(--base-300) | 11 | Deeper alternates, heavy borders |
Style guide variables (set with the set_theme styleGuide)
| Variable | Key | Default |
|---|---|---|
| --radius-box | radiusBox | 0.5rem |
| --radius-field | radiusField | 0.25rem |
| --radius-selector | radiusSelector | 0.5rem |
| --size-field | sizeField | 0.25rem |
| --size-selector | sizeSelector | 0.25rem |
| --depth | depth | 1 |
| --noise | noise | 0 |
| --button-padding-x / -y | buttonPadding | 1.5rem 0.75rem |
| --container-padding / -x / -y | containerPadding | 2rem 2rem |
| --content-width | contentWidth | 80rem |
| --shadow-style | shadowStyle | 0 1px 3px rgba(0,0,0,0.1) |
| --heading-font-family | headingFontFamily | (from Google Fonts) |
| --body-font-family | bodyFontFamily | (from Google Fonts) |
Using variables in classes
Always use the palette and style tokens as Tailwind classes, never a hard-coded hex or named color:
class="bg-primary text-primary-content border-base-200 rounded-box gap-container max-w-page"
Exceptions: bg-transparent and opacity modifiers such as bg-white/10.
Responsive classes (mobile first)
- A class with no prefix applies at every width, phones first.
- sm: 640px+ (2-column grids, side-by-side buttons), md: 768px+ (flex-row, larger text), lg: 1024px+ (4-column grids, split heroes)
Example: class="flex flex-col gap-space-sm py-space-lg px-container-x bg-base-100 text-base-content md:flex-row"
Spacing tokens grow with the screen on their own, so you never need md:py-* or md:gap-*:
--space-xs (micro), --space-sm (element), --space-md (content), --space-lg (section), --space-xl (hero)
Common utility classes
Layout: flex, flex-col, flex-row, grid, grid-cols-, gap-space-, items-, justify-, w-full, w-1/2, max-w-page, h-[400px], min-h-screen, py-space-, px-container-x, mx-auto, relative, absolute, z-, overflow-hidden
Surface: bg-primary, text-base-content, border, border-base-200, rounded-box, shadow-sm, shadow-md
Typography: text-4xl, font-bold, leading-relaxed, tracking-widest, uppercase
Section skeleton
px-container-xand the background live on the section root; the content sits in ONE innermax-w-page mx-auto w-full. Puttingpx-container-xon both doubles the inset.- Section roots use
py-space-lg(or larger tiers), nevermd:py-*. - Headings step down on mobile:
text-3xl sm:text-4xl md:text-6xl, never a baretext-6xl. - A grid below a heading needs its own
mt-space-xl;gap-*is not a top margin. - Dividers are an element with
border-tinside the section, never a 1px-tall box. - Exactly one h1 per page; sequential heading levels.
Background image overlays
A gradient and a background image on the same element do not combine. Put the photo on the section (bg-cover bg-center bg-no-repeat, or an <img>), then a child overlay <div class="absolute inset-0 z-[1] bg-linear-to-b from-primary/90 via-primary/60 to-primary/85" aria-hidden="true"></div> and give the content relative z-10. Never a flat bg-black/70 overlay.
Buttons
- Primary:
btn btn-primary cta-responsive rounded-box px-space-md py-space-xs min-h-12 font-semibold - Outline:
btn btn-outline cta-outline-responsive rounded-box px-space-md py-space-xs min-h-12 font-semibold border-base-content/30 text-base-content - Never a bare
btn btn-primarywithout spatial padding; neverpx-8 py-3. A long label needsmax-w-full whitespace-normal text-xs sm:text-smto wrap on mobile. generate_button_classes/validate_button_classesbuild and check a class string.
Form styling
Style inputs explicitly: they don't inherit the card's background.
- Every input:
border border-base-300 bg-base-100 rounded-box px-space-sm py-space-xs; inside abg-base-200card use abg-base-100(orbg-base-300) input, insidebg-base-100usebg-base-200. Input bg must differ from the card bg. - Every input has a visible
<label>andautocompleteon personal-data fields. - Forms post with
{% form 'name', redirect: '/thanks' %}…{% endform %}. Withoutredirectthe post returns to the same page, once:form.posted_successfully?is true after a good post,form.errorsis nil unless it failed ({{ form.errors | default_errors }}),form.values.<input name>refills the fields.
Variables
| Variable | Example |
|---|---|
| {{ company.name }} | Acme Inc. |
| {{ company.tagline }} | The ultimate solution |
| {{ company.type }} | technology |
| {{ company.location }} | Los Angeles, CA |
| {{ company.address }} | 123 Main St, Suite 100 |
| {{ company.phone }} | (555) 123-4567 |
| {{ company.email }} | contact@acme.com |
| {{ company.website }} | https://www.acme.com |
| {{ year }} | (current year) |
Company fields live in site.json; set them there with edit_site_file. Without them {{ company.name }} shows "Acme Inc.".
Key rules
- Always match text color to background: bg-primary → text-primary-content; bg-base-100 → text-base-content. A
bg-base-100band is paired withtext-base-contentin the same class list. - Palette (outline CTAs): On minimal monochrome themes, Primary and Base Content must differ in lightness — not both the same near-black OKLCH. DaisyUI 5 btn-outline uses --btn-color for label/border; if primary ≈ base-content, canonical outline + text-base-content can collapse to illegible dark-on-dark.
- Content colors: your "* Content" palette entries ship only when they clear WCAG AA (4.5:1) on their surface; otherwise the renderer swaps in a derived color. set_theme warns when that will happen.
- A mobile nav needs a toggle: a desktop nav
hidden lg:flexis paired with alg:hiddenbutton and apopover/<dialog>drawer. - Header AND footer always reserve a logo slot: an icon mark plus the wordmark, replaced by the uploaded logo (
image_tag) once the site has one. - Case-review, consultation and quote CTAs open a
<dialog>form (commandfor+command="show-modal"), they do not scroll to a contact section. - Behaviour is native HTML or a runtime
data-*attribute first (carousel, lightbox, show/hide, state, cart…); a site may also carry its own JavaScript. Both are documented with examples inget_style_reference({ topic: "liquid" }). Filters, sort, search and pagination are?queryparams read asrequest.query.<name>.
Check it: take one screenshot_site with a selector after building a section.