A theme in Bones is a short CSS file, not a fork. Every design decision is a custom property prefixed --ub-, and components only ever read the semantic ones (--ub-bg, --ub-accent, ...), never raw palette steps. No provider, no config file; the switches are HTML attributes.

Color steps

The eight color scales run 50 through 950 on one shared lightness ladder, so any step swaps cleanly across hues; gray runs a slightly darker ladder tuned for backgrounds. Gray is the neutral default. Red, orange, green, and blue back the feedback roles (danger, warning, success, info); violet, teal, fuchsia, and rose are secondary colors for badges and accents. Raw steps are for themes to reference; components never read them directly.

gray
red
orange
green
teal
blue
violet
fuchsia
rose
50
100
200
300
400
500
600
700
800
900
950

Dark mode

Add data-theme="dark" to <html> or any subtree (a .dark class works too). There is no JavaScript involved; it's a CSS scope. To default to the system preference without a flash, run this before paint:

<script>
  try {
    var t = localStorage.getItem("ub-theme");
    if (t === "dark" || (!t && matchMedia("(prefers-color-scheme: dark)").matches))
      document.documentElement.setAttribute("data-theme", "dark");
  } catch (e) {}
</script>

Radius: rounded or pill

Interactive elements read --ub-radius-control, which is 8px by default. Set data-radius="pill" anywhere to make every control inside fully rounded.

<html data-radius="pill">

Your own accent

The neutral default uses near-black as the accent. Give it a color by overriding four tokens:

:root {
  --ub-accent: oklch(0.55 0.2 260);
  --ub-accent-hover: oklch(0.5 0.2 260);
  --ub-accent-contrast: white;
  --ub-ring: oklch(0.7 0.15 260);
}

Prebuilt accent themes

Each of the eight color scales ships as a ready accent theme. One import recolors everything that means "primary action" in both modes:

import "@usebones/tokens/themes/blue.css";

Full themes

A full theme replaces every color role, not just the accent. It ships inert and activates with the same attribute that drives dark mode. matrix is a quirky one to try: green phosphor, sharp corners.

import "@usebones/tokens/themes/matrix.css";

<html data-theme="matrix">

Creating a theme

A theme is a plain CSS file imported after index.css. Accent theme: override the four action tokens in both modes (copy themes/blue.css). Full theme: scope every color role under your own data-theme value:

[data-theme="my-theme"] {
  color-scheme: dark; /* or light: pick one, a full theme replaces both */

  /* backgrounds */
  --ub-bg: ...;
  --ub-bg-subtle: ...;
  --ub-bg-muted: ...;
  --ub-bg-muted-hover: ...;
  --ub-surface: ...;
  --ub-overlay: ...;

  /* text: primary, secondary, tertiary, disabled */
  /* borders: border, border-strong */
  /* interactive: accent, accent-hover, accent-contrast, ring */
  /* feedback: danger (+hover/contrast), success, warning, info */
  /* elevation: shadow-sm/md/lg */
  /* optional: the radius scale, like matrix does */
}

The override reference below is the checklist. Keep primary text at 4.5:1 contrast, leave danger red so destructive reads as destructive, and skip --ub-surface-glass; it derives from your surface automatically. A theme file works from anywhere; to ship one in @usebones/tokens, open a PR adding a file under css/themes/.

Switch to the Matrix

Styling component states

Below the token layer, every part has a stable class (ub-button, ub-select-trigger) and exposes state as data attributes (data-checked, data-highlighted, data-invalid). Each component page lists its hooks under "Styling states".

.ub-combobox-item[data-highlighted] {
  background: var(--ub-accent);
  color: var(--ub-accent-contrast);
}

Density is a token override

Because components read the size tokens for their default size, remapping them in a scope makes everything inside compact by default, no prop drilling:

.dense-section {
  --ub-font-size: var(--ub-font-size-compact);
  --ub-control-height: var(--ub-control-height-compact);
  --ub-icon-size-default: var(--ub-icon-size-compact);
}

The size tokens themselves are covered in Sizes.

Scoped themes

Tokens cascade, so any override can be scoped to a subtree: a sidebar that's always dark, a marketing section with a different accent. Wrap it in a class and override tokens there.

Override reference

Everything a theme can change, in one place. Colors list their light and dark defaults; the rest apply to both modes.

TokenLightDarkControls
--ub-bgwhitegray-950Page background
--ub-bg-subtlegray-50gray-900Wells, code blocks, panels
--ub-bg-mutedgray-200gray-800Hovers, secondary buttons, active pills
--ub-bg-muted-hovergray-300gray-700One step deeper on hover
--ub-surfacewhitegray-900Cards and popups
--ub-surface-glasssurface at 90%surface at 90%Translucent overlays, pairs with blur
--ub-overlayblack 40%black 60%Dimming behind dialogs
--ub-text-primarygray-950gray-50Headings and body text
--ub-text-secondarygray-600gray-400Supporting text, labels
--ub-text-tertiarygray-400gray-500Hints, captions, placeholders
--ub-text-disabledgray-300gray-700Disabled text
--ub-bordergray-200gray-800Hairlines and dividers
--ub-border-stronggray-300gray-700Inputs, select triggers, scrollbars
--ub-accentgray-900gray-50Primary actions, checked states
--ub-accent-hovergray-800gray-200Primary actions on hover
--ub-accent-contrastwhitegray-950Text and icons on the accent
--ub-ringgray-400gray-500Focus rings
--ub-dangerred-600red-500Destructive actions, invalid states
--ub-danger-hoverred-500red-400Destructive actions on hover
--ub-danger-contrastwhitewhiteText on danger
--ub-successgreen-600green-500Positive feedback
--ub-warningorange-500orange-400Caution
--ub-infoblue-600blue-400Informational
--ub-tint-<hue>-bg / -texthue-100 / hue-700 (gray 200 / 700)hue-900 / hue-300 (gray 800 / 300)Soft fill and text per palette hue; badges
--ub-shadow-sm / md / lgsoftstrongerElevation scale for overlays
--ub-z-overlay1000sameOne layer for every floating part; raise above taller app chrome
--ub-radius-xs to xl, full4 to 16px, 9999pxsameContainer rounding steps (radii, not sizes); pill mode also rounds the sm, md, and lg steps further
--ub-radius-control8px (pill: full)sameEvery interactive element
--ub-font-size / -compact16px / 14pxsameThe two text sizes
--ub-control-height / -compact36px / 28pxsameThe two control heights
--ub-icon-size-default / -compact16px / 14pxsameThe two icon sizes
--ub-duration-fast / base / slow120 / 180 / 280mssameAll motion; zero under reduced motion
--ub-ease-out / in-out / springcurvessameEasings; spring for thumbs and pills