Skip to content

@absoluteui/theme

Theme runtime, built-in themes, tokens, component tokens, contrast evaluation, and web fonts.

import { ... } from '@absoluteui/theme';

This package holds the design tokens, the built-in themes (absolute-default, absolute-dark, absolute-high-contrast, and absolute-system), the theme runtime that applies them, and the WCAG contrast engine. Reach for it once at startup, and again when you override tokens or check your own colors. The Theming guide explains tokens, overrides, and custom themes in depth; to build a theme from brand colors, use the Theme Designer engine.

How do I switch the theme, density, or direction at runtime?

Section titled “How do I switch the theme, density, or direction at runtime?”

Start the runtime once, then change configuration: the runtime keeps <html> in sync. Pass applyDirection: true if it should also set dir.

import { configureAbsoluteUI } from '@absoluteui/core';
import { startThemeRuntime } from '@absoluteui/theme';
const stop = startThemeRuntime({ applyDirection: true });
configureAbsoluteUI({ common: { theme: 'absolute-dark', density: 'compact', direction: 'rtl' } });
const html = document.documentElement;
[html.dataset.auiTheme, html.dataset.auiDensity, html.dir]; // => ['absolute-dark', 'compact', 'rtl']
stop(); // stops syncing; the attributes stay

How do I follow the operating system’s dark mode?

Section titled “How do I follow the operating system’s dark mode?”

Use absolute-system: its CSS follows prefers-color-scheme and prefers-contrast without any script. A family exported from the Theme Designer has the same <name>-system theme in its theme.css.

import { configureAbsoluteUI } from '@absoluteui/core';
import { startThemeRuntime } from '@absoluteui/theme';
startThemeRuntime();
configureAbsoluteUI({ common: { theme: 'absolute-system' } });
document.documentElement.dataset.auiTheme; // => 'absolute-system'

Every token is a --aui-* custom property. The built-in definitions give you the values in script; set the property on :root or on any container (in CSS or inline) to override it for that subtree.

import { absoluteDefault } from '@absoluteui/theme';
absoluteDefault.tokens['color-primary']; // => '#1E40AF'
const panel = document.createElement('section');
document.body.append(panel);
panel.style.setProperty('--aui-radius-md', '0.75rem');
getComputedStyle(panel).getPropertyValue('--aui-radius-md'); // => '0.75rem'

Component tokens such as --aui-button-radius fall back to a global token until you set them. Set one on a container, or put it in a registered theme so it follows theme switches.

import { absoluteDefault, applyTheme, getComponentToken, registerTheme } from '@absoluteui/theme';
getComponentToken('button-radius')?.fallback; // => 'radius-md'
registerTheme({ ...absoluteDefault, name: 'acme-hr', tokens: { ...absoluteDefault.tokens, 'button-radius': '999px' } });
applyTheme('acme-hr');
getComputedStyle(document.documentElement).getPropertyValue('--aui-button-radius'); // => '999px'

Registered themes are applied as constructable stylesheets, so they work under a strict Content Security Policy.

How do I check the contrast of my own color pair?

Section titled “How do I check the contrast of my own color pair?”

contrastRatio measures two parsed colors; compare it with WCAG_AA_MINIMUM and ask suggestForeground for the nearest passing color. evaluateThemeContrast runs the same check over every pair a theme declares.

import { WCAG_AA_MINIMUM, absoluteDefault, contrastRatio, evaluateThemeContrast, parseColor, suggestForeground } from '@absoluteui/theme';
const text = parseColor('#94A3B8');
const card = parseColor('#FFFFFF');
const ratio = text && card ? contrastRatio(text, card) : 0; // => ≈ 2.56
ratio >= WCAG_AA_MINIMUM.text; // => false
const fixed = text && card ? suggestForeground(text, card, WCAG_AA_MINIMUM.text) : null; // => '#6C7786'
const report = evaluateThemeContrast({ ...absoluteDefault, name: 'acme', tokens: { ...absoluteDefault.tokens, 'color-text-muted': '#94A3B8' } });
report.failed.length; // => 4

The default font stack starts with Noto Sans but never downloads it. Opt in by importing the loader stylesheet, and allow the Google Fonts hosts in your Content Security Policy. A hosted font service receives every visitor’s IP address, so self-hosting with @font-face is often the better choice.

import '@absoluteui/theme/fonts/noto-sans.css';
import { webFontHosts } from '@absoluteui/theme';
const csp = `style-src 'self' ${webFontHosts.google.styleSrc}; font-src 'self' ${webFontHosts.google.fontSrc}`;

Theme names must be built in or registered, built-in names are reserved, and token values are checked against a safe CSS grammar (no url(), braces, or semicolons), so imported themes cannot inject CSS. The runtime needs a DOM: start it in browser code, not during server rendering.

import { absoluteDefault, applyTheme, registerTheme } from '@absoluteui/theme';
applyTheme('acme-hr'); // => throws THEME_UNKNOWN
registerTheme({ ...absoluteDefault }); // => throws THEME_RESERVED_NAME
registerTheme({ ...absoluteDefault, name: 'acme-hr', tokens: { ...absoluteDefault.tokens, 'color-bg': 'url(/x.png)' } }); // => throws THEME_INVALID

componentTokensToScss

SCSS map $aui-component-tokens: (component: (property: fallback)) for aui.ctoken().

function componentTokensToScss(): string;

getComponentToken

Look up a component token by its flat name.

function getComponentToken(name: string): (ComponentTokenSpec & {
readonly component: string;
readonly property: string;
}) | undefined;

componentTokenNames

Flat component token names (button-radius), in registry order.

const componentTokenNames: readonly string[];

componentTokens

const componentTokens: Readonly<Record<string, ComponentTokenGroup>>;

ComponentTokenGroup

interface ComponentTokenGroup {
readonly tokens: Readonly<Record<string, ComponentTokenSpec>>;
readonly contrastPairs: readonly ContrastPair[];
}

ComponentTokenSpec

interface ComponentTokenSpec {
readonly fallback: string;
readonly kind: ComponentTokenKind;
readonly usage: string;
}

ComponentTokenKind

type ComponentTokenKind = 'color' | 'dimension' | 'shadow' | 'font-weight';

composite

Source-over alpha compositing of top onto an opaque bottom.

function composite(top: RGBA, bottom: RGBA): RGBA;

contrastRatio

function contrastRatio(a: RGBA, b: RGBA): number;

evaluateThemeContrast

Evaluate every declared contrast pair of a theme.

function evaluateThemeContrast(theme: ThemeDefinition): ContrastReport;

parseColor

function parseColor(value: string): RGBA | null;

relativeLuminance

function relativeLuminance(c: RGBA): number;

suggestForeground

Nearest foreground (mixed toward black or white) that meets required against bg.

function suggestForeground(fg: RGBA, bg: RGBA, required: number): string | null;

WCAG_AA_MINIMUM

WCAG 2.2 AA minimums: 1.4.3 (text), 1.4.3 large text, 1.4.11 (non-text).

const WCAG_AA_MINIMUM: Readonly<Record<ContrastKind, number>>;

ContrastReport

interface ContrastReport {
readonly theme: string;
readonly passed: readonly ContrastResult[];
readonly failed: readonly ContrastResult[];
readonly errors: readonly string[];
}

ContrastResult

interface ContrastResult {
readonly rule: 'WCAG 2.2 1.4.3' | 'WCAG 2.2 1.4.11';
readonly pair: ContrastPair;
readonly ratio: number;
readonly required: number;
readonly pass: boolean;
readonly severity: Severity;
readonly remediation: string | null;
}

RGBA

interface RGBA {
r: number;
g: number;
b: number;
a: number;
}

Severity

axe-core impact levels; contrast results use critical and serious, designer advisories add the others.

type Severity = 'critical' | 'serious' | 'moderate' | 'minor';

optionalTokenProblem

Why a value is not allowed for an optional token, or null when it is.

function optionalTokenProblem(name: string, value: string): string | null;

optionalTokenNames

const optionalTokenNames: readonly string[];

optionalTokens

const optionalTokens: Readonly<Record<string, OptionalTokenSpec>>;

OptionalTokenSpec

interface OptionalTokenSpec {
readonly kind: OptionalTokenKind;
readonly usage: string;
readonly values?: readonly string[];
}

OptionalTokenKind

Optional global tokens (the specification “scrollbar appearance where standards allow”). Unlike role tokens they are not required for theme completeness and have no default: while unset, the declarations that read them are invalid at computed-value time and the user agent’s native appearance applies. Uses the standard CSS Scrollbars Styling properties scrollbar-color and scrollbar-width.

type OptionalTokenKind = 'color' | 'keyword';

applyTheme

Apply a theme by name to a target element (default: the document element).

function applyTheme(name: string, options?: ApplyThemeOptions): void;

getTheme

function getTheme(name: string): ThemeDefinition | undefined;

registerTheme

Register a custom theme (validated, the specification). Returns the normalized definition.

function registerTheme(input: unknown): ThemeDefinition;

startThemeRuntime

Keep the target in sync with global configuration (theme, density, optional direction). Returns a disposer.

function startThemeRuntime(options?: Omit<ApplyThemeOptions, 'density'>): Disposer;

builtInThemeNames

Built-in theme names accepted by the theme configuration option.

const builtInThemeNames: readonly ["absolute-default", "absolute-dark", "absolute-high-contrast", "absolute-system"];

ApplyThemeOptions

interface ApplyThemeOptions {
target?: HTMLElement;
density?: 'compact' | 'comfortable' | 'spacious';
applyDirection?: boolean;
}

isSafeTokenValue

function isSafeTokenValue(value: unknown): value is string;

themeToCssText

Theme as CSS text scoped to a selector, inside the aui.theme cascade layer.

function themeToCssText(theme: ThemeDefinition, selector?: string): string;

tokensToDeclarations

CSS declarations for a token map (custom properties prefixed --aui-).

function tokensToDeclarations(tokens: Readonly<Record<string, string>>): string;

tokensToJson

Design-token JSON.

function tokensToJson(themeList: readonly ThemeDefinition[]): string;

tokensToScss

SCSS maps for all given themes plus densities.

function tokensToScss(themeList: readonly ThemeDefinition[]): string;

validateTheme

Validate an untrusted theme (e.g. imported JSON) against schema version 1.

function validateTheme(input: unknown): ThemeDefinition;

absoluteDark

const absoluteDark: ThemeDefinition;

absoluteDefault

const absoluteDefault: ThemeDefinition;

absoluteHighContrast

const absoluteHighContrast: ThemeDefinition;

densities

Density overrides applied over the comfortable baseline.

const densities: Readonly<Record<'compact' | 'spacious', TokenMap>>;

requiredTokenNames

Every theme must define exactly these tokens (the specification theme completeness).

const requiredTokenNames: readonly string[];

themes

const themes: {
readonly 'absolute-default': ThemeDefinition;
readonly 'absolute-dark': ThemeDefinition;
readonly 'absolute-high-contrast': ThemeDefinition;
};

ContrastPair

interface ContrastPair {
readonly foreground: string;
readonly background: string;
readonly backdrop?: string;
readonly kind: ContrastKind;
readonly usage: string;
}

ThemeDefinition

interface ThemeDefinition {
readonly schemaVersion: 1;
readonly name: string;
readonly mode: ThemeMode;
readonly seeds: TokenMap;
readonly tokens: TokenMap;
readonly contrastPairs: readonly ContrastPair[];
}

ContrastKind

type ContrastKind = 'text' | 'large-text' | 'non-text';

ThemeMode

Built-in themes. Seeds are brand hues and are never consumed by components directly; components use role tokens only. This module is the single source of truth: SCSS maps, CSS custom properties, tokens.json, and the contrast gate are all generated from it.

type ThemeMode = 'light' | 'dark' | 'high-contrast';

TokenMap

type TokenMap = Readonly<Record<string, string>>;

webFontProblem

Why a web font entry is invalid, or null when it is valid.

function webFontProblem(font: unknown): string | null;

webFontProviderOf

Provider implied by a stylesheet URL.

function webFontProviderOf(stylesheet: string): WebFontProvider;

webFontsToCss

@import rules for the given fonts (valid only at the top of a stylesheet).

function webFontsToCss(fonts: readonly WebFont[]): string;

defaultWebFonts

The default theme family’s web fonts.

const defaultWebFonts: readonly WebFont[];

notoSansStylesheet

Google Fonts CSS for Noto Sans: italic, width 62.5-100, weight 100-900.

const notoSansStylesheet = "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wdth,wght@0,62.5..100,100..900;1,62.5..100,100..900&display=swap";

webFontHosts

Hosts each provider serves from, for Content-Security-Policy style-src and font-src.

const webFontHosts: Readonly<Record<Exclude<WebFontProvider, 'custom'>, {
readonly styleSrc: string;
readonly fontSrc: string;
}>>;

WebFont

interface WebFont {
readonly provider: WebFontProvider;
readonly family: string;
readonly stylesheet: string;
}

WebFontProvider

Web fonts (the specification typography and font stacks). A theme lists the font stylesheets it needs; the font stack tokens name the families. Loading is always opt-in: absolute-default.css never requests a third-party font. The default theme’s font-family-sans starts with “Noto Sans”, and @absoluteui/theme/fonts/noto-sans.css loads it from Google Fonts for applications that choose to.

Privacy and CSP: a hosted font service receives every visitor’s IP address and user agent. Applications must allow its hosts in style-src and font-src, and decide on consent where that applies (for example the GDPR); self-hosting avoids both.

type WebFontProvider = 'google' | 'bunny' | 'custom';