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 restyles 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/mcss.components.css); /* optional */@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 raw values: palettes, type scale, spacing, etc. - 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 */ --base-950: #1a1208;
/* Every bordered surface at once */ --ui-border-color: light-dark(var(--base-300), var(--base-600));
/* Single component override */ --bt-border-radius: 0; }}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, only for what tokens can’t express. Pseudo-elements,
nth-childrhythm,text-decoration, and text rendering onbodysuch as font smoothing,font-synthesis, ortext-wraphave no token hooks, so themes may style them directly. Theme files are the one place in mCSS allowed to select framework classes from outside: component classes, framework globals like.a11y-skipLink, and bare element selectors (seetheme.wireframe.cssfor a real example). Abodyrule belongs here, not in your project CSS. - Check tokens before writing a
bodyrule. Most of what people write into one is already an interface token. The elements layer paints the page from--body-background-color,--text-color, and--text, so a theme that sets those does not also need to setbackground-color,color, andfont-family. What is left over (the text-rendering properties the framework exposes no token for) is exactly what a theme style rule is for. - No
!important. A theme is meant to be overridable by your page CSS; the last word belongs to helpers.