Skip to content
djust/docs
Appearance
Mode
djust.org →
Browse documentation

3 min read

Structure vs. Style

A theme pack is for colors, typography, animations, and named knobs. It is not for the structural CSS classes that define your specific page layouts.

The principle, in one diagram:

Theme pack          ─►  the painter's palette
Project's input.css ─►  the canvas and what's painted on it

This page exists because we made the mistake once.

The failure mode

Imagine you put your project's editorial CSS — .docs-hero, .docs-step, .docs-pull, .docs-features — into the theme pack as a giant editorial_css field. Switching to any other pack on your site would reveal the bug immediately:

  • <section class="docs-hero"> collapses to default flow because .docs-hero no longer exists.
  • The three-column step grid becomes a stack of <article>s with no spacing.
  • The card grid becomes a vertical list of unstyled bullets.

Every other registered pack (Dracula, Vercel, Nord, Catppuccin, your custom ones) would need to redefine those ~500 lines of structural CSS to keep the page from collapsing. That's a maintenance hazard at 70+ shipped packs.

The right mental split

Theme pack:

  • Color tokens (--background, --primary, --border, …)
  • Typography (font families, scale ratios)
  • Animation timings + easings
  • Surface treatments (shadow intensity, border radius)
  • Named knobs that other packs might want to vary (extra_css_vars, see here)

Project's static/css/input.css:

  • Page-specific layout classes (.hero, .section, .feature-grid)
  • Component-specific structural rules (.toolbar, .modal-shell)
  • Editorial / branded layout patterns
  • Site-specific typography overrides
  • Tailwind brand aliases (see Tailwind page)

The structural CSS should reach into the active theme pack via var(--color-brand-*) aliases, so swapping packs still recolors and refonts the site — it just doesn't restructure it. That's the point.

How to tell the difference

QuestionAnswerBelongs in
"Can a different theme pack reasonably want a different value here?"yes (e.g. accent color)theme pack
"Would two visually different packs share this value?"yesproject CSS
"Is this a layout class my markup references?"alwaysproject CSS
"Is this a CSS variable my project references in many places?"yescould go either place — see decision below

For the "many places" case: if the variable is genuinely a brand choice (gutter width, accent variant), it can live in extra_css_vars on the pack. If it's a project layout choice (max-width of the content column, breakpoint), it lives in your input.css :root.

Per-pack structural variation, the right way

If you genuinely want different gutter widths or different button shapes per pack, parameterize the structural CSS via named knobs in extra_css_vars:

# Pack A
extra_css_vars={'--docs-gutter': '32px', '--docs-card-radius': '4px'}

# Pack B
extra_css_vars={'--docs-gutter': '48px', '--docs-card-radius': '12px'}

Then your structural CSS in input.css just uses the variables:

.docs-section {
    padding: 48px var(--docs-gutter);
}

.docs-card {
    border-radius: var(--docs-card-radius);
}

The structure stays in one place; the values come from the pack.

See also

  • extra_css_vars — how to add named knobs the pack can vary per-pack.
  • Tailwind — the brand-alias pattern that lets Tailwind utilities reach pack tokens.
  • Troubleshooting — what to look at first when a page collapses on pack swap.

Spotted a typo or want to improve this page? Edit on GitHub →