Colors
Ooko exposes a theme-aware color palette through CSS custom properties. Components select a color family through their typed color prop and consume semantic shades instead of raw color values.
src/styles/tokens/primitives.scss is the single source of truth for palette values.
Color families
| Color | Intended use |
|---|---|
primary | Primary brand actions and emphasis. |
secondary | Secondary brand accents and supporting emphasis. |
info | Informational feedback and neutral notices. |
success | Successful actions and positive feedback. |
warning | Cautionary feedback requiring attention. |
danger | Destructive actions, errors, and critical states. |
neutral | Ink: text, borders, and default actions. |
neutral is a slate ink scale whose pale shades carry a faint blue tint. Its 800 shade (#163144) is the default text-strong and action color in light mode; the dark theme reverses the scale, so the same semantic shade becomes the pale end (#e3e8ed). OButton uses it by default, and primary stays the accent for focus, links, and selection.
Each family exposes primitive shades from 50 to 900:
color: var(--o-color-primary-700);
background: var(--o-color-success-100);Prefer semantic tokens in components and application UI. Use primitive tokens only when no semantic role describes the intended use.
Halo
--o-halo-1 to --o-halo-4 are the four stops of the ambient backdrop: mint, cream, lavender, and sky in light mode, and deep versions of the same full-page wash in dark mode. Put the o-halo utility class on the app shell (or body) to paint it, and lay glass surfaces on top:
<body class="o-halo"></body>Glass
Surfaces and controls are glass by default, so a halo shows through them:
| Token | Use |
|---|---|
--o-background-glass-strong | Cards, popups, dialogs, drawers, and form controls with text |
--o-background-glass | The lighter glass variants, for sparse panels |
--o-border-glass | The light hairline edge of a glass surface |
--o-backdrop-filter-glass | The blur behind a glass surface |
Without backdrop-filter, browsers show the translucent fill without blur.
Borders stay discreet and translucent, so they pick up the surface behind them: --o-border-color-control (form controls, triggers, checkboxes) and --o-border-color-subtle (dividers, popup and table edges). High contrast turns both back into the solid --o-border-neutral-default.
Themes
Light mode maps 50 to the lightest palette value and 900 to the darkest. Dark mode reverses that mapping, so semantic shade names preserve their relative emphasis without component-level theme overrides.
Apply a theme with data-ooko-theme:
<section data-ooko-theme="dark">
<!-- Ooko components -->
</section>data-ooko-theme="light" explicitly selects the light palette. Without an attribute, Ooko uses the browser or operating-system preference through prefers-color-scheme. Use useOokoTheme from ooko-ui/theme when an application needs a persistent override; see Theme.
Semantic shades
Semantic shades replace numeric palette indexes with names that communicate relative visual emphasis.
| Semantic shade | Primitive shade | Relative emphasis |
|---|---|---|
minimal | 50 | Lowest |
subtle | 100 | Very low |
lighter | 200 | Low |
light | 300 | Soft |
default | 400 | Default |
dark | 500 | Moderate |
darker | 600 | Strong |
deep | 700 | Very strong |
deeper | 800 | High contrast |
deepest | 900 | Highest contrast |
Every color family exposes background, border, and text semantic tokens:
.notice {
background: var(--o-background-info-subtle);
border-color: var(--o-border-info-default);
color: var(--o-text-info-deep);
}The available patterns are:
--o-background-{color}-{semantic-shade}
--o-border-{color}-{semantic-shade}
--o-text-{color}-{semantic-shade}--o-background-overlay is a translucent black surface in both themes. It is intended for blocking overlays and deliberately does not reverse with the palette.
Overriding the palette
Load a plain unlayered CSS file after Ooko's stylesheet. Define the replacement palette once, then map it in normal order for light mode and reverse order for dark mode:
:root {
--app-primary-50: #f0fdfa;
--app-primary-100: #ccfbf1;
--app-primary-200: #99f6e4;
--app-primary-300: #5eead4;
--app-primary-400: #2dd4bf;
--app-primary-500: #14b8a6;
--app-primary-600: #0d9488;
--app-primary-700: #0f766e;
--app-primary-800: #115e59;
--app-primary-900: #134e4a;
}
:root,
[data-ooko-theme="light"] {
--o-color-primary-50: var(--app-primary-50);
--o-color-primary-100: var(--app-primary-100);
--o-color-primary-200: var(--app-primary-200);
--o-color-primary-300: var(--app-primary-300);
--o-color-primary-400: var(--app-primary-400);
--o-color-primary-500: var(--app-primary-500);
--o-color-primary-600: var(--app-primary-600);
--o-color-primary-700: var(--app-primary-700);
--o-color-primary-800: var(--app-primary-800);
--o-color-primary-900: var(--app-primary-900);
}
[data-ooko-theme="dark"] {
--o-color-primary-50: var(--app-primary-900);
--o-color-primary-100: var(--app-primary-800);
--o-color-primary-200: var(--app-primary-700);
--o-color-primary-300: var(--app-primary-600);
--o-color-primary-400: var(--app-primary-500);
--o-color-primary-500: var(--app-primary-400);
--o-color-primary-600: var(--app-primary-300);
--o-color-primary-700: var(--app-primary-200);
--o-color-primary-800: var(--app-primary-100);
--o-color-primary-900: var(--app-primary-50);
}The --app-* properties belong to the consumer and may use any prefix. Override the complete 50–900 scale to keep primitive and semantic relationships coherent. Keep the reversed dark mapping so semantic shades retain the same theme behavior as the built-in palette. Adapt the theme selectors to a container when only part of an application should use the custom palette.
Consumer overrides must use the public --o-* contract. Do not override internal component color channels or copy component selectors solely to change a palette family.