Skip to content

Theming & Tokens

Every visual property in the library — color, radius, spacing, motion timing, per-component density — is a CSS custom property. Components never hardcode colors or magic numbers; they read var(--*) and fall back to sane defaults. This page lists the public token contract and how to override it.

Two independent axes, both switched with an attribute on <html> (or any ancestor, since components pick them up via inheritance):

<html and-color="slate-amber" and-theme="compact" and-mode="dark"></html>
  • and-color — palette: primary/secondary color scales and the semantic surface colors derived from them.
  • and-theme — shape/density: radius, spacing, motion, and per-component sizing (navbar height, sidebar width, carousel controls, …).
  • and-modelight / dark, on top of either axis. Defaults to prefers-color-scheme; .dark is a supported shorthand for and-mode="dark".

Set by themes/colors/*.css (default, slate-amber, emerald-orange, violet-cyan, rose-teal, indigo-rose). Each palette defines:

TokenPurpose
--primary-50--primary-950Full brand color scale
--secondary-50--secondary-950Complementary accent scale
--primary, --primary-foregroundMain CTA/link/focus color + its text color
--secondary, --secondary-foregroundAccent badges/highlights + its text color
--background, --foregroundPage background/text
--card, --card-foregroundCard surface
--popover, --popover-foregroundOverlay surface (dropdown, tooltip, menu)
--muted, --muted-foregroundDe-emphasized surface/text
--accent, --accent-foregroundHover/selected surface
--destructive, --destructive-foregroundDanger actions
--border, --input, --ringBorder, form input border, focus ring
--success / --warning / --info (+ -foreground)Status colors

All are bare HSL triplets (243 75% 59%, no hsl() wrapper) so both the library and your own CSS can apply opacity modifiers: hsl(var(--primary) / 0.5) or, in Tailwind, bg-primary/50.

Set by themes/styles/*.css (default, compact, playful, retro, elegant):

TokenPurpose
--radiusBase corner radius — drives rounded-lg/md/sm everywhere
--spacing-factorMultiplier on the t-gap-* spacing scale
--border-widthDefault border thickness
--navbar-link-active-weight/letter-spacing/text-transformNav link typography
--theme-layout-max-widthPage/container max width
--theme-motion-fast/base/slowAnimation durations
--theme-easing-standardAnimation easing curve
--theme-focus-ring-width/offsetFocus ring sizing
--theme-surface-blur, --theme-overlay-opacity, --theme-modal-backdrop-blurOverlay/backdrop treatment
--theme-navbar-*Navbar height, padding, item/section gap, active indicator
--theme-sidebar-*Sidebar width (expanded/collapsed), header height, padding, icon size
--theme-carousel-*Carousel track radius, control size/offset/radius/border

--radius alone reshapes every rounded corner in the library (rounded-lg = var(--radius), rounded-md/rounded-sm scale proportionally from it, rounded-full is unaffected). The --theme-* tokens are what make and-theme feel different beyond corners — they drive real dimensions in and-navbar, and-sidebar, and-modal, and-carousel, and and-select.

Don’t fork a new theme file — layer your own values on top of an existing preset, the same pattern shadcn/ui and Radix Themes use. There is no separate “custom theme” mechanism to learn: and-theme is just an attribute selector, so any value you put on it — including one the library doesn’t ship — works as long as you write the matching CSS rule.

Start from the built-in preset closest to what you want (default, compact, playful, retro, or elegant) — see the Style tokens table above for what each changes. Load it the same way as any other theme:

import '@andersseen/web-components/tokens.css'; // default palette + default style

Add a rule scoped to your own theme name. Override only the tokens that differ from the base — everything else keeps inheriting from what you loaded in step 1:

[and-theme='brand'] {
/* Brand primary color, its scale, and the text color that sits on it */
--primary: 262 83% 58%;
--primary-foreground: 0 0% 100%;
/* Optional: full scale if you use primary-50..950 utilities anywhere */
--primary-500: 262 83% 66%;
--primary-600: 262 83% 58%;
--primary-700: 262 74% 48%;
}

Remember the values are bare H S% L% triplets (no hsl() wrapper) — that’s what lets both the library and hsl(var(--primary) / 0.5)-style opacity overrides work.

Same rule, same selector — add whichever --radius, --spacing-factor, or --theme-* tokens your brand needs:

[and-theme='brand'] {
/* ...colors from step 2... */
--radius: 0.375rem;
--theme-navbar-height: 3.75rem;
--theme-focus-ring-width: 3px;
}

If your brand color needs a different value in dark mode, nest it under and-mode="dark" (or .dark, the supported shorthand) combined with your theme selector:

[and-theme='brand'][and-mode='dark'],
[and-theme='brand'].dark {
--primary: 262 83% 70%;
--primary-foreground: 262 47% 14%;
}
<html and-theme="brand" and-color="indigo-rose"></html>

Because every component consumes these through var(--token, fallback), overriding a handful of tokens is enough — you don’t need to touch component CSS, and updates to the library won’t silently revert your overrides (they live in your own stylesheet, loaded after the library’s). To sanity-check a theme visually before wiring it into your app, the fastest loop is Storybook (pnpm storybook in packages/web-components) or the Angular demo app (pnpm start:demo) — both apply and-theme/and-color on <html> the same way, and the demo app’s navbar has a live theme/palette switcher you can use as a reference for how the attribute wiring should look in your own app.

See Styling Integration for the no-Tailwind and Tailwind-preset paths for loading these tokens in the first place.