---
title: "Structure vs. Style"
slug: theming-structure-vs-style
section: theming
order: 70
level: intermediate
description: "What does NOT belong in a theme pack — the editorial-CSS lesson. Structural classes belong in your project's input.css; theme packs paint, they don't restructure."
---

# 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:

```text
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](/theming/extra-css-vars/))

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](/theming/tailwind/))

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`:

```python
# 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:

```css
.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`](/theming/extra-css-vars/)** — how to add named
  knobs the pack can vary per-pack.
- **[Tailwind](/theming/tailwind/)** — the brand-alias pattern that lets
  Tailwind utilities reach pack tokens.
- **[Troubleshooting](/theming/troubleshooting/)** — what to look at
  first when a page collapses on pack swap.
