Theming
The @apx-ui/ds theming system is built on three orthogonal axes:
- Mode —
light·dark·system(follows OS preference) - Variant — stylistic family (
defaultadaptive,tetsu,origami,katana) - Platform — Safari / Apple-WebKit vs everything else (auto-detected; powers the adaptive
default) - Direction —
ltr·rtl
All three are driven from a single <ThemeProvider> and exposed as CSS variables, so toggling any
axis updates the page without re-rendering component class strings.
Mode
import { useMode } from '@apx-ui/ds';
function Header() {
const { mode, setMode } = useMode();
return (
<button type="button" onClick={() => setMode(mode === 'dark' ? 'light' : 'dark')}>
Toggle mode
</button>
);
}import { useMode } from '@apx-ui/ds';
function Header() {
const { mode, setMode } = useMode();
return (
<button type="button" onClick={() => setMode(mode === 'dark' ? 'light' : 'dark')}>
Toggle mode
</button>
);
}Variants
A variant is a partial theme override that swaps a coherent set of tokens at once (radii, spacing,
shadows, …). They're defined in @@apx-ui/ds/tokens/variants and can be extended by passing a
custom theme to <ThemeProvider>:
import { ThemeProvider, defineTheme } from '@apx-ui/ds';
const theme = defineTheme({
variants: {
brand: {
radius: { md: '999px' },
},
},
});
<ThemeProvider theme={theme} defaultVariant="brand">
…
</ThemeProvider>;import { ThemeProvider, defineTheme } from '@apx-ui/ds';
const theme = defineTheme({
variants: {
brand: {
radius: { md: '999px' },
},
},
});
<ThemeProvider theme={theme} defaultVariant="brand">
…
</ThemeProvider>;Direction
import { useThemeDirection } from '@apx-ui/ds';
function Bidi() {
const { dir, setDir } = useThemeDirection();
return (
<button type="button" onClick={() => setDir(dir === 'ltr' ? 'rtl' : 'ltr')}>
Flip direction
</button>
);
}import { useThemeDirection } from '@apx-ui/ds';
function Bidi() {
const { dir, setDir } = useThemeDirection();
return (
<button type="button" onClick={() => setDir(dir === 'ltr' ? 'rtl' : 'ltr')}>
Flip direction
</button>
);
}The provider writes dir to <html> and DS components use CSS logical properties
(margin-inline-start, etc.) plus Tailwind's rtl: variants, so flipping LTR ↔ RTL is a single
attribute change.
Override precedence
The DS resolves component styling in this strict order — useThemedClasses() enforces it
internally so every component is consistent:
- The component's own
cv()recipe (base + variant + compound classes). - The active theme's
styleOverrides[componentName]. - The
classNameprop the consumer passes. - The
sxprop (theme-aware style object) translated to inlinestyle. - The raw
styleprop the consumer passes (highest priority).
Each layer can override the one before it, giving consumers complete control without ever having to fork a component.