Skip to content

Styles

Components in this section are headless — they ship with no CSS. The elements-kit/ui/styles layer is an optional theme package on top. Skip it entirely and style with your own system, or import only the pieces you want.

Install

import "elements-kit/ui/styles/theme.css";
import "elements-kit/ui/styles/material.css";
import "elements-kit/ui/styles/scaling.css";
import "elements-kit/ui/styles/radius.css";
import "elements-kit/ui/styles/space.css";
import "elements-kit/ui/styles/shadow.css";
import "elements-kit/ui/styles/typography.css";
import "elements-kit/ui/styles/cursor.css";
import "elements-kit/ui/styles/unset.css";
// One gray scale (pick one)
import "elements-kit/ui/styles/neutral/slate.css";
// One accent scale (any color)
import "elements-kit/ui/styles/accent/green.css";

Theming via data attributes

Switches are data-* attributes on a root container — not classes you toggle, not a JS provider.

<body
class="dark"
data-radius="medium"
data-scaling="md"
data-material-background="translucent"
data-accent="iris"
data-neutral="slate"
>
...
</body>
AttributeValues
data-radiusnone · small · medium · large · pill
data-scalingxs · sm · md · lg · xl
data-material-backgroundsolid · translucent
data-accentaccent name — see accent list
data-neutralgray · mauve · olive · sage · sand · slate

Light & dark

Color tokens flip when the container has .dark or .dark-theme. .light / .light-theme opt back in.

<html class="dark">...</html>

Pair with createMediaQuery to follow the OS preference:

import { createMediaQuery } from "elements-kit/utilities/media-query";
import { effect } from "elements-kit/signals";
const prefersDark = createMediaQuery("(prefers-color-scheme: dark)");
effect(() => {
document.documentElement.classList.toggle("dark", prefersDark());
});

Token reference

Override any token by re-declaring on :root or any container.

Color

Semantic — read these in your component CSS:

  • --color-background
  • --color-overlay
  • --color-material-solid, --color-material-translucent, --color-material, --backdrop-filter-material
  • --color-surface
  • --color-transparent
  • --focus-1..--focus-12, --focus-a1..--focus-a12 (alias of the active color)

Scales:

  • --neutral-1..--neutral-12 plus --neutral-a1..--neutral-a12 — the active neutral (set by neutral/<scale>.css or data-neutral).
  • --accent-1..--accent-12 plus --accent-a1..--accent-a12 — the active accent (set by accent/<color>.css or data-accent).
  • --<color>-1..--<color>-12 plus alpha — raw palette scales from palette/<color>.css, e.g. --blue-9.
  • --black-a1..--black-a12 and --white-a1..--white-a12 — alpha-on-color scales from palette/black-alpha.css / palette/white-alpha.css (auto-imported by theme.css).

Typography

--font-size-1..--font-size-9, --line-height-1..9, --letter-spacing-1..9, plus weights --font-weight-{light,regular,medium,bold}. Per-element families and overrides (--heading-*, --code-*, --strong-*, --em-*, --quote-*, --tab-*) live in typography.css.

Radius

--radius-1..--radius-6, --radius-pill, --radius-thumb. All scale with --radius-factor (driven by data-radius) and --scaling.

Spacing

--space-1 (4px) · --space-2 (8px) · --space-3 (12px) · --space-4 (16px) · --space-5 (24px) · --space-6 (32px) · --space-7 (40px) · --space-8 (48px) · --space-9 (64px). Each multiplied by --scaling.

Shadow

--shadow-1..--shadow-6. Light/dark variants ship out of the box, with a color-mix(in oklab, ...) upgrade for browsers that support it.

Cursor

--cursor-button, --cursor-checkbox, --cursor-link, --cursor-disabled, --cursor-menu-item, --cursor-radio, --cursor-slider-thumb, --cursor-slider-thumb-active, --cursor-switch. Defaults are platform-native; override per-element if you want a pointer button.

Unset native styles

Native form and text elements — <button>, <input>, <a>, <h1><h6>, lists, tables, <svg>, <img> — ship with browser defaults that bleed through component styles (gray button background, default font, list bullets, etc.). Apply class="unset" from unset.css to neutralise them in one line:

<button class="unset x-button" data-variant="solid" data-size="2">
Click me
</button>

unset uses :where(...) everywhere so it has zero specificity — your component classes (.x-button, .x-checkbox, …) and your own overrides always win.

Composite components in this kit (Button, future Checkbox, Switch, Listbox…) all rely on it. If you skip the import, native button rendering will leak through .x-button styling.

Customization

Override any token at any level. Container-scoped overrides win over :root:

.brand-card {
--color-material-solid: var(--accent-2);
--radius-3: var(--radius-5);
background: var(--color-material);
border-radius: var(--radius-3);
padding: var(--space-4);
}

To swap the active accent, import a different accent/<color>.css:

// Replace this …
import "elements-kit/ui/styles/accent/green.css";
// … with this.
import "elements-kit/ui/styles/accent/iris.css";

Available accents: amber, blue, bronze, brown, crimson, cyan, gold, grass, green, indigo, iris, jade, lime, mint, orange, pink, plum, purple, red, ruby, sky, teal, tomato, violet, yellow. Plus semantic aliases that point at the active neutral or a fixed hue: neutral, success, error, warning, info.

Available neutral scales (pick one): gray, mauve, olive, sage, sand, slate.