Customization
Understand VaneUI's powerful theming system and design token architecture.
VaneUI uses a powerful theme system based on ComponentTheme classes that define styling for each component. This page explains the theme architecture and how to customize it.
Each component has a ComponentTheme instance that defines:
BaseTheme subclasses that generate CSS classes// Simplified view of how a component theme is structuredconst buttonTheme = new ComponentTheme( "button", // Default tag "vane-button w-fit cursor-pointer", // Base classes { size: { px, py, text, gap }, // Size-related themes appearance: { bg, text, border }, // Appearance themes layout: { radius, border, ring } // Layout themes }, { md: true, primary: true }, // Defaults BUTTON_CATEGORIES // Prop categories);Each BaseTheme subclass generates specific CSS classes based on extracted props:
// FontSizeTheme returns consumer class for font sizeclass FontSizeTheme extends BaseTheme { getClasses(extractedKeys) { return ["text-(length:--fs)"]; // Consumes --fs CSS variable }}
// SimpleConsumerTheme returns classes that consume color variablesclass SimpleConsumerTheme extends BaseTheme { getClasses(extractedKeys) { if (!extractedKeys.appearance) return []; return ["[background:var(--bg-color)]", "text-(--text-color)"]; }}Use the useTheme hook to access the current theme:
import { useTheme } from '@vaneui/ui';
function CustomComponent() { const theme = useTheme(); // Access component themes const buttonTheme = theme.button; const cardTheme = theme.card; return <div>Custom component</div>;}VaneUI includes themes for all components:
UI Components:
button, badge, chip, code, input, checkbox, label, imgLayout Components:
card, divider, container, row, col, stack, sectiongrid2, grid3, grid4, grid5, grid6Typography Components:
text, title, pageTitle, sectionTitle, link, list, listItemSet default prop values for components:
import { ThemeProvider } from '@vaneui/ui';
const defaults = { button: { primary: true, // All buttons are primary by default md: true, // All buttons are medium size }, card: { rounded: true, // All cards have rounded corners }};
<ThemeProvider themeDefaults={defaults}> <Button>Primary medium button</Button> <Card>Rounded card</Card></ThemeProvider>Add additional CSS classes based on active props:
const extraClasses = { button: { primary: 'shadow-lg hover:shadow-xl transition-shadow', danger: 'animate-pulse', }, card: { filled: 'backdrop-blur-sm', }};
<ThemeProvider extraClasses={extraClasses}> <Button primary>Button with shadow</Button> <Button danger>Pulsing danger button</Button></ThemeProvider>Function for programmatic theme modifications:
<ThemeProvider themeOverride={(theme) => { // Modify button base classes theme.button.base += ' uppercase tracking-wide'; // Modify defaults theme.button.defaults = { ...theme.button.defaults, semibold: true }; return theme;}}> <App /></ThemeProvider>Control how nested ThemeProviders combine:
// Default: 'merge' - child theme merges with parent<ThemeProvider themeDefaults={{ button: { lg: true } }}> <ThemeProvider themeDefaults={{ button: { primary: true } }}> {/* Button gets both lg AND primary */} <Button>Large Primary</Button> </ThemeProvider></ThemeProvider>
// 'replace' - child theme replaces parent entirely<ThemeProvider themeDefaults={{ button: { lg: true } }}> <ThemeProvider themeDefaults={{ button: { sm: true } }} mergeStrategy="replace" > {/* Button only gets sm, not lg */} <Button>Small Only</Button> </ThemeProvider></ThemeProvider>Components output data attributes that CSS rules use for styling:
<button class="vane-button text-(length:--fs) py-(--py) ..." data-size="md" data-appearance="primary" data-variant="outline"> Click me</button>CSS rules in vars.css set variables based on these attributes:
.vane-button[data-size="md"] { --fs-unit: 8; --py-unit: 2; }
[data-variant="outline"][data-appearance="primary"] { --text-color: var(--color-text-primary); --bg-color: var(--color-bg-primary);}<Button primary lg filled>Click</Button>useTheme() to get theme.buttonThemedComponent calls theme.getComponentConfig(props){ size: 'lg', appearance: 'primary', variant: 'filled' }BaseTheme.getClasses() returns CSS classestwMerge(), data attributes are added<button class="..." data-size="lg" data-appearance="primary" data-variant="filled">vars.css set variables based on data attributes