Theming
Theme every c2n component from a handful of design tokens with @c2n/theme, or reach for any component variable directly.
Every c2-* component is styled exclusively through its own CSS custom properties, named --c2-<component>__<part>[__<state>]--<property> (about 700 in total, listed on each component’s API tab). They stay fully independent: you can theme an app by setting those variables one by one, without any other layer.
@c2n/theme adds a convenience layer on top: a small set of design tokens and a generated base theme that maps every component variable onto them. Set ~35 tokens and every component follows; any component variable you set yourself still wins.
Installation
npm install @c2n/themeimport '@c2n/theme/theme.css' // tokens + base theme, once at the app rootThen override the tokens that differ in your brand:
:root {
--c2-theme--color-primary: #7c3aed;
--c2-theme--color-primary-hover: #6d28d9;
--c2-theme--radius-md: 10px;
--c2-theme--font-family: 'Inter', system-ui, sans-serif;
}How the layers stack
- Tokens —
--c2-theme--<name>. Shared colours, type, shape, focus ring, motion and elevation. Defined bytokens.css, overridden by your app. - Base theme —
base.csssets each component variable tovar(--c2-theme--<token>, <component default>)on:root. It is generated from the components’ manifests, so coverage grows with the library. - Component variables — the fine-grained escape hatch.
--c2-button__container--background-color: crimsonon an element or class beats the base theme because the base theme only sets:rootvalues. - Application variant components — repeated looks wrapped in your own components (a class, a wrapper, or a Lit subclass) built from the two layers above.
/* What base.css emits for one variable: the token first, the component's own default as the innermost fallback. */
--c2-list--border-top: var(--c2-theme--border, var(--c2-theme--border-width, 1px) solid var(--c2-theme--color-outline, #e4e4e7));Composite tokens such as --c2-theme--border and --c2-theme--focus-ring wrap their primitives, so you can set the whole shorthand (--c2-theme--border: 2px dashed hotpink) or just the colour inside it (--c2-theme--color-outline).
Dark mode
tokens.css defines light values on :root and dark values under [data-theme='dark'] and the .c2-dark class, with the OS preference as fallback when no data-theme attribute is present. data-theme="light" / .c2-light opts a subtree out.
<html data-theme="dark">
…
<section class="c2-light">Light island inside a dark page</section>
</html>Because the selectors are not tied to the root element, any subtree can be inverted: a preview canvas, a chat panel, a sidebar.
Bring your own tokens
If your app already has a token system, skip tokens.css, load only base.css, and bridge your tokens onto the --c2-theme--* names. Dark mode then follows your own switch automatically.
@import '@c2n/theme/base.css';
:root {
--c2-theme--color-primary: var(--brand-600);
--c2-theme--color-on-primary: var(--brand-on-600);
--c2-theme--color-surface: var(--surface);
--c2-theme--color-on-surface: var(--text);
--c2-theme--color-outline: var(--border);
--c2-theme--radius-md: var(--radius);
--c2-theme--font-family: var(--font-sans);
--c2-theme--focus-ring: 2px solid var(--brand-600);
}Tokens
| Token | Light | Dark | Description |
|---|---|---|---|
--c2-theme--color-primary | #0265dc | #5aa3ff | Accent colour: filled buttons, selected states, focused borders, links. |
--c2-theme--color-primary-hover | #0154b8 | #7bb6ff | Accent colour on hover. |
--c2-theme--color-primary-active | #01469a | #9cc9ff | Accent colour while pressed. |
--c2-theme--color-on-primary | #ffffff | #032a5c | Text and icons drawn on the accent colour. |
--c2-theme--color-primary-container | #edf1fe | #0f2d5c | Soft accent surface, e.g. a selected list item. |
--c2-theme--color-surface | #ffffff | #18181b | Default surface of inputs, lists, cards, dialogs. |
--c2-theme--color-surface-container-low | #fafafa | #1f1f23 | Slightly raised surface: side navigation, read-only fields, subtle hover. |
--c2-theme--color-surface-container | #f4f4f5 | #27272a | Hover surface for rows and icon buttons. |
--c2-theme--color-on-surface | #18181b | #f4f4f5 | Primary text colour. |
--c2-theme--color-on-surface-variant | #71717a | #a1a1aa | Secondary text, placeholders, icons, supporting text. |
--c2-theme--color-outline | #d4d4d8 | #3f3f46 | Resting border colour of inputs, cards and lists. |
--c2-theme--color-outline-variant | #e4e4e7 | #27272a | Hairline dividers and light borders. |
--c2-theme--color-outline-strong | #a1a1aa | #52525b | Border colour on hover. |
--c2-theme--color-error | #dc2626 | #f87171 | Error borders, error text, required indicators. |
--c2-theme--color-scrim | rgba(9, 9, 11, 0.45) | rgba(0, 0, 0, 0.6) | Backdrop behind dialogs and drawers. |
--c2-theme--color-inverse-surface | #18181b | #f4f4f5 | High-contrast surface, e.g. tooltips. |
--c2-theme--color-on-inverse-surface | #fafafa | #18181b | Text drawn on the inverse surface. |
--c2-theme--font-family | unset | same | Font family of every component. Unset by default so components inherit the page font. |
--c2-theme--font-size-sm | 12px | same | Small text: supporting text, tooltips, descriptions, timestamps. |
--c2-theme--font-size-md | 14px | same | Body text: buttons, inputs, list items, dialog content. |
--c2-theme--font-weight-medium | 500 | same | Medium weight: buttons, headers, tooltips. |
--c2-theme--font-weight-semibold | 600 | same | Semibold weight: dialog titles, list headings, avatars. |
--c2-theme--radius-sm | 4px | same | Small radius: checkboxes, link buttons, inline controls. |
--c2-theme--radius-md | 6px | same | Default radius: buttons, inputs, list items, tooltips. |
--c2-theme--radius-lg | 8px | same | Large radius: cards, panels, popovers. |
--c2-theme--radius-xl | 14px | same | Extra large radius: dialogs. |
--c2-theme--radius-full | 999px | same | Pill / circle radius: avatars, icon buttons. |
--c2-theme--border-width | 1px | same | Width of every themed border. |
--c2-theme--border | unset | same | Complete resting border shorthand. Unset by default: it falls back to `border-width solid color-outline`. |
--c2-theme--focus-ring | 2px solid rgba(2, 101, 220, 0.4) | 2px solid rgba(90, 163, 255, 0.5) | Focus-visible outline of every component. |
--c2-theme--disabled-opacity | 0.38 | same | Opacity of disabled components. |
--c2-theme--motion-scale | 1 | same | Multiplier applied to every transition and animation duration (0 disables motion). |
--c2-theme--shadow-md | 0 8px 24px rgba(24, 24, 27, 0.08) | 0 8px 24px rgba(0, 0, 0, 0.45) | Shadow of popovers, menus and tooltips. |
--c2-theme--shadow-lg | 0 24px 60px rgba(0, 0, 0, 0.25) | 0 24px 60px rgba(0, 0, 0, 0.6) | Shadow of dialogs and drawers. |
What the base theme covers
Colours, backgrounds, borders, radii, focus rings, disabled opacity, font sizes and weights, durations and shadows with a concrete default are mapped. Sizes, paddings, gaps, identity colours (avatar fallbacks), status colours and the code viewer’s syntax theme stay component-specific by design, and variables without a default (they inherit) are left untouched. @c2n/theme/report.json lists, per package, what is mapped and what is not.