Theming
Built-in themes
Set data-theme on the root element (<html> or any container) to switch between three built-in themes:
data-theme | Description |
|---|---|
light (default) | Light |
dark | Dark |
high-contrast | High contrast (WCAG AAA friendly, stronger borders/text contrast) |
<html data-theme="dark">
…
</html>document.documentElement.dataset.theme = 'high-contrast'Skin presets (optional)
@oas-ui/theme/skins.css ships 6 brand-color skin presets (violet / emerald / rose / amber / graphite / teal): layer data-skin on top of data-theme to switch, freely combinable with the three built-in themes (a skin only changes the brand hue, not sizes/structure).
<!-- opt-in: no effect unless this file is imported -->
<link rel="stylesheet" href="https://unpkg.com/@oas-ui/theme@2/skins.css" />
<html data-theme="dark" data-skin="violet">
…
</html>document.documentElement.dataset.skin = 'emerald' // violet | emerald | rose | amber | graphite | tealA skin only overrides --oas-color-primary; the derived steps (-hover / -active / -primary-text / focus ring) follow automatically. For deeper customization (surfaces / radius / density) use the CSS variable overrides below.
Liquid Glass (glass.css)
@oas-ui/theme/glass.css provides a liquid-glass material layer (optional, same mechanism as skins.css: opt-in, zero impact when not loaded)—translucent surfaces + backdrop blur + specular edge ring + larger radii + deeper layered shadows. Stack data-glass on top of data-theme / data-skin; all three compose freely:
<link rel="stylesheet" href="https://unpkg.com/@oas-ui/theme@2/glass.css" />
<html data-theme="dark" data-glass>
…
</html>Mechanism: 25 floating-surface components (modal / drawer / popover / tooltip / dropdown / select-family panels / message / snackbar / bottom-sheet / app-bar, etc.) uniformly consume --oas-glass-blur (backdrop-filter tier) and --oas-glass-ring (specular outline ring); nine controls/nav/notification components (button / switch / slider / app-bar / bottom-navigation / message / toast / snackbar / notification) additionally consume --oas-glass-refraction (edge-refraction displacement filter; content panels excluded; auto-disabled for disabled buttons and while the app-bar overflow panel is open). Without glass.css everything falls back to none / transparent, leaving components exactly as before.
Relationship to Apple's full definition (honest note): this material layer is a static approximation—it delivers translucency, gaussian blur, specular edge ring, and layered shadows. The signature edge refraction distortion now ships as a static displacement approximation (feDisplacementMap edge-outline displacement—only the edge band distorts, the center never does; self-contained data-URI filter that resolves inside shadow DOM). Dynamic fluidity (interaction-driven morphing and environment-reactive live highlights) is provided by the optional glass-fluid.js runtime (pointer specular highlight + press tightening; see Liquid Glass). Engine boundary (honest note): the refraction displacement takes real effect in Chromium-based browsers; Firefox does not execute data-URI SVG filters via CSS filter and degrades safely to no refraction (rendering is unaffected).
Boundaries:
- the material needs backdrop contrast—richest over colorful gradients/photos, recedes on flat same-hue backgrounds (text stays legible via token safe tiers);
- text safety is a hard constraint: the dark tier uses low-alpha, high-transparency surfaces (translucency is the signature look of dark glass; alpha tiers are calibrated together with text tiers; perceptual-contrast gate ≥60, see the gallery);
- blur applies only to container-level surfaces (rows/long lists are excluded to avoid the cost of blurring large areas);
- disabled under the
high-contrasttheme (solid accessibility tier wins); - local opt-out: override
--oas-glass-blur: none; --oas-glass-ring: transparent; --oas-glass-refraction: ;on any container (the refraction opt-out value must be empty, notnone: button hover/pressed/selected states composebrightness()with this variable, andnonemixed with functions invalidates the whole filter declaration, breaking the brightness feedback; an empty value is valid on both paths). For dense lists, disabling refraction at the container avoids many small filter rasterizations; likewise disable it for app-bars hosting overlay content (dropdown/popover)—filterclips overflowing subtrees into the filter region (the built-in overflow panel is handled automatically). If your build chain strips empty custom properties (some minifiers), use--oas-glass-refraction: saturate(1)instead (an identity filter that is equally valid in compositions).
Live preview: Liquid Glass Gallery.
Custom themes (CSS variable overrides)
All components in principle reference semantic tokens only (see docs/ui-spec.md §1; a few extreme-value/preset exceptions exist), so you can customize brand colors by overriding CSS variables without touching component code:
:root {
/* Brand colors */
--oas-color-primary: #7c3aed;
--oas-color-primary-hover: #8b5cf6;
--oas-color-primary-active: #6d28d9;
/* Text */
--oas-color-text-primary: #18181b;
--oas-color-text-secondary: #71717a;
/* Sizing */
--oas-radius-md: 8px;
--oas-control-height-md: 36px;
}Token overview
| Group | Variables |
|---|---|
| Brand | --oas-color-primary(-hover/-active)、--oas-color-success、--oas-color-warning、--oas-color-danger |
| Text | --oas-color-text-primary/-secondary/-disabled |
| Border/bg | --oas-color-border、--oas-color-bg(-hover/-disabled)、--oas-color-overlay |
| Font size | --oas-font-size-xs/sm/md/lg/xl |
| Spacing | --oas-space-1/1_5/2/2_5/3/4/4_5/5/6 (4px base, half steps included) |
| Radius | --oas-radius-xs/sm/md/lg/xl |
| Controls | --oas-control-height-xs/sm/md/lg/xl |
| Motion | --oas-transition-fast/base、--oas-ease-out/in-out |
| Z-index | --oas-z-dropdown/sticky/fixed/overlay/modal/message/toast/tooltip |
| Focus ring | --oas-focus-ring |
Reduced motion
The library ships prefers-reduced-motion support: when the system has "reduce motion" enabled, all transitions/animations are shortened automatically.
Customization capability matrix
Theme / skin / layout / style are four orthogonal, freely combinable control axes; the final "style" is what they jointly determine:
| Axis | Meaning | Mechanism | How far |
|---|---|---|---|
| Theme | Color mode (light / dark / contrast) | data-theme (light / dark / high-contrast) | Any color mode; custom via tokens |
| Skin | Brand hue (primary) | data-skin + skins.css (6 presets) | Any primary; derived steps follow automatically |
| Layout | Sizing / spacing / density / radius / container | --oas-space-* / radius-* / control-height-* / container-*; size presets | Any density / radius / container; RTL auto-mirrors |
| Style | Typography / motion / elevation / focus ring / contrast | --oas-font-size-* / transition / ease / shadow-* / focus-ring + -text safe grades | Most design languages can be approximated |
Ceiling: anything expressible as a CSS variable (token) can be re-skinned purely via CSS, without touching components. Boundary: ① per-component structural variants (size / variant / status / shape attributes) are the component's own contract, not a global axis; ② swapping in a wholly different design language (font stack / motion-curve family / contrast tiers) is beyond what a single default token set can express — define it in your own application/design system (override tokens or build a theme layer on top); ③ arbitrarily re-rendering a component's internal structure / DOM is intentionally not supported (keeps components consistent).