Theme Setup
Customize your app design by defining styles as a theme with variants.
Define Your Own Styles
JSX
// boxExtends.ts — register the styles, and export what you registered.
import Box from '@box-kite/react';
export const components = Box.components({
button: {
styles: {
bgColor: 'blue-500',
b: 0,
hover: {
bgColor: 'blue-400',
},
},
variants: {
primary: {
bgColor: 'sky-400',
hover: {
bgColor: 'sky-500',
},
},
secondary: {
bgColor: 'indigo-400',
hover: {
bgColor: 'indigo-500',
},
},
},
},
});Teach TypeScript the names
JSX
// box.d.ts — without this, variant="primary" is a type error: the names exist at runtime only.
import '@box-kite/react';
import { ExtractComponentsAndVariants } from '@box-kite/core/types';
import { components } from './boxExtends';
declare module '@box-kite/core/types' {
namespace Augmented {
interface ComponentsTypes extends ExtractComponentsAndVariants<typeof components> {}
}
}Use them
JSX
import Flex from '@box-kite/react/components/flex';
import Button from '@box-kite/react/components/button';
function App() {
return (
<Flex gap={3}>
<Button>Default</Button>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
</Flex>
);
}App-wide Styles (globalStyles)
JSX
import Box from '@box-kite/react';
// Apply Box props to <html> for app-wide, inheritable CSS like scrollbar-color, fontFamily, color.
// Only takes effect with use="global". Supports theme-keyed values.
function Providers({ children }: { children: React.ReactNode }) {
return (
<Box.Theme
use="global"
globalStyles={{
scrollbarColor: ['violet-500', 'transparent'],
scrollbarWidth: 'thin',
theme: {
dark: { scrollbarColor: ['violet-700', 'gray-900'] },
light: { scrollbarColor: ['violet-300', 'gray-100'] },
},
}}
>
{children}
</Box.Theme>
);
}
// Emits rules directly on <html>:
// html { scrollbar-color: var(--violet-500) var(--transparent); scrollbar-width: thin; }
// html.dark { scrollbar-color: var(--violet-700) var(--gray-900); }
// html.light { scrollbar-color: var(--violet-300) var(--gray-100); }Theme Switching
This is light theme
JSX
import Box from '@box-kite/react';
import Button from '@box-kite/react/components/button';
import Flex from '@box-kite/react/components/flex';
function App() {
return (
<Box.Theme theme="light">
<Sample />
</Box.Theme>
);
}
function Sample() {
const [theme, setTheme] = Box.useTheme();
return (
<Box
p={3}
borderRadius={2}
b={1}
theme={{
light: { color: 'indigo-950', bgColor: 'white' },
dark: { color: 'white', bgColor: 'indigo-950' },
}}
>
<Flex gap={3} ai="center">
<Button bgColor="transparent" onClick={() => setTheme('light')}>
Light
</Button>
<Button bgColor="transparent" onClick={() => setTheme('dark')}>
Dark
</Button>
<Box textTransform="capitalize" p={3}>
This is {theme} theme
</Box>
</Flex>
</Box>
);
}Animating the Switch
JSX
import Box from '@box-kite/react';
// viewTransition="reveal" grows the new theme out of the button that was pressed, as a circle;
// viewTransition (or "fade") cross-fades the page. Both animate a screenshot on the compositor, so a
// page of ten thousand elements switches as smoothly as a page of ten. Reduced motion skips it.
function Providers({ children }: { children: React.ReactNode }) {
return (
<Box.Theme use="global" storageKey="theme" viewTransition="reveal">
{children}
</Box.Theme>
);
}
// Every Box transitions its own colours, and color is inherited — so a theme switch would start a
// transition on every element on the page at once. <Box.Theme> pauses them while the theme changes,
// which leaves the view transition as the only thing that moves.A Theme Inside a Theme
Light in here, whatever the page around it is.
JSX
import Box from '@box-kite/react';
// A theme reaches the subtree it owns and stops at the next element that declares one, so a panel
// keeps its own theme inside a page that has another — the demo above is one, inside this page.
// What marks a theme root is data-theme: <Box.Theme> renders it beside the class, and a theme class
// you write by hand (the one on <html> in a prerendered shell) wants it beside it too.
function Panel() {
return (
<Box.Theme use="local" theme="light">
<Box
p={4}
borderRadius={2}
theme={{ light: { bgColor: 'white', color: 'slate-900' }, dark: { bgColor: 'slate-950', color: 'white' } }}
>
Light in here, whatever the page around it is.
</Box>
</Box.Theme>
);
}
// Each theme is one scoped rule:
// @scope (.light) to ([data-theme]) { :scope .theme-light-bgColor-white { background-color: var(--white) } }
// The limit is what settles it. Two themes are two ancestor classes of the same specificity, so
// without one the rule written last would win however far away it was — and an outer rule carrying
// one more pseudo-class would win even then. Chrome 118+, Safari 17.4+, Firefox 128+.