Skip to content

Theming ​

Catnip supports theming via CSS variables. Design tokens are exposed as --catnip-* variables that you can override.

Light and Dark Themes ​

Default (light) ​

css
@import "@signicat/catnip-design-tokens/css";

Tokens are applied on :root. Color examples:

  • --catnip-color-content-neutral-strongest
  • --catnip-color-background-neutral-subtlest
  • --catnip-color-brand-500

The default theme also sets color-scheme: light. This keeps browser-rendered UI such as form controls and autofill consistent with Catnip's light semantic colors, regardless of the operating-system preference.

Typography: responsive --catnip-font-* presets are included in the default CSS bundle (see Typography tokens). Spacing and shadow tokens ship as --catnip-space-* and --catnip-shadow-* (see Spacing tokens and Shadow tokens). Catnip CSS shadow utilities use the same variables.

Component tokens (for example --catnip-button-radius) also ship on :root. Components consume these named variables; themes override them. See Component tokens below.

Dark theme ​

css
@import "@signicat/catnip-design-tokens/css";
@import "@signicat/catnip-design-tokens/css/dark";

Dark semantic color overrides apply when the subtree's data-theme includes dark:

html
<html data-theme="dark">
  <!-- Dark semantic colors apply -->
</html>

Or scope to a section:

html
<div data-theme="dark">
  <!-- Only this subtree uses dark semantic overrides -->
</div>

The dark-theme rule also sets color-scheme: dark. Because color-scheme is inherited, native controls inside Catnip shadow roots use the matching browser rendering, including Chrome autofill, without component-specific browser paint overrides.

Primitive color variables stay on :root unless you override them yourself.

EndUI (whitelabel) theme ​

Use the EndUI theme for non-Signicat / whitelabel surfaces so primary UI uses the blue brand palette instead of Signicat purple, and main buttons use a squared radius.

EndUI is opt-in: import it from the tokens package (it is not bundled into @signicat/catnip-css). Both files use data-theme="endui". Pick the file for where you set that attribute.

On <html>, import css/endui. That file remaps color.brand.* to the blue scale, sets --catnip-color-accent-300 to Steel 200 (Mint 300 by default), and sets --catnip-button-radius to the squared scale step (radius.s). --catnip-graphic-accent references --catnip-color-accent-300, so illustration accents follow that remap. Semantic aliases such as --catnip-color-content-primary-strong already live on :root, which is the same element, so they recompute against the blue brand.

css
@import "@signicat/catnip-design-tokens/css";
@import "@signicat/catnip-design-tokens/css/endui";
html
<html data-theme="endui">
  <!-- Blue brand + squared main buttons -->
</html>

On a wrapper, import css/endui-scoped. A custom property inherits its computed value. Remapping --catnip-color-brand-* on a child leaves primary content, background, border, and the focus ring on the color already resolved on :root. The scoped file is the css/endui rule plus those dependent variables, repeated with the same var() expressions as :root, so the subtree recomputes them. That includes --catnip-graphic-primary, --catnip-logo-background, and --catnip-shadow-focus-default.

css
@import "@signicat/catnip-design-tokens/css";
@import "@signicat/catnip-design-tokens/css/endui-scoped";
html
<div data-theme="endui">
  <!-- This subtree uses the blue brand, including semantic primary tokens -->
</div>

EndUI with dark ​

data-theme takes a space-separated list. Theme rules match one word in it ([data-theme~="dark"]), so set both values on the same element:

html
<html data-theme="endui dark">
  <!-- Dark semantic colors on the blue brand -->
</html>

Load css/dark and the EndUI file for where the attribute is set: css/endui on <html>, css/endui-scoped on a wrapper. Their order does not matter. Dark tokens reference --catnip-color-brand-*, and EndUI remaps that scale, so dark primary colors use blue.

data-theme="dark" on an element inside an EndUI element also works. The reverse does not: an EndUI wrapper inside a dark element gets light primary tokens. Put data-theme="endui dark" on that wrapper instead.

CDN ​

If you already load css/latest/styles.css, default and dark token rules are included — set data-theme="dark" as above. For EndUI, or when loading tokens without the full CSS package:

html
<link rel="stylesheet" href="https://static.signicat.com/catnip/design-tokens/latest/css/default.css" />
<link rel="stylesheet" href="https://static.signicat.com/catnip/design-tokens/latest/css/dark.css" />
<link rel="stylesheet" href="https://static.signicat.com/catnip/design-tokens/latest/css/endui.css" />
<!-- Subtree EndUI: endui-scoped.css instead of endui.css -->

Then apply dark, EndUI, or both with data-theme="dark", data-theme="endui", or data-theme="endui dark" as above. For EndUI on a wrapper, load css/endui-scoped.css instead of css/endui.css. For the full CDN setup (CSS utilities, icons, fonts), see Installation — CDN.

Component tokens ​

Component tokens are named for a control (for example --catnip-button-radius) but defined on :root in the design-tokens package — not invented inside each component stylesheet.

Rules:

  1. Defaults always exist on :root (Signicat look).
  2. Themes override the same variables (for example EndUI sets --catnip-button-radius).
  3. Component SCSS only consumes the variable.
  4. Use a leaf name first (button.radius → --catnip-button-radius). Add a suffix only when a second value is needed (button.radius.utility → --catnip-button-radius-utility).

Per-instance overrides still work: set the CSS variable on a host or wrapper; cascade wins over :root.

See also the shipped inventory: Component tokens.

Overriding tokens ​

Redefine CSS variables where you need custom branding:

css
:root {
  --catnip-color-brand-500: #6d28d9;
  --catnip-color-brand-600: #5b21b6;
}

Custom theme ​

Add another data-theme value and set the variables you want to change. Load that stylesheet after default.css.

On <html>, redefining those variables is enough. Aliases declared on :root recompute on that same element:

css
/* my-theme.css — html only */
[data-theme~="brand"] {
  --catnip-color-brand-500: #0ea5e9;
  --catnip-color-brand-600: #0284c7;
}
html
<html data-theme="brand"></html>

Match with ~= so the theme also applies in a list such as data-theme="brand dark". Dark tokens reference the brand scale, so a theme that remaps --catnip-color-brand-* gets dark mode without its own dark values. Dark mode uses different steps than light mode (for example 200 for primary text and 975 for subtle primary backgrounds), so remap every step from 25 to 975, not only the two shown here.

On a wrapper, repeat every custom property whose value references a variable you override, using the same var() expression as :root. Indirect references count. --catnip-shadow-focus-default references --catnip-color-border-primary-focus, so the focus shadow has to be in the rule whenever that border token changes. When the theme remaps brand, copy the dependent declarations from endui-scoped.css (primary content, background, and border, graphic primary, logo background, and the default focus shadow). Put those repeated declarations in a :where() rule, as endui-scoped.css does. :where() adds no specificity, so with data-theme="brand dark" the dark values win whatever the stylesheet order. The block below shows the shape; a full brand remap needs the whole dependent set:

css
/* my-theme.css — subtree */
[data-theme~="brand"] {
  --catnip-color-brand-500: #0ea5e9;
  --catnip-color-brand-600: #0284c7;
}

:where([data-theme~="brand"]) {
  --catnip-color-content-primary-strong: var(--catnip-color-brand-600);
  --catnip-color-content-primary-strong-hover: var(--catnip-color-brand-500);
  --catnip-color-border-primary-focus: var(--catnip-color-brand-500);
  --catnip-shadow-focus-default: 0 0 0 3px var(--catnip-color-border-primary-focus);
}
html
<div data-theme="brand"></div>

Give the selector a specific value, such as [data-theme~="brand"]. A bare [data-theme] rule matches dark and EndUI as well and can override their values.

Prefer shipping shared brand themes the same way as EndUI (token JSON + build).

Token reference ​

See Design tokens for what ships today and Color tokens for the full color list.

Catnip Design System by Signicat