Box functions

Everything Box carries that is not a prop: add props of your own, name a set of styles, register keyframes, sample a spring, switch themes and configure the engine — with the smallest real example of each.

The whole surface

Ten names, and you will use two of them. Box.components() is how a design system is written here, and Box.extend() is how the prop set grows. The other eight are for the day you need them.
NameWhat it is for
Box.extend()Add CSS variables, props of your own, and extra values on props that already exist. Afterwards they are indistinguishable from built-ins.
Box.components()Name a set of styles — with variants, named parts and inheritance — and wear it with component="card".
Box.keyframes()Register an @keyframes sequence whose steps are Box props. Nothing is emitted until a rule names it.
Box.spring()Sample a damped oscillator into { easing, duration } — the two halves the timing-function and duration props take. No runtime.
Box.viewTransition()Run a DOM change inside a view transition where the browser has one, and plainly where it has not — the same three promises either way.
Box.ThemeThe provider that writes a theme class onto <html> or onto a wrapper of its own, follows the system preference and persists a choice.
Box.useTheme()Read the theme the nearest provider settled on, and set it. Passing null hands control back to the system preference.
Box.configure()Tell the engine how to name classes, where to write rules and what the base class transitions. Call it once, before the first render.
Box.getVariableValue()The var(--…) reference behind a token, declared on first use — for handing a themed colour to something that takes a string.
useClassNames()Box props as a class attribute, for an element Box cannot render: a router’s NavLink, a motion.div, an icon from another library.
useVisibility()Open/closed state that closes itself on an outside press, on Escape, and optionally on scroll or resize.
Everything is registered on one engine, shared by the whole app, so these are called once at module scope — not in a component, not in an effect. Register before the first render and nothing has to be re-emitted.

Box.components()

Box.components({ card: { styles, variants, children, extends } })
A named set of styles with variants, named parts and inheritance. An element wears it with component="card", and props written on that element still win — a default is a starting point, not a lock. It is how every pre-built component here is styled, so restyling button or datagrid is the same call.
Register a card, and two variants of it
JSX
// boxExtends.ts
import Box from '@box-kite/react';

export const components = Box.components({
  card: {
    styles: {
      p: 5,
      borderRadius: 3,
      b: 1,
      bgColor: 'white',
      borderColor: 'slate-200',
      theme: { dark: { bgColor: 'slate-800', borderColor: 'slate-700' } },
    },
    variants: {
      danger: { borderColor: 'rose-400', bgColor: 'rose-50' },
      flat: { b: 0, shadow: 'none' },
    },
    children: {
      title: { styles: { fontSize: 16, fontWeight: 600, mb: 2 } },
    },
  },
});
Wear it
JSX
<Box component="card" variant="danger" p={6}>
  <Box component="card.title">Payment failed</Box>
  We could not charge the card on file.
</Box>

A name is not a type until you say so

variant="danger" is a type error until a declare module hands TypeScript what you registered — the names exist at runtime only. One file, written once, and every later Box.components() call it exports from is picked up automatically.
box.d.ts — the one file that teaches the names
JSX
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> {}
  }
}
In depth: Theme Setup

Box.extend()

Box.extend(variables, newProps, newValues)
Three arguments, and each one is a different kind of growth: variables declares CSS custom properties, new props adds props the registry does not have, and new values teaches a prop that already exists to accept one more. All three land in the same pipeline as the built-ins — typed, nestable, shared, server-rendered.
A brand colour, a prop of your own, and a new value on an existing prop
JSX
// boxExtends.ts
import Box from '@box-kite/react';

export const { extendedProps, extendedPropTypes } = Box.extend(
  // 1. Variables: declared in :root the first time something uses one.
  { 'brand-500': '#4f46e5', 'grid-gutter': '1.5rem' },
  // 2. Props of your own.
  {
    columnRule: [
      {
        values: ['thin', 'thick'] as const,
        styleName: 'column-rule-width',
        valueFormat: (value: string) => (value === 'thin' ? '1px' : '4px'),
      },
    ],
  },
  // 3. More values on props that already exist.
  {
    bgColor: [{ values: ['brand-500'] as const, styleName: 'background-color', valueFormat: (value, getVariable) => getVariable(value) }],
  },
);
Use them like any other prop
JSX
<Box bgColor="brand-500" columnRule="thick" hover={{ bgColor: 'brand-500/80' }} md={{ columnRule: 'thin' }} />

A variable is a value, not a second system

A token registered here is accepted wherever the palette is, opacity modifier included — "brand-500/80" is a color-mix over the variable, so it stays themeable. The types need the same declare module the components do, with ExtractBoxStyles<typeof extendedProps> for your new props and ExtractBoxStyles<typeof extendedPropTypes> for the new values.
In depth: Style Grouping

Box.keyframes()

Box.keyframes({ slideIn: { from: {…}, to: {…} } })
An @keyframes sequence whose steps are Box props rather than CSS — so a step is written in the same scale, tokens and all. Registering costs nothing: the engine writes a sequence the first time a rule names it.
Register it, then name it
JSX
Box.keyframes({
  slideIn: {
    from: { opacity: 0, translateY: 4 },
    to: { opacity: 1, translateY: 0 },
  },
});
On an element
JSX
<Box animationName="slideIn" animationDuration={300} animationFillMode="both" />
In depth: Animation

Box.spring()

Box.spring({ stiffness, damping, mass, velocity })
Spring physics sampled into a linear() curve and a settling time — the two halves the timing-function and duration props already take. There is no runtime: the spring is a value, so it shares a class like any other and costs nothing per frame.
A spring of your own — the curve on one prop, the settling time on the other
JSX
const bouncy = Box.spring({ stiffness: 220, damping: 12 });

<Box transitionTimingFunction={bouncy.easing} transitionDuration={bouncy.duration} hover={{ scale: 1.05 }} />;
Two of the four named ones — hover either
Hover: spring-bouncy
Hover: spring-gentle
JSX
<Box transitionTimingFunction="spring-bouncy" transitionDuration="spring-bouncy" hover={{ scale: 1.08 }}>
  Hover: spring-bouncy
</Box>
<Box transitionTimingFunction="spring-gentle" transitionDuration="spring-gentle" hover={{ scale: 1.08 }}>
  Hover: spring-gentle
</Box>

Four are already named

spring, spring-gentle, spring-bouncy and spring-snappy are values on the timing-function and duration props, so the common case needs no call at all — and their durations are multiples of --transitionTime, which reduced motion sets to zero.
In depth: Animation

Box.Theme

<Box.Theme use="global" storageKey="theme" globalStyles={…}>
The provider. It reads prefers-color-scheme, follows it live, persists an explicit choice, and writes the theme class and data-theme onto <html> (use="global") or onto a wrapper of its own (use="local", the default). Switching costs no re-render — it is one class moving on an ancestor.
Once, at the root
JSX
<Box.Theme use="global" storageKey="theme" globalStyles={{ colorScheme: 'light dark' }}>
  <App />
</Box.Theme>

A theme name is any identifier

light and dark are only the two you get for free. A theme={{ midnight: … }} block on any element works the moment a provider above it is called midnight, and a nested provider owns its own subtree outright.
In depth: Theme Setup

Box.useTheme()

const [theme, setTheme] = Box.useTheme()
Read what the nearest provider settled on, and set it. Passing null clears the stored choice and hands control back to the operating system.
A theme toggle
JSX
function ThemeToggle() {
  const [theme, setTheme] = Box.useTheme();

  return <Button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>{theme === 'dark' ? 'Light' : 'Dark'}</Button>;
}
In depth: Theme Setup

Box.configure()

Box.configure({ classNames, sink, transition })
How the engine names classes, where it writes rules, and what the base class transitions. The defaults suit a browser and a Node server alike, so most apps never call it — reach for it to make class names reproducible across processes, or to turn the base transition off.
Before the first render, once
JSX
Box.configure({
  // 'hashed' (default), 'readable' for tests, or 'stable' — content-hashed, so two processes agree.
  classNames: 'stable',
  // 'cssom' | 'textContent' | 'string' | 'element'. Defaults to the environment.
  sink: 'cssom',
  // What every Box transitions by default — a transition group, or false to declare nothing at all.
  transition: 'colors',
});

It re-emits everything

Changing the sink or the base transition after rules have been written drops the class-name cache and writes them all again, which is why this belongs at module scope and not in a component.

Box.getVariableValue()

Box.getVariableValue("sky-500")
The var(--…) reference behind a palette token, declaring it in :root the first time it is asked for. For handing a themed colour to something that takes a plain string — a canvas, a chart library, an SVG attribute somebody else renders.
A token as a string
JSX
const stroke = Box.getVariableValue('sky-500'); // 'var(--sky-500)'

For markup you do render, vars is the prop

vars={{ 'color-revenue': 'sky-500' }} declares --color-revenue on an element and everything inside it, so it nests in a theme and a breakpoint like any other prop. Reach for getVariableValue only when a string is genuinely what is wanted.

useClassNames()

const { className, styles } = useClassNames(props)
Box props as a className, for an element Box cannot render: a router’s NavLink, a motion.div, a component from another library that takes a class and nothing else. All the nesting works — hover, breakpoints, themes — because it is the same resolution Box does.
Styling somebody else's component
JSX
import { useClassNames } from '@box-kite/react';

function Crumb({ to, children }: { to: string; children: React.ReactNode }) {
  const { className, styles } = useClassNames({ color: 'sky-600', hover: { color: 'sky-400' } });

  return (
    <>
      {styles}
      <NavLink to={to} className={className}>
        {children}
      </NavLink>
    </>
  );
}

Render styles either way

styles is defined in element mode only, where the CSS travels as <style> elements React hoists. Everywhere else it is undefined and rendering it costs nothing — so that line is what to write in both.

useVisibility()

const [visible, setVisible, ref] = useVisibility(options)
Open/closed state that closes itself: an outside press, Escape, and optionally a scroll or a resize. The ref goes on the element that counts as inside.
A panel that dismisses itself
JSX
const [isVisible, setVisible, ref] = useVisibility<HTMLDivElement>({ hideOnScroll: true });

<Box ref={ref}>
  <Button onClick={() => setVisible(!isVisible)}>Filters</Button>
  {isVisible && <Box p={4}>…</Box>}
</Box>;

For a real layer, reach past it

A dismissable panel that also needs focus return, layering or the top layer is Popover, and the primitive under it is useDismiss from @box-kite/react/a11y. useVisibility is the small case: a disclosure that owns nothing else.