@zentrades-ui/theme
v0.2.1
Published
Theme runtime for Zen UI.
Downloads
594
Readme
@zentrades-ui/theme
React theme provider for Zen UI components. Provides light/dark mode support and custom theme overrides.
Installation
pnpm add @zentrades-ui/themeBasic Usage
Wrap your application with ThemeProvider:
import { ThemeProvider } from "@zentrades-ui/theme";
function App() {
return (
<ThemeProvider theme="light">
<YourApp />
</ThemeProvider>
);
}Theme Modes
The provider supports light, dark, and system modes:
// Light mode
<ThemeProvider theme="light">
// Dark mode
<ThemeProvider theme="dark">
// Follow system preference
<ThemeProvider theme="system">Dynamic Theme Switching
Use the useTheme hook to access and change the current theme:
import { useTheme } from "@zentrades-ui/theme";
function ThemeToggle() {
const { theme, effectiveTheme, setTheme, toggleTheme } = useTheme();
return (
<div>
<p>Current setting: {theme}</p>
<p>Effective theme: {effectiveTheme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
<button onClick={() => setTheme("system")}>Use System</button>
</div>
);
}Note: To use
setThemeortoggleTheme, you must provide anonThemeChangehandler toThemeProvider:
function App() {
const [theme, setTheme] = useState<"light" | "dark" | "system">("light");
return (
<ThemeProvider theme={theme} onThemeChange={setTheme}>
<YourApp />
</ThemeProvider>
);
}Custom Theme Overrides
You can customize colors, typography, spacing, and borders while keeping the rest of the Zen UI defaults:
import { ThemeProvider, type CustomTheme } from "@zentrades-ui/theme";
const myBrandTheme: CustomTheme = {
// Color overrides (supports light/dark modes)
colors: {
light: {
backgroundBrand: "#0066cc",
backgroundBrandHover: "#0055aa",
backgroundBrandPressed: "#004488",
contentBrand: "#0066cc",
borderBrand: "#0066cc",
},
dark: {
backgroundBrand: "#3399ff",
backgroundBrandHover: "#66b3ff",
backgroundBrandPressed: "#99ccff",
contentBrand: "#3399ff",
borderBrand: "#3399ff",
},
},
// Typography overrides (mode-independent)
typography: {
fontFamilies: {
geist: "Inter, system-ui, sans-serif",
},
fontSizes: {
m: "0.875rem",
},
},
// Spacing overrides (mode-independent)
spacing: {
sm: "0.5rem",
md: "1rem",
lg: "1.5rem",
},
// Border overrides (mode-independent)
borders: {
radii: {
md: "8px",
lg: "12px",
},
},
};
function App() {
return (
<ThemeProvider theme="light" customTheme={myBrandTheme}>
<YourApp />
</ThemeProvider>
);
}How Custom Themes Work
- The default Zen UI theme is always applied as the base
- Your
customThemeoverrides only the specific tokens you define - Switching between light/dark mode automatically applies the correct custom values
- TypeScript provides autocomplete for all available tokens
Available Tokens
You can import token lists to see all available options:
import {
customizableColorTokens,
customizableSpacingTokens,
customizableFontSizeTokens,
customizableLineHeightTokens,
customizableFontWeightTokens,
customizableFontFamilyTokens,
customizableBorderRadiusTokens,
customizableBorderWidthTokens,
} from "@zentrades-ui/theme";
console.log(customizableColorTokens);
// ['contentPrimary', 'contentSecondary', 'backgroundBrand', ...]
console.log(customizableSpacingTokens);
// ['none', '2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', ...]
console.log(customizableFontFamilyTokens);
// ['geist', 'mono']Spacing Tokens
none,2xs,xs,sm,md,lg,xl2xl,3xl,4xl,5xl,6xl,7xl,8xl,9xl,10xl,11xl,12xlsection,page,container
Typography Tokens
Font Families:
geist- Primary sans-serif fontmono- Monospace font
Font Sizes:
xs,s,m,l,xl,2xl,3xl,4xl,5xl,6xl,7xl,8xl
Font Weights:
regular(400),medium(500),semibold(600),bold(700)
Line Heights:
xs,s,m,l,xl,2xl,3xl,4xl,5xl,6xl,7xl,8xl
Letter Spacing:
none,xs,s,m
Border Tokens
Border Radius:
xs,sm,md,lg,xl,pill,circle
Border Width:
none,xs,s,m,l,xl
Content Colors
contentPrimary,contentSecondary,contentTertiary,contentQuaternarycontentPrimaryInverse,contentSecondaryInverse,contentTertiaryInversecontentBrand,contentSecondaryBrandcontentLink,contentLinkHover,contentLinkPressedcontentInfo,contentInfoBoldcontentNotice,contentNoticeBoldcontentNegative,contentNegativeBoldcontentPositive,contentPositiveBoldcontentDisabled,contentDefaultWhitecontentAttention,contentActive
Background Colors
backgroundPrimary,backgroundSecondarybackgroundHover,backgroundPressed,backgroundSelected,backgroundSecondarySelectedbackgroundBrand,backgroundBrandHover,backgroundBrandPressedbackgroundSecondaryBrand,backgroundSecondaryBrandHover,backgroundSecondaryBrandPressed,backgroundSecondaryBrandSubtlebackgroundInfo,backgroundInfoSubtlebackgroundNotice,backgroundNoticeSubtlebackgroundNegative,backgroundNegativeHover,backgroundNegativePressed,backgroundNegativeSubtlebackgroundPositive,backgroundPositiveSubtlebackgroundInverse,backgroundInverseHoverbackgroundDisabledbackgroundAttention,backgroundActive
Border Colors
borderPrimary,borderSecondary,borderTertiary,borderQuaternaryborderBrand,borderSecondaryBrandborderFocus,borderInverse,borderDisabled,borderMonoborderInfo,borderNotice,borderNegative,borderPositiveborderAttention,borderActive
Surface Colors
surfaceL0,surfaceL1,surfaceL2,surfaceL3,surfaceL4,surfaceL5,surfaceL6
Overlay Colors
overlay50,overlay50Inverse
Example: Complete Brand Customization
import { ThemeProvider, type CustomTheme } from "@zentrades-ui/theme";
import { Button, Input } from "@zentrades-ui/components";
// Blue brand theme
const blueBrandTheme: CustomTheme = {
light: {
// Primary brand colors
backgroundBrand: "#2563eb",
backgroundBrandHover: "#1d4ed8",
backgroundBrandPressed: "#1e40af",
contentBrand: "#2563eb",
borderBrand: "#2563eb",
// Selection colors
backgroundSelected: "#dbeafe",
// Focus ring
borderFocus: "#2563eb",
},
dark: {
backgroundBrand: "#3b82f6",
backgroundBrandHover: "#60a5fa",
backgroundBrandPressed: "#93c5fd",
contentBrand: "#3b82f6",
borderBrand: "#3b82f6",
backgroundSelected: "#1e3a5f",
borderFocus: "#3b82f6",
},
};
function App() {
return (
<ThemeProvider theme="light" customTheme={blueBrandTheme}>
<div style={{ padding: 24 }}>
<Input placeholder="Enter your email" />
<Button variant="primary">Subscribe</Button>
</div>
</ThemeProvider>
);
}Custom Target Element
Apply theme to a specific element instead of document.body:
const containerRef = useRef<HTMLDivElement>(null);
<div ref={containerRef}>
<ThemeProvider theme="light" target={containerRef.current}>
{/* Theme only applied within this container */}
</ThemeProvider>
</div>TypeScript
The package exports useful types:
import type {
// Provider types
ThemeProviderProps,
ThemeMode, // "light" | "dark"
AllowedThemeOptions, // "light" | "dark" | "system"
// Custom theme types
CustomTheme,
ColorCustomization,
SpacingCustomization,
TypographyCustomization,
BorderCustomization,
// Token name types
ColorVarName,
SpacingVarName,
FontSizeVarName,
LineHeightVarName,
FontWeightVarName,
LetterSpacingVarName,
FontFamilyVarName,
BorderRadiusVarName,
BorderWidthVarName,
Theme,
} from "@zentrades-ui/theme";Exports
// Components & Hooks
export { ThemeProvider, useTheme } from "@zentrades-ui/theme";
// Token lists (for documentation/validation)
export {
customizableColorTokens,
customizableSpacingTokens,
customizableFontSizeTokens,
customizableLineHeightTokens,
customizableFontWeightTokens,
customizableLetterSpacingTokens,
customizableFontFamilyTokens,
customizableBorderRadiusTokens,
customizableBorderWidthTokens,
} from "@zentrades-ui/theme";
// CSS variable references (for advanced usage in vanilla-extract)
export {
colorVars,
spacingVars,
fontSizeVars,
lineHeightVars,
fontWeightVars,
letterSpacingVars,
fontFamilyVars,
borderRadiusVars,
borderWidthVars,
lightTheme,
darkTheme,
} from "@zentrades-ui/theme";