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-herono 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
| Question | Answer | Belongs 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?" | yes | project CSS |
| "Is this a layout class my markup references?" | always | project CSS |
| "Is this a CSS variable my project references in many places?" | yes | could 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.