Docs :: Themes
| File name | Description | Source |
|---|---|---|
theme.default.css |
Empty template: copy it to start your own theme | Github |
theme.wireframe.css |
Example theme with a hand-drawn wireframe look | Github |
A theme is one CSS file that reskins your whole site. It contains token overrides, plus optional style rules for the few things tokens can’t express. Swap which theme file your entry point imports and everything changes; anything the theme doesn’t mention falls back to the framework defaults.
mCSS ships with no theme active: the default look is simply the framework’s own token defaults with an empty theme layer.
Activating a theme
Import exactly one theme file from your own entry point, after the framework:
@import url(./framework/mcss.css);@import url(./framework/theme.wireframe.css);That’s the entire swap. Theme files are self-layered: each one wraps its own content in @layer theme, so it lands in the right cascade slot no matter how you load it (plain @import, a <link> tag on dist/css/theme.wireframe.css, or a bundler). No layer() annotation needed.
The theme layer sits after components and before helpers, so a theme beats every framework default but still loses to your own unlayered CSS and to helpers. The layer order runs from default to deliberate: the framework’s layers are what things look like when nobody has an opinion, a theme is a deliberate opinion about those defaults, and your unlayered CSS is more deliberate still.
Why small theme files go far
Themes work because of the two token tiers below them:
- Raw tokens (
settings.tokens.css) hold primitive values: palettes, the type scale, spacing, radii. - Interface tokens (
settings.ui.css) map those primitives onto meaning: semantic aliases like--ui-border-color, plus every component’s token defaults (--bt-*,--card-*, …). Interface tokens only ever take another token for value.
Because components read var(--ui-border-color) instead of a raw hex, a theme gets three levels of blast radius from a single override:
@layer theme { :root { /* Whole palette shifts: every light-dark() alias follows */ --base-950: #1a1208;
/* Cross-cutting: every bordered surface at once */ --ui-border-color: light-dark(var(--base-300), var(--base-600));
/* Surgical: one component knob */ --bt-border-radius: 0; }}Worked example: the wireframe theme
theme.wireframe.css turns the whole site into a hand-drawn wireframe and shows both halves of a theme in action. To see it applied to a real page, flip the switcher on the marketing template.
The token half does most of the work: a grayscale palette (--base-*, --primary-*, and the feedback scales, because in a wireframe success and danger are annotations, not colors), a marker-style handwriting stack for text, headings, and --display, pen-weight borders (--border-sm: 2px), and hard-offset “paper cutout” shadows replacing the soft blur scale. On/off states (toggle, avatar status dot) get explicit ink-on-paper overrides since the green they normally lean on is gone. For wobbly sketch borders it defines three oversized asymmetric radii (--wobble-1/2/3) and remaps each component’s radius token to a different one, so cards, buttons, and inputs don’t all wobble identically. It deliberately does NOT override the raw --radius-* scale: uniform wobble reads as a bug, not a hand.
The style-rule half covers what tokens can’t express: wavy link underlines (only the text-decoration-style longhand, so un-underlined links stay clean), tiny alternating tilts on cards via the independent rotate property (which composes with the transforms components animate instead of fighting them), drawn-panel borders on filled and primary sections, and a traced-twice pencil stroke drawn by a pseudo-element with the same border color, a different wobble, and a 1px offset. Progressive enhancements ride on top for browsers that support them: an SVG displacement filter roughens the traced card stroke; sibling-index() (all current engines) seeds per-element variation through sin() so every box gets its own tilt, stroke offset, and corner radii, with a registered inherited seed keeping a card’s corners in sync with its media; and CSS random() (Safari 26.5+) upgrades tilts and stroke offsets to true randomness. Everything degrades to the fixed sketch silently.
Also worth copying: what it leaves alone. The focus ring stays crisp and high-contrast, --radius-round consumers (avatars, toggles) stay round, and text stays at the near-black end of the gray scale.
Writing your own theme
Copy theme.default.css and start overriding. Guidelines:
- Tokens first. Most themes never need a selector: raw tokens for the palette and type, interface tokens for cross-cutting changes, component tokens for details.
- Style rules for the rest. Pseudo-elements,
nth-childrhythm, andtext-decorationhave no token hooks, so themes may style them directly. Theme files are the one place in mCSS allowed to select component classes from outside (seetheme.wireframe.cssfor a real example). - No
!important. A theme is meant to be overridable by your page CSS; the last word belongs to helpers.