Styling

Palette and style tokens, the section skeleton, buttons, forms and company variables.

This is the guide the PageHub MCP server gives an AI agent for get_style_reference(). Names like set_theme are its tools (connect one), and the liquid topic is the HTML + Liquid reference.
On this page

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)

VariableSlotTypical Use
var(--primary)0Main brand color (buttons, links, hero backgrounds)
var(--primary-content)1Text on primary backgrounds
var(--secondary)2Supporting color (cards, badges, secondary buttons)
var(--secondary-content)3Text on secondary backgrounds
var(--accent)4CTA/highlight color (call-to-action buttons, emphasis)
var(--accent-content)5Text on accent backgrounds
var(--neutral)6Muted color (borders, disabled, subtle backgrounds)
var(--neutral-content)7Text on neutral backgrounds
var(--base-100)8Page/site background
var(--base-content)9Default body text
var(--base-200)10Alternate section backgrounds, cards, dividers
var(--base-300)11Deeper alternates, heavy borders

Style guide variables (set with the set_theme styleGuide)

VariableKeyDefault
--radius-boxradiusBox0.5rem
--radius-fieldradiusField0.25rem
--radius-selectorradiusSelector0.5rem
--size-fieldsizeField0.25rem
--size-selectorsizeSelector0.25rem
--depthdepth1
--noisenoise0
--button-padding-x / -ybuttonPadding1.5rem 0.75rem
--container-padding / -x / -ycontainerPadding2rem 2rem
--content-widthcontentWidth80rem
--shadow-styleshadowStyle0 1px 3px rgba(0,0,0,0.1)
--heading-font-familyheadingFontFamily(from Google Fonts)
--body-font-familybodyFontFamily(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

HTML + Liquid
<section id="services" data-edit-name="Services" class="w-full bg-base-100 text-base-content py-space-lg px-container-x">
  <div class="max-w-page mx-auto w-full flex flex-col gap-space-md">
    <h2 class="font-heading text-3xl sm:text-4xl font-bold">…</h2>
    <div class="mt-space-xl grid gap-space-md sm:grid-cols-2 lg:grid-cols-3">…</div>
  </div>
</section>
  • px-container-x and the background live on the section root; the content sits in ONE inner max-w-page mx-auto w-full. Putting px-container-x on both doubles the inset.
  • Section roots use py-space-lg (or larger tiers), never md:py-*.
  • Headings step down on mobile: text-3xl sm:text-4xl md:text-6xl, never a bare text-6xl.
  • A grid below a heading needs its own mt-space-xl; gap-* is not a top margin.
  • Dividers are an element with border-t inside 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-primary without spatial padding; never px-8 py-3. A long label needs max-w-full whitespace-normal text-xs sm:text-sm to wrap on mobile.
  • generate_button_classes / validate_button_classes build 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 a bg-base-200 card use a bg-base-100 (or bg-base-300) input, inside bg-base-100 use bg-base-200. Input bg must differ from the card bg.
  • Every input has a visible <label> and autocomplete on personal-data fields.
  • Forms post with {% form 'name', redirect: '/thanks' %}…{% endform %}. Without redirect the post returns to the same page, once: form.posted_successfully? is true after a good post, form.errors is nil unless it failed ({{ form.errors | default_errors }}), form.values.<input name> refills the fields.

Variables

VariableExample
{{ 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

  1. Always match text color to background: bg-primary → text-primary-content; bg-base-100 → text-base-content. A bg-base-100 band is paired with text-base-content in the same class list.
  2. 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.
  3. 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.
  4. A mobile nav needs a toggle: a desktop nav hidden lg:flex is paired with a lg:hidden button and a popover / <dialog> drawer.
  5. 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.
  6. Case-review, consultation and quote CTAs open a <dialog> form (commandfor + command="show-modal"), they do not scroll to a contact section.
  7. 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 in get_style_reference({ topic: "liquid" }). Filters, sort, search and pagination are ?query params read as request.query.<name>.

Check it: take one screenshot_site with a selector after building a section.