Themes
21 themes, every one accessible
Pick a mode (light, dark, hc), an accent and a shape. Themes are plain CSS attributes, so they work in every
framework and can be scoped to any part of a page. Each card below is a live, scoped theme.
import { applyTheme, saveTheme } from '@pelagic-ui/react';
applyTheme({ mode: 'dark', accent: 'indigo', shape: 'round' }); import { applyTheme } from '@pelagic-ui/vue';
applyTheme({ mode: 'hc' }); // high contrast import { applyTheme } from '@pelagic-ui/svelte';
applyTheme({ mode: 'system', accent: 'rose' }); <html data-theme="dark" data-accent="emerald" data-shape="sharp">
<!-- or scope a section: <section data-theme="dark"> --> <PelagicProvider mode="system" accent="indigo">
<App />
</PelagicProvider> Custom brand colors
Your color, every mode, still accessible
Pass any color to createTheme(). Pelagic keeps its hue and tunes lightness and chroma in OKLCH until every text,
control and focus pair passes WCAG 2.2 AA in light, dark and high contrast. Try a color that is hard to use, like a bright yellow.
Hex, rgb(), hsl(), oklch() or a color name.
That is not a color Pelagic can read.
- Light primary
- #c55000
- Dark primary
- #ff6a00
- High contrast
- #963b00
- WCAG pairs
- All pass
Generated CSS
Bake a brand into the build instead (CSS plus React Native and Flutter tokens):
node packages/core/scripts/build.mjs --brand "#ff6a00" --brand-name acme, or an pelagic.config.json with
{ "brands": [{ "name": "acme", "primary": "#ff6a00", "default": true }] }.
Light
Dark
High contrast
Color themes
Ten complete palettes on data-theme-preset: page, cards, text, borders, status colors and
their own accent, in light and dark (high contrast keeps pure black on white with the preset's accent).
Every preset, and every preset with each of the 9 accents, is checked against WCAG 2.2 AA in the build.
A brand color from createTheme({ primary, preset }) is fitted to the preset's surfaces.
Animated theme switch
switchTheme(next, { origin }) changes mode, accent, preset, shape or brand color and reveals the
new theme in a circle that grows from the control you used (View Transitions API). Browsers without it get a
short crossfade; reduced motion (the OS setting or data-a11y-motion reduce / off) switches
instantly. The choice is saved, and a pl-theme-change event keeps every control in sync.
import { switchTheme } from '@pelagic-ui/core/theme';
button.addEventListener('click', (event) => switchTheme({ mode: 'dark' }, { origin: event }));
switchTheme({ preset: 'abyss' }); // a whole palette
switchTheme({ brand: '#ff6a00' }); // any color, tuned to WCAG AA
// React: const { theme, switchTheme } = useThemeSwitch(); onClick={(e) => switchTheme({ mode: 'dark' }, e)}
// Vue: const { theme, switchTheme } = useThemeSwitch();
// Svelte: <button use:themeSwitch={{ mode: 'dark' }}> · $themeState
// Angular: inject(PelagicThemeService).switch({ mode: 'dark' }, { origin: $event }) Shapes
Corner radius is a theme setting too: data-shape="sharp | default | round".