UIdocs/ui/theming.md

Theming

The kit's look comes from CSS custom properties (--a-*) declared in @layer arachne.tokens. Palettes, dark mode and per-component tokens all come down to setting those variables.

Guides: getting started · customization · theming · accessibility · SSR & hydration · component reference

Core tokens #

TokenRole
--a-inkBody text
--a-paperSurfaces (cards, inputs, menus)
--a-canvasPage background, recessed areas
--a-surface, --a-surface-raisedAlternate and raised surfaces
--a-mutedSecondary text; meets 4.5:1 on paper and canvas
--a-line, --a-hairlineBorders and dividers
--a-accent, --a-accent-softBrand colour and its tinted wash
--a-danger, --a-success, --a-warning, --a-infoSemantic tones
--a-accent-ink, --a-danger-ink, --a-success-ink, --a-warning-ink, --a-info-inkText on a solid tone fill (buttons, badges); chosen for AA contrast
--a-radius (--a-radius-none / -sm / -lg)Corner radius
--a-font, --a-font-display, --a-font-monoTypefaces
--a-shadow, --a-shadow-md, --a-shadow-lg, --a-shadow-xlElevation
--a-ring, --a-focus-ringFocus indication
--a-duration-fast, --a-duration, --a-duration-slow, --a-ease, --a-ease-out, --a-ease-springMotion
--a-z-sticky, --a-z-dropdown, --a-z-overlay, --a-z-toast, --a-z-tooltipStacking order

If you set tokens by hand, check each tone's -ink partner: white text on a light custom accent needs --a-accent-ink set to a dark colour. applyPalette does this for you when accentInk is omitted.

Components add their own tokens on top, e.g. --a-btn-height, --a-modal-width and --a-tab-x. Set them on the component through style/styles, or in a CSS rule for the component class.

Palettes #

Thirty-two named presets map a brand palette onto these roles. Graphite is the default.

import { applyPalette, applyRadius, paletteStyle, palettes } from "@arachnejs/ui";

const restore = applyPalette("lagoon"); // writes --a-* variables on <html>; returns an undo function
applyRadius("lg"); // "none" | "sm" (default) | "lg"
applyPalette({ accent: "#7c3aed" }); // partial palette over the defaults; accentInk is picked for contrast
Object.keys(palettes); // preset names

Scope a palette to one subtree with an inline style:

<section style={paletteStyle("marina")}>…</section>

applyPalette sets inline custom properties, and inline properties outrank the dark-theme rules. So a palette and dark mode are alternatives on the same element. To support both, apply palettes to a subtree, or switch the palette when the theme changes.

Dark mode #

Three ways to opt in:

<html data-theme="dark">    <html data-theme="system">  <div class="a-theme-dark">  ```

The dark token set swaps surfaces and ink, uses amber as the accent, and pairs each tone with a dark `-ink`, so filled buttons and badges stay readable. `ThemeToggle` renders the switch. Apply the theme in its `onChange`:

```tsx
<ThemeToggle
    value={theme()}
    onChange={(next) => {
        theme.set(next);
        document.documentElement.dataset.theme = next;
    }}
/>

Motion #

Enter and exit animations use the motion tokens and run on data-state="open" | "closed", managed by createPresence. Under prefers-reduced-motion: reduce every transition and animation collapses, and overlays unmount immediately. To change the feel app-wide, set the duration and easing tokens.