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+.