# Start a project

_Box Kite 2.2.7 · a markdown copy of https://www.box-kite.dev/start-a-project/_

One command creates a Vite + React app with Box Kite, a design and a sample page in it, ready for npm run dev.

## One command

Pick a design and a language, then copy the command. Each card is drawn in its own design, so what you see is what the app starts as, in light and dark.

Aa Button Cancel Classic Indigo on cool grey — the look every component ships with. Aa Button Cancel Ember Warm paper, vermilion and a serif — editorial, quiet, and lit from within. Aa Button Cancel Azure Clear-sky blue on frosted glass — airy, precise, and bright in the dark. Aa Button Cancel Mono Ink on white in single pixels — flat, quiet, and exact.

LanguageTypeScriptJavaScript

**Mono, in TypeScript**

```shell
npm create @box-kite@latest my-app -- --design mono
cd my-app
npm run dev
```

It needs Node 22.12 or later. Run it without `--design` and it asks which design you want.

## What you get

The command runs Vite’s own `create-vite`, so the project is the one Vite gives you, with these changes:

- `@box-kite/react` and the design’s fonts are added to `package.json` and installed.
- `src/design.tsx` is the design: the two colour families every component is drawn in, the page’s colours and fonts, and each component’s styles, in light and dark.
- `src/main.tsx` wraps the app in it, and `src/index.css` keeps one rule.
- `src/App.tsx` is a sample page: buttons, a dialog, fields, choices, tabs and a data grid, with a switch for dark mode. The template’s own `App.css` and logos are removed.

**src/design.tsx**

```jsx
/**
 * Mono: ink #16191e on white in single pixels, with 1px edges, focus as the second pixel and no gradients.
 *
 * The whole design is in this file: the two palette families every component is drawn in (accent and
 * base), the page's colours and fonts, and the component styles, in light and dark. It is yours to edit.
 */
import '@fontsource-variable/roboto';
import Box, { type BoxComponentStyles, type BoxStyleProps } from '@box-kite/react';
import type { ReactNode } from 'react';

const FONT_BODY = "'Roboto Variable', 'Helvetica Neue', Arial, sans-serif";

/** The neutrals: page, text, borders. Repoint either family with a palette name ('stone') or a ramp like this. */
const BASE = {
  50: '#f7f8f9',
  100: '#eff1f3',
  200: '#e2e5e9',
  300: '#cdd1d7',
  400: '#9ba2ad',
  500: '#6b7380',
  600: '#4f5763',
  700: '#3b424d',
  800: '#2c323b',
  900: '#1d2127',
  950: '#16191e',
} as const;

/** The accent: buttons, focus, selection. */
const ACCENT = {
  50: '#f7f8f9',
  100: '#eff1f3',
  200: '#e2e5e9',
  300: '#cdd1d7',
  400: '#8c939e',
  500: '#2c323b',
  600: '#16191e',
  700: '#101216',
  800: '#0b0d10',
  900: '#08090b',
  950: '#050607',
} as const;

const INK = 'base-950';
const PAPER = 'base-50';

/** A filled control's focus: one pixel of page, then one of ink — the second pixel, kept apart so it shows on a fill. */
const RING_APART: BoxComponentStyles = {
  outline: 1,
  outlineStyle: 'solid',
  outlineOffset: 1,
  outlineColor: INK,
  theme: { dark: { outlineColor: PAPER } },
};

/** An outlined control's focus: the border turns ink and a second pixel sits right on it, so the line is 2px. */
const RING_TIGHT: BoxComponentStyles = {
  outline: 1,
  outlineStyle: 'solid',
  outlineOffset: 0,
  outlineColor: INK,
  borderColor: INK,
  theme: { dark: { outlineColor: PAPER, borderColor: PAPER } },
};

/** What floats: white, a hairline, and the one shadow the design allows — short and soft, so it lifts without glowing. */
const LAYER: BoxComponentStyles = {
  p: 1,
  b: 1,
  borderRadius: 1.5,
  bgColor: 'white',
  borderColor: 'base-200',
  shadow: 'lg',
  shadowColor: 'black/8',
  theme: { dark: { bgColor: 'base-900', borderColor: 'base-800', shadowColor: 'black/40' } },
};

/** One row in a layer: a grey fill under the pointer, the choice marked by weight rather than by colour. */
const LAYER_ITEM: BoxComponentStyles = {
  borderRadius: 1,
  hover: { bgColor: 'base-100' },
  focus: { bgColor: 'base-100' },
  selected: { bgColor: 'base-100', color: INK, fontWeight: 500, hover: { bgColor: 'base-200' } },
  theme: {
    dark: {
      hover: { bgColor: 'base-800' },
      focus: { bgColor: 'base-800' },
      selected: { bgColor: 'base-800', color: PAPER, hover: { bgColor: 'base-700' } },
    },
  },
};

/** Where the keyboard is in a listbox: the pointer's fill, with the outline back in a forced-colors mode. */
const LAYER_HIGHLIGHT: BoxComponentStyles = {
  outline: 0,
  bgColor: 'base-100',
  selected: { bgColor: 'base-200' },
  forcedColors: { outline: 2, outlineColor: 'Highlight' },
  theme: { dark: { bgColor: 'base-800', selected: { bgColor: 'base-700' } } },
};

/** A field at rest: white, a grey hairline that darkens under the pointer and turns to two pixels of ink on focus. */
const FIELD: BoxComponentStyles = {
  borderRadius: 1.5,
  borderColor: 'base-300',
  bgColor: 'white',
  shadow: 'none',
  hover: { borderColor: 'base-500' },
  focus: RING_TIGHT,
  theme: {
    dark: {
      bgColor: 'base-950',
      borderColor: 'base-700',
      hover: { borderColor: 'base-500' },
      focus: { outlineColor: PAPER, borderColor: PAPER },
    },
  },
};

/** The primary action: solid ink with a 1px edge of its own, so every button is the same height. Hover lifts it to #2c323b. */
const PRIMARY: BoxComponentStyles = {
  b: 1,
  bgGradient: 'none',
  bgColor: INK,
  borderColor: INK,
  color: 'white',
  shadow: 'none',
  hover: { bgColor: 'base-800', borderColor: 'base-800' },
  active: { bgColor: 'base-700', borderColor: 'base-700' },
  disabled: { bgColor: 'base-100', borderColor: 'base-200', color: 'base-400' },
  theme: {
    dark: {
      bgColor: PAPER,
      borderColor: PAPER,
      color: INK,
      hover: { bgColor: 'base-200', borderColor: 'base-200' },
      active: { bgColor: 'base-300', borderColor: 'base-300' },
      disabled: { bgColor: 'base-900', borderColor: 'base-800', color: 'base-600' },
    },
  },
};

/** The library's marks are white; on the dark page Mono fills a control with paper, so its mark is drawn in ink. */
const inkMark = (shape: string) =>
  `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns='http://www.w3.org/2000/svg' width='100%' viewBox='0 0 20 20'>${shape}</svg>`)}")`;
const INK_TICK = inkMark(
  "<path fill='none' stroke='#16191E' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M6 10l3 3l6-6'/>",
);
const INK_DASH = inkMark(
  "<line stroke='#16191E' stroke-linecap='round' x1='6' y1='10' x2='14' y2='10' stroke-width='2' />",
);
const INK_DOT = inkMark("<circle fill='#16191E' cx='10' cy='10' r='5'/>");

const heading: BoxComponentStyles = { fontWeight: 500, letterSpacing: 'tight', css: { fontFamily: FONT_BODY } };

Box.components({
  h1: { styles: heading },
  h2: { styles: heading },
  h3: { styles: heading },

  button: {
    styles: { ...PRIMARY, px: 4, borderRadius: 1.5, fontWeight: 500, focusVisible: RING_APART },
    variants: {
      secondary: {
        bgColor: 'white',
        color: INK,
        borderColor: 'base-300',
        hover: { bgColor: 'base-50', borderColor: INK },
        active: { bgColor: 'base-100', borderColor: INK },
        focusVisible: RING_TIGHT,
        theme: {
          dark: {
            bgColor: 'transparent',
            color: PAPER,
            borderColor: 'base-700',
            hover: { bgColor: 'base-800', borderColor: 'base-500' },
            active: { bgColor: 'base-700', borderColor: 'base-500' },
            focusVisible: { outlineColor: PAPER, borderColor: PAPER },
          },
        },
      },
      ghost: {
        bgColor: 'transparent',
        color: 'base-700',
        borderColor: 'transparent',
        hover: { bgColor: 'base-100', borderColor: 'transparent', color: INK },
        active: { bgColor: 'base-200', borderColor: 'transparent' },
        theme: {
          dark: {
            bgColor: 'transparent',
            color: 'base-300',
            borderColor: 'transparent',
            hover: { bgColor: 'base-800', borderColor: 'transparent', color: PAPER },
            active: { bgColor: 'base-700', borderColor: 'transparent' },
          },
        },
      },
    },
  },

  textbox: { styles: FIELD },
  textarea: { styles: FIELD },
  dropdown: {
    styles: FIELD,
    children: {
      items: { styles: { ...LAYER, gap: 0.5 } },
      item: { styles: LAYER_ITEM, variants: { highlighted: LAYER_HIGHLIGHT } },
      unselect: { styles: { borderRadius: 1 }, variants: { highlighted: LAYER_HIGHLIGHT } },
      selectAll: { styles: { borderRadius: 1 }, variants: { highlighted: LAYER_HIGHLIGHT } },
    },
  },
  combobox: {
    styles: { ...FIELD, hasDisabled: { bgColor: 'base-100' } },
    children: {
      items: { styles: { ...LAYER, gap: 0.5 } },
      item: { styles: LAYER_ITEM, variants: { highlighted: LAYER_HIGHLIGHT } },
      chip: {
        styles: {
          b: 1,
          borderRadius: 1,
          bgColor: 'white',
          borderColor: 'base-300',
          color: INK,
          theme: { dark: { bgColor: 'base-900', borderColor: 'base-700', color: PAPER } },
        },
      },
    },
  },

  checkbox: {
    styles: {
      // One pixel like every other edge, and a quarter more padding so the box stays 20px.
      b: 1,
      p: 2.25,
      borderRadius: 1,
      borderColor: 'base-400',
      hover: { borderColor: INK },
      focusVisible: RING_APART,
      checked: { bgColor: INK, borderColor: INK },
      indeterminate: { bgColor: INK, borderColor: INK },
      theme: {
        dark: {
          borderColor: 'base-500',
          hover: { borderColor: PAPER },
          checked: { bgColor: PAPER, borderColor: PAPER, css: { backgroundImage: INK_TICK } },
          indeterminate: { bgColor: PAPER, borderColor: PAPER, css: { backgroundImage: INK_DASH } },
        },
      },
    },
  },
  radioButton: {
    styles: {
      borderColor: 'base-400',
      focusVisible: RING_APART,
      checked: { bgColor: INK, borderColor: INK },
      hover: { borderColor: INK },
      theme: {
        dark: {
          borderColor: 'base-500',
          hover: { borderColor: PAPER },
          checked: { bgColor: PAPER, borderColor: PAPER, css: { backgroundImage: INK_DOT } },
        },
      },
    },
  },
  switch: {
    styles: {
      b: 1,
      bgColor: 'base-200',
      borderColor: 'base-300',
      insetShadow: 'none',
      before: { shadow: 'xs', shadowColor: 'black/20' },
      focusVisible: RING_APART,
      hover: { bgColor: 'base-300' },
      checked: { bgColor: INK, borderColor: INK, hover: { bgColor: 'base-800', borderColor: 'base-800' } },
      theme: {
        dark: {
          bgColor: 'base-800',
          borderColor: 'base-700',
          hover: { bgColor: 'base-700' },
          checked: {
            bgColor: PAPER,
            borderColor: PAPER,
            before: { bgColor: INK },
            hover: { bgColor: 'base-200', borderColor: 'base-200' },
          },
        },
      },
    },
  },
  slider: {
    children: {
      track: {
        styles: { height: 1, borderRadius: 0.5, bgColor: 'base-200', theme: { dark: { bgColor: 'base-800' } } },
      },
      fill: { styles: { bgGradient: 'none', bgColor: INK, theme: { dark: { bgGradient: 'none', bgColor: PAPER } } } },
      thumb: {
        styles: {
          width: 4,
          height: 4,
          ms: -2,
          b: 1,
          bgColor: 'white',
          borderColor: INK,
          ring: 0,
          shadow: 'sm',
          shadowColor: 'black/12',
          hover: { bgColor: 'base-100' },
          focusVisible: RING_APART,
          theme: { dark: { bgColor: INK, borderColor: PAPER, hover: { bgColor: 'base-800' } } },
        },
      },
    },
  },
  progress: {
    styles: { height: 1, borderRadius: 0.5, bgColor: 'base-200', theme: { dark: { bgColor: 'base-800' } } },
    children: {
      fill: {
        styles: {
          borderRadius: 0.5,
          bgGradient: 'none',
          bgColor: INK,
          theme: { dark: { bgGradient: 'none', bgColor: PAPER } },
        },
      },
    },
  },

  dialog: {
    styles: {
      p: 6,
      b: 1,
      borderRadius: 2,
      borderColor: 'base-200',
      shadow: 'xl',
      shadowColor: 'black/12',
      backdrop: { bgColor: 'base-950/40', backdropBlur: 'none' },
      theme: {
        dark: {
          bgColor: 'base-900',
          borderColor: 'base-800',
          shadowColor: 'black/50',
          backdrop: { bgColor: 'black/60' },
        },
      },
    },
    children: {
      title: { styles: { ...heading, fontSize: 20, lineHeight: 28 } },
      description: { styles: { color: 'base-500', theme: { dark: { color: 'base-400' } } } },
    },
  },
  popover: { styles: { ...LAYER, p: 4, borderRadius: 2 } },
  menu: {
    styles: LAYER,
    children: {
      item: { styles: { ...LAYER_ITEM, px: 2.5 } },
      separator: { styles: { mx: -1, borderColor: 'base-200', theme: { dark: { borderColor: 'base-800' } } } },
    },
  },
  tooltip: {
    styles: {
      borderRadius: 1,
      bgColor: INK,
      color: 'white',
      shadow: 'none',
      theme: { dark: { bgColor: PAPER, color: INK } },
    },
  },
  toaster: {
    children: {
      toast: { styles: { ...LAYER, py: 3.5, ps: 4, borderRadius: 2 } },
      action: { styles: { borderRadius: 1 } },
      close: { styles: { borderRadius: 1 } },
    },
  },

  tabs: {
    children: {
      list: { styles: { gap: 1, borderColor: 'base-200', theme: { dark: { borderColor: 'base-800' } } } },
      tab: {
        styles: {
          px: 1,
          mx: 1.5,
          fontWeight: 400,
          color: 'base-500',
          hover: { color: INK },
          selected: { color: INK, fontWeight: 500 },
          theme: { dark: { color: 'base-400', hover: { color: PAPER }, selected: { color: PAPER } } },
        },
        variants: {
          underline: { selected: { borderColor: INK }, theme: { dark: { selected: { borderColor: PAPER } } } },
        },
      },
      indicator: { styles: { borderRadius: 0, bgGradient: 'none', bgColor: INK, theme: { dark: { bgColor: PAPER } } } },
    },
  },
  accordion: {
    children: {
      item: { styles: { borderColor: 'base-200', theme: { dark: { borderColor: 'base-800' } } } },
      trigger: {
        styles: {
          py: 4,
          fontSize: 15,
          fontWeight: 500,
          hover: { color: 'base-600' },
          ariaAttr: { expanded: { color: INK } },
          theme: { dark: { hover: { color: 'base-300' }, ariaAttr: { expanded: { color: PAPER } } } },
        },
      },
    },
  },

  datagrid: {
    styles: {
      borderRadius: 2,
      borderColor: 'base-200',
      shadow: 'none',
      theme: { dark: { bgColor: 'base-950', borderColor: 'base-800', shadow: 'none' } },
    },
    children: {
      // The header is the body's own surface, set apart by one line of ink under it — which is also the sticky
      // edge, so it needs no shadow. Opaque, since the rows scroll beneath it.
      header: {
        styles: {
          bb: 1,
          bgColor: 'white',
          borderColor: INK,
          theme: { dark: { bgColor: 'base-950', borderColor: 'white/60' } },
        },
        children: {
          hover: { styles: { before: { bgColor: 'base-900/4' }, theme: { dark: { before: { bgColor: 'white/6' } } } } },
          cell: {
            styles: {
              fontSize: 12,
              fontWeight: 500,
              letterSpacing: 'normal',
              color: 'base-600',
              borderColor: 'base-200',
              ariaAttr: {
                'sort=ascending': { color: INK, fontWeight: 600 },
                'sort=descending': { color: INK, fontWeight: 600 },
              },
              theme: {
                dark: {
                  color: 'base-400',
                  borderColor: 'base-800',
                  ariaAttr: { 'sort=ascending': { color: PAPER }, 'sort=descending': { color: PAPER } },
                },
              },
            },
            variants: {
              isGroup: { color: INK, fontWeight: 600, theme: { dark: { color: PAPER } } },
              isPinned: { bgColor: 'white', theme: { dark: { bgColor: 'base-950' } } },
              isSortable: { hover: { color: INK }, theme: { dark: { hover: { color: PAPER } } } },
              hasHoverPill: { before: { bgColor: 'base-900/4' }, theme: { dark: { before: { bgColor: 'white/6' } } } },
            },
          },
        },
      },
      // The ink line under the header is the one rule, so the filter row draws none of its own.
      filter: {
        children: {
          row: { styles: { bb: 0, bgColor: 'white', theme: { dark: { bgColor: 'base-950' } } } },
          cell: {
            variants: { isPinned: { bgColor: 'white', theme: { dark: { bgColor: 'base-950' } } } },
            children: {
              input: {
                styles: {
                  borderRadius: 1.5,
                  borderColor: 'base-300',
                  focus: RING_TIGHT,
                  theme: { dark: { borderColor: 'base-700', focus: { outlineColor: PAPER, borderColor: PAPER } } },
                },
              },
            },
          },
        },
      },
    },
  },
  widget: {
    styles: { borderRadius: 2, shadow: 'none', theme: { dark: { borderColor: 'base-800' } } },
    children: { title: { styles: { ...heading, fontSize: 15, lineHeight: 22 } } },
  },
  toolCall: { styles: { borderRadius: 2, theme: { dark: { borderColor: 'base-800' } } } },
  approval: {
    styles: {
      borderRadius: 2,
      before: { bgGradient: 'none', bgColor: INK, theme: { dark: { bgColor: PAPER } } },
      theme: { dark: { borderColor: 'base-800' } },
    },
  },
  markdown: {
    children: {
      heading: { styles: heading },
      code: {
        styles: {
          borderRadius: 1,
          bgColor: 'base-100',
          color: INK,
          theme: { dark: { bgColor: 'base-800', color: PAPER } },
        },
      },
      codeBlock: { styles: { borderRadius: 2 } },
      quote: { styles: { borderColor: INK, theme: { dark: { borderColor: PAPER } } } },
    },
  },
  list: {
    variants: { card: { borderRadius: 2, theme: { dark: { bgColor: 'base-900' } } } },
  },
  calendar: {
    children: {
      day: {
        variants: { selected: { bgColor: INK, color: 'white', theme: { dark: { bgColor: PAPER, color: INK } } } },
      },
    },
  },
});

/** The page itself: set on <html>, so every component, popover and dialog inherits it. */
const page: BoxStyleProps = {
  vars: Box.palette({ accent: ACCENT, base: BASE }),
  css: { fontFamily: FONT_BODY },
  selection: { bgColor: 'base-950/14' },
  theme: {
    light: { colorScheme: 'light', bgColor: 'white', color: 'base-950' },
    dark: { colorScheme: 'dark', bgColor: 'base-950', color: 'base-100', selection: { bgColor: 'base-50/22' } },
  },
};

/** Light or dark follows the system until the reader picks one, and the pick is kept under `storageKey`. */
export default function Design({ children }: { children: ReactNode }) {
  return (
    <Box.Theme use="global" storageKey="theme" globalStyles={page}>
      {children}
    </Box.Theme>
  );
}
```

**src/App.tsx**

```jsx
import Box from '@box-kite/react';
import Button from '@box-kite/react/components/button';
import Checkbox from '@box-kite/react/components/checkbox';
import DataGrid from '@box-kite/react/components/dataGrid';
import Dialog from '@box-kite/react/components/dialog';
import Dropdown from '@box-kite/react/components/dropdown';
import Flex from '@box-kite/react/components/flex';
import Grid from '@box-kite/react/components/grid';
import RadioGroup from '@box-kite/react/components/radioGroup';
import { H1, H2, P } from '@box-kite/react/components/semantics';
import Slider from '@box-kite/react/components/slider';
import Switch from '@box-kite/react/components/switch';
import Tabs from '@box-kite/react/components/tabs';
import Textbox from '@box-kite/react/components/textbox';

const people = [
  { name: 'Ada Lovelace', role: 'Analyst', city: 'London', age: 36 },
  { name: 'Grace Hopper', role: 'Admiral', city: 'New York', age: 85 },
  { name: 'Alan Turing', role: 'Mathematician', city: 'Wilmslow', age: 41 },
  {
    name: 'Katherine Johnson',
    role: 'Mathematician',
    city: 'Hampton',
    age: 101,
  },
];

/** A page of the components a design reaches, to see the one you pasted in light and dark. */
export default function App() {
  const [theme, setTheme] = Box.useTheme();

  return (
    <Box maxWidth={240} mx="auto" px={6} py={12}>
      <Flex ai="center" jc="space-between" gap={4} mb={2}>
        <H1 fontSize={40} lineHeight={48}>
          Hello, Box Kite
        </H1>
        <Button variant="secondary" onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
          {theme === 'dark' ? 'Light' : 'Dark'} mode
        </Button>
      </Flex>
      <P mb={10} color="base-500">
        Every component below is drawn by the design in src/design.tsx.
      </P>

      <Grid gridTemplateColumns={1} md={{ gridTemplateColumns: 2 }} gap={10}>
        <Flex d="column" gap={4}>
          <H2 fontSize={20}>Actions</H2>
          <Flex gap={3} flexWrap="wrap">
            <Button>Save changes</Button>
            <Button variant="secondary">Cancel</Button>
            <Button variant="ghost">Skip</Button>
          </Flex>
          <Flex>
            <Dialog trigger={(trigger) => <Button {...trigger}>Open a dialog</Button>}>
              <Dialog.Title>Rename this view</Dialog.Title>
              <Dialog.Description>The name is only shown to you.</Dialog.Description>
              <Flex d="column" gap={4} mt={5}>
                <Textbox placeholder="Name" props={{ 'aria-label': 'Name' }} />
                <Flex jc="end">
                  <Button>Save</Button>
                </Flex>
              </Flex>
            </Dialog>
          </Flex>
        </Flex>

        <Flex d="column" gap={4}>
          <H2 fontSize={20}>Fields</H2>
          <Textbox placeholder="Your email" props={{ 'aria-label': 'Email' }} />
          <Dropdown label="Plan" defaultValue="pro" width="fit" labelProps={{ width: 'fit' }}>
            <Dropdown.Item value="free">Free</Dropdown.Item>
            <Dropdown.Item value="pro">Pro</Dropdown.Item>
            <Dropdown.Item value="team">Team</Dropdown.Item>
          </Dropdown>
        </Flex>

        <Flex d="column" gap={4}>
          <H2 fontSize={20}>Choices</H2>
          <Checkbox label="Accept the terms" defaultChecked />
          <Checkbox label="Select all rows" indeterminate />
          <Switch label="Email notifications" defaultChecked />
          <RadioGroup label="Billing" orientation="horizontal" defaultValue="monthly">
            <RadioGroup.Item value="monthly" label="Monthly" />
            <RadioGroup.Item value="yearly" label="Yearly" />
          </RadioGroup>
          <Slider label="Volume" defaultValue={40} />
        </Flex>

        <Flex d="column" gap={4}>
          <H2 fontSize={20}>Tabs</H2>
          <Tabs defaultValue="overview">
            <Tabs.List label="Project">
              <Tabs.Tab value="overview">Overview</Tabs.Tab>
              <Tabs.Tab value="activity">Activity</Tabs.Tab>
              <Tabs.Tab value="settings">Settings</Tabs.Tab>
            </Tabs.List>
            <Tabs.Panel value="overview">Who is on it, and what is left.</Tabs.Panel>
            <Tabs.Panel value="activity">What changed this week.</Tabs.Panel>
            <Tabs.Panel value="settings">Names, members and access.</Tabs.Panel>
          </Tabs>
        </Flex>
      </Grid>

      <H2 fontSize={20} mt={12} mb={4}>
        Data
      </H2>
      <DataGrid
        data={people}
        def={{
          rowKey: 'name',
          visibleRowsCount: 'all',
          rowSelection: true,
          columns: [
            {
              key: 'person',
              header: 'Person',
              columns: [
                { key: 'name', header: 'Name' },
                { key: 'role', header: 'Role' },
              ],
            },
            { key: 'city', header: 'City' },
            { key: 'age', header: 'Age', align: 'end' },
          ],
        }}
      />
    </Box>
  );
}
```

## Options

With npm the options go after `--`; pnpm and yarn take them as they are.

| Option | What it does |
| --- | --- |
| `-d, --design NAME` | classic, ember, azure or mono. Asks when it is not given. |
| `-t, --template NAME` | react-ts (the default), react-compiler-ts, react or react-compiler. The last two are JavaScript. |
| `--no-install` | Writes the files and skips the install. |
| `--overwrite` | Empties the directory first when it already has files in it. |
| `--eslint` | Uses ESLint instead of Oxlint. Passed to create-vite. |
| `--no-interactive` | Never asks; anything not given takes its default. It asks by default when run in a terminal. |

## By hand

The same result in a Vite app you already have, or one you would rather set up yourself. It uses the design and language picked above.

1

Install Box Kite and the design's fonts

```shell
npm install @box-kite/react @fontsource-variable/roboto
```

2

Save the design as src/design.tsx

**src/design.tsx**

```jsx
/**
 * Mono: ink #16191e on white in single pixels, with 1px edges, focus as the second pixel and no gradients.
 *
 * The whole design is in this file: the two palette families every component is drawn in (accent and
 * base), the page's colours and fonts, and the component styles, in light and dark. It is yours to edit.
 */
import '@fontsource-variable/roboto';
import Box, { type BoxComponentStyles, type BoxStyleProps } from '@box-kite/react';
import type { ReactNode } from 'react';

const FONT_BODY = "'Roboto Variable', 'Helvetica Neue', Arial, sans-serif";

/** The neutrals: page, text, borders. Repoint either family with a palette name ('stone') or a ramp like this. */
const BASE = {
  50: '#f7f8f9',
  100: '#eff1f3',
  200: '#e2e5e9',
  300: '#cdd1d7',
  400: '#9ba2ad',
  500: '#6b7380',
  600: '#4f5763',
  700: '#3b424d',
  800: '#2c323b',
  900: '#1d2127',
  950: '#16191e',
} as const;

/** The accent: buttons, focus, selection. */
const ACCENT = {
  50: '#f7f8f9',
  100: '#eff1f3',
  200: '#e2e5e9',
  300: '#cdd1d7',
  400: '#8c939e',
  500: '#2c323b',
  600: '#16191e',
  700: '#101216',
  800: '#0b0d10',
  900: '#08090b',
  950: '#050607',
} as const;

const INK = 'base-950';
const PAPER = 'base-50';

/** A filled control's focus: one pixel of page, then one of ink — the second pixel, kept apart so it shows on a fill. */
const RING_APART: BoxComponentStyles = {
  outline: 1,
  outlineStyle: 'solid',
  outlineOffset: 1,
  outlineColor: INK,
  theme: { dark: { outlineColor: PAPER } },
};

/** An outlined control's focus: the border turns ink and a second pixel sits right on it, so the line is 2px. */
const RING_TIGHT: BoxComponentStyles = {
  outline: 1,
  outlineStyle: 'solid',
  outlineOffset: 0,
  outlineColor: INK,
  borderColor: INK,
  theme: { dark: { outlineColor: PAPER, borderColor: PAPER } },
};

/** What floats: white, a hairline, and the one shadow the design allows — short and soft, so it lifts without glowing. */
const LAYER: BoxComponentStyles = {
  p: 1,
  b: 1,
  borderRadius: 1.5,
  bgColor: 'white',
  borderColor: 'base-200',
  shadow: 'lg',
  shadowColor: 'black/8',
  theme: { dark: { bgColor: 'base-900', borderColor: 'base-800', shadowColor: 'black/40' } },
};

/** One row in a layer: a grey fill under the pointer, the choice marked by weight rather than by colour. */
const LAYER_ITEM: BoxComponentStyles = {
  borderRadius: 1,
  hover: { bgColor: 'base-100' },
  focus: { bgColor: 'base-100' },
  selected: { bgColor: 'base-100', color: INK, fontWeight: 500, hover: { bgColor: 'base-200' } },
  theme: {
    dark: {
      hover: { bgColor: 'base-800' },
      focus: { bgColor: 'base-800' },
      selected: { bgColor: 'base-800', color: PAPER, hover: { bgColor: 'base-700' } },
    },
  },
};

/** Where the keyboard is in a listbox: the pointer's fill, with the outline back in a forced-colors mode. */
const LAYER_HIGHLIGHT: BoxComponentStyles = {
  outline: 0,
  bgColor: 'base-100',
  selected: { bgColor: 'base-200' },
  forcedColors: { outline: 2, outlineColor: 'Highlight' },
  theme: { dark: { bgColor: 'base-800', selected: { bgColor: 'base-700' } } },
};

/** A field at rest: white, a grey hairline that darkens under the pointer and turns to two pixels of ink on focus. */
const FIELD: BoxComponentStyles = {
  borderRadius: 1.5,
  borderColor: 'base-300',
  bgColor: 'white',
  shadow: 'none',
  hover: { borderColor: 'base-500' },
  focus: RING_TIGHT,
  theme: {
    dark: {
      bgColor: 'base-950',
      borderColor: 'base-700',
      hover: { borderColor: 'base-500' },
      focus: { outlineColor: PAPER, borderColor: PAPER },
    },
  },
};

/** The primary action: solid ink with a 1px edge of its own, so every button is the same height. Hover lifts it to #2c323b. */
const PRIMARY: BoxComponentStyles = {
  b: 1,
  bgGradient: 'none',
  bgColor: INK,
  borderColor: INK,
  color: 'white',
  shadow: 'none',
  hover: { bgColor: 'base-800', borderColor: 'base-800' },
  active: { bgColor: 'base-700', borderColor: 'base-700' },
  disabled: { bgColor: 'base-100', borderColor: 'base-200', color: 'base-400' },
  theme: {
    dark: {
      bgColor: PAPER,
      borderColor: PAPER,
      color: INK,
      hover: { bgColor: 'base-200', borderColor: 'base-200' },
      active: { bgColor: 'base-300', borderColor: 'base-300' },
      disabled: { bgColor: 'base-900', borderColor: 'base-800', color: 'base-600' },
    },
  },
};

/** The library's marks are white; on the dark page Mono fills a control with paper, so its mark is drawn in ink. */
const inkMark = (shape: string) =>
  `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns='http://www.w3.org/2000/svg' width='100%' viewBox='0 0 20 20'>${shape}</svg>`)}")`;
const INK_TICK = inkMark(
  "<path fill='none' stroke='#16191E' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M6 10l3 3l6-6'/>",
);
const INK_DASH = inkMark(
  "<line stroke='#16191E' stroke-linecap='round' x1='6' y1='10' x2='14' y2='10' stroke-width='2' />",
);
const INK_DOT = inkMark("<circle fill='#16191E' cx='10' cy='10' r='5'/>");

const heading: BoxComponentStyles = { fontWeight: 500, letterSpacing: 'tight', css: { fontFamily: FONT_BODY } };

Box.components({
  h1: { styles: heading },
  h2: { styles: heading },
  h3: { styles: heading },

  button: {
    styles: { ...PRIMARY, px: 4, borderRadius: 1.5, fontWeight: 500, focusVisible: RING_APART },
    variants: {
      secondary: {
        bgColor: 'white',
        color: INK,
        borderColor: 'base-300',
        hover: { bgColor: 'base-50', borderColor: INK },
        active: { bgColor: 'base-100', borderColor: INK },
        focusVisible: RING_TIGHT,
        theme: {
          dark: {
            bgColor: 'transparent',
            color: PAPER,
            borderColor: 'base-700',
            hover: { bgColor: 'base-800', borderColor: 'base-500' },
            active: { bgColor: 'base-700', borderColor: 'base-500' },
            focusVisible: { outlineColor: PAPER, borderColor: PAPER },
          },
        },
      },
      ghost: {
        bgColor: 'transparent',
        color: 'base-700',
        borderColor: 'transparent',
        hover: { bgColor: 'base-100', borderColor: 'transparent', color: INK },
        active: { bgColor: 'base-200', borderColor: 'transparent' },
        theme: {
          dark: {
            bgColor: 'transparent',
            color: 'base-300',
            borderColor: 'transparent',
            hover: { bgColor: 'base-800', borderColor: 'transparent', color: PAPER },
            active: { bgColor: 'base-700', borderColor: 'transparent' },
          },
        },
      },
    },
  },

  textbox: { styles: FIELD },
  textarea: { styles: FIELD },
  dropdown: {
    styles: FIELD,
    children: {
      items: { styles: { ...LAYER, gap: 0.5 } },
      item: { styles: LAYER_ITEM, variants: { highlighted: LAYER_HIGHLIGHT } },
      unselect: { styles: { borderRadius: 1 }, variants: { highlighted: LAYER_HIGHLIGHT } },
      selectAll: { styles: { borderRadius: 1 }, variants: { highlighted: LAYER_HIGHLIGHT } },
    },
  },
  combobox: {
    styles: { ...FIELD, hasDisabled: { bgColor: 'base-100' } },
    children: {
      items: { styles: { ...LAYER, gap: 0.5 } },
      item: { styles: LAYER_ITEM, variants: { highlighted: LAYER_HIGHLIGHT } },
      chip: {
        styles: {
          b: 1,
          borderRadius: 1,
          bgColor: 'white',
          borderColor: 'base-300',
          color: INK,
          theme: { dark: { bgColor: 'base-900', borderColor: 'base-700', color: PAPER } },
        },
      },
    },
  },

  checkbox: {
    styles: {
      // One pixel like every other edge, and a quarter more padding so the box stays 20px.
      b: 1,
      p: 2.25,
      borderRadius: 1,
      borderColor: 'base-400',
      hover: { borderColor: INK },
      focusVisible: RING_APART,
      checked: { bgColor: INK, borderColor: INK },
      indeterminate: { bgColor: INK, borderColor: INK },
      theme: {
        dark: {
          borderColor: 'base-500',
          hover: { borderColor: PAPER },
          checked: { bgColor: PAPER, borderColor: PAPER, css: { backgroundImage: INK_TICK } },
          indeterminate: { bgColor: PAPER, borderColor: PAPER, css: { backgroundImage: INK_DASH } },
        },
      },
    },
  },
  radioButton: {
    styles: {
      borderColor: 'base-400',
      focusVisible: RING_APART,
      checked: { bgColor: INK, borderColor: INK },
      hover: { borderColor: INK },
      theme: {
        dark: {
          borderColor: 'base-500',
          hover: { borderColor: PAPER },
          checked: { bgColor: PAPER, borderColor: PAPER, css: { backgroundImage: INK_DOT } },
        },
      },
    },
  },
  switch: {
    styles: {
      b: 1,
      bgColor: 'base-200',
      borderColor: 'base-300',
      insetShadow: 'none',
      before: { shadow: 'xs', shadowColor: 'black/20' },
      focusVisible: RING_APART,
      hover: { bgColor: 'base-300' },
      checked: { bgColor: INK, borderColor: INK, hover: { bgColor: 'base-800', borderColor: 'base-800' } },
      theme: {
        dark: {
          bgColor: 'base-800',
          borderColor: 'base-700',
          hover: { bgColor: 'base-700' },
          checked: {
            bgColor: PAPER,
            borderColor: PAPER,
            before: { bgColor: INK },
            hover: { bgColor: 'base-200', borderColor: 'base-200' },
          },
        },
      },
    },
  },
  slider: {
    children: {
      track: {
        styles: { height: 1, borderRadius: 0.5, bgColor: 'base-200', theme: { dark: { bgColor: 'base-800' } } },
      },
      fill: { styles: { bgGradient: 'none', bgColor: INK, theme: { dark: { bgGradient: 'none', bgColor: PAPER } } } },
      thumb: {
        styles: {
          width: 4,
          height: 4,
          ms: -2,
          b: 1,
          bgColor: 'white',
          borderColor: INK,
          ring: 0,
          shadow: 'sm',
          shadowColor: 'black/12',
          hover: { bgColor: 'base-100' },
          focusVisible: RING_APART,
          theme: { dark: { bgColor: INK, borderColor: PAPER, hover: { bgColor: 'base-800' } } },
        },
      },
    },
  },
  progress: {
    styles: { height: 1, borderRadius: 0.5, bgColor: 'base-200', theme: { dark: { bgColor: 'base-800' } } },
    children: {
      fill: {
        styles: {
          borderRadius: 0.5,
          bgGradient: 'none',
          bgColor: INK,
          theme: { dark: { bgGradient: 'none', bgColor: PAPER } },
        },
      },
    },
  },

  dialog: {
    styles: {
      p: 6,
      b: 1,
      borderRadius: 2,
      borderColor: 'base-200',
      shadow: 'xl',
      shadowColor: 'black/12',
      backdrop: { bgColor: 'base-950/40', backdropBlur: 'none' },
      theme: {
        dark: {
          bgColor: 'base-900',
          borderColor: 'base-800',
          shadowColor: 'black/50',
          backdrop: { bgColor: 'black/60' },
        },
      },
    },
    children: {
      title: { styles: { ...heading, fontSize: 20, lineHeight: 28 } },
      description: { styles: { color: 'base-500', theme: { dark: { color: 'base-400' } } } },
    },
  },
  popover: { styles: { ...LAYER, p: 4, borderRadius: 2 } },
  menu: {
    styles: LAYER,
    children: {
      item: { styles: { ...LAYER_ITEM, px: 2.5 } },
      separator: { styles: { mx: -1, borderColor: 'base-200', theme: { dark: { borderColor: 'base-800' } } } },
    },
  },
  tooltip: {
    styles: {
      borderRadius: 1,
      bgColor: INK,
      color: 'white',
      shadow: 'none',
      theme: { dark: { bgColor: PAPER, color: INK } },
    },
  },
  toaster: {
    children: {
      toast: { styles: { ...LAYER, py: 3.5, ps: 4, borderRadius: 2 } },
      action: { styles: { borderRadius: 1 } },
      close: { styles: { borderRadius: 1 } },
    },
  },

  tabs: {
    children: {
      list: { styles: { gap: 1, borderColor: 'base-200', theme: { dark: { borderColor: 'base-800' } } } },
      tab: {
        styles: {
          px: 1,
          mx: 1.5,
          fontWeight: 400,
          color: 'base-500',
          hover: { color: INK },
          selected: { color: INK, fontWeight: 500 },
          theme: { dark: { color: 'base-400', hover: { color: PAPER }, selected: { color: PAPER } } },
        },
        variants: {
          underline: { selected: { borderColor: INK }, theme: { dark: { selected: { borderColor: PAPER } } } },
        },
      },
      indicator: { styles: { borderRadius: 0, bgGradient: 'none', bgColor: INK, theme: { dark: { bgColor: PAPER } } } },
    },
  },
  accordion: {
    children: {
      item: { styles: { borderColor: 'base-200', theme: { dark: { borderColor: 'base-800' } } } },
      trigger: {
        styles: {
          py: 4,
          fontSize: 15,
          fontWeight: 500,
          hover: { color: 'base-600' },
          ariaAttr: { expanded: { color: INK } },
          theme: { dark: { hover: { color: 'base-300' }, ariaAttr: { expanded: { color: PAPER } } } },
        },
      },
    },
  },

  datagrid: {
    styles: {
      borderRadius: 2,
      borderColor: 'base-200',
      shadow: 'none',
      theme: { dark: { bgColor: 'base-950', borderColor: 'base-800', shadow: 'none' } },
    },
    children: {
      // The header is the body's own surface, set apart by one line of ink under it — which is also the sticky
      // edge, so it needs no shadow. Opaque, since the rows scroll beneath it.
      header: {
        styles: {
          bb: 1,
          bgColor: 'white',
          borderColor: INK,
          theme: { dark: { bgColor: 'base-950', borderColor: 'white/60' } },
        },
        children: {
          hover: { styles: { before: { bgColor: 'base-900/4' }, theme: { dark: { before: { bgColor: 'white/6' } } } } },
          cell: {
            styles: {
              fontSize: 12,
              fontWeight: 500,
              letterSpacing: 'normal',
              color: 'base-600',
              borderColor: 'base-200',
              ariaAttr: {
                'sort=ascending': { color: INK, fontWeight: 600 },
                'sort=descending': { color: INK, fontWeight: 600 },
              },
              theme: {
                dark: {
                  color: 'base-400',
                  borderColor: 'base-800',
                  ariaAttr: { 'sort=ascending': { color: PAPER }, 'sort=descending': { color: PAPER } },
                },
              },
            },
            variants: {
              isGroup: { color: INK, fontWeight: 600, theme: { dark: { color: PAPER } } },
              isPinned: { bgColor: 'white', theme: { dark: { bgColor: 'base-950' } } },
              isSortable: { hover: { color: INK }, theme: { dark: { hover: { color: PAPER } } } },
              hasHoverPill: { before: { bgColor: 'base-900/4' }, theme: { dark: { before: { bgColor: 'white/6' } } } },
            },
          },
        },
      },
      // The ink line under the header is the one rule, so the filter row draws none of its own.
      filter: {
        children: {
          row: { styles: { bb: 0, bgColor: 'white', theme: { dark: { bgColor: 'base-950' } } } },
          cell: {
            variants: { isPinned: { bgColor: 'white', theme: { dark: { bgColor: 'base-950' } } } },
            children: {
              input: {
                styles: {
                  borderRadius: 1.5,
                  borderColor: 'base-300',
                  focus: RING_TIGHT,
                  theme: { dark: { borderColor: 'base-700', focus: { outlineColor: PAPER, borderColor: PAPER } } },
                },
              },
            },
          },
        },
      },
    },
  },
  widget: {
    styles: { borderRadius: 2, shadow: 'none', theme: { dark: { borderColor: 'base-800' } } },
    children: { title: { styles: { ...heading, fontSize: 15, lineHeight: 22 } } },
  },
  toolCall: { styles: { borderRadius: 2, theme: { dark: { borderColor: 'base-800' } } } },
  approval: {
    styles: {
      borderRadius: 2,
      before: { bgGradient: 'none', bgColor: INK, theme: { dark: { bgColor: PAPER } } },
      theme: { dark: { borderColor: 'base-800' } },
    },
  },
  markdown: {
    children: {
      heading: { styles: heading },
      code: {
        styles: {
          borderRadius: 1,
          bgColor: 'base-100',
          color: INK,
          theme: { dark: { bgColor: 'base-800', color: PAPER } },
        },
      },
      codeBlock: { styles: { borderRadius: 2 } },
      quote: { styles: { borderColor: INK, theme: { dark: { borderColor: PAPER } } } },
    },
  },
  list: {
    variants: { card: { borderRadius: 2, theme: { dark: { bgColor: 'base-900' } } } },
  },
  calendar: {
    children: {
      day: {
        variants: { selected: { bgColor: INK, color: 'white', theme: { dark: { bgColor: PAPER, color: INK } } } },
      },
    },
  },
});

/** The page itself: set on <html>, so every component, popover and dialog inherits it. */
const page: BoxStyleProps = {
  vars: Box.palette({ accent: ACCENT, base: BASE }),
  css: { fontFamily: FONT_BODY },
  selection: { bgColor: 'base-950/14' },
  theme: {
    light: { colorScheme: 'light', bgColor: 'white', color: 'base-950' },
    dark: { colorScheme: 'dark', bgColor: 'base-950', color: 'base-100', selection: { bgColor: 'base-50/22' } },
  },
};

/** Light or dark follows the system until the reader picks one, and the pick is kept under `storageKey`. */
export default function Design({ children }: { children: ReactNode }) {
  return (
    <Box.Theme use="global" storageKey="theme" globalStyles={page}>
      {children}
    </Box.Theme>
  );
}
```

3

Wrap the app in it, in src/main.tsx

**src/main.tsx**

```jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import Design from './design';
import './index.css';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <Design>
      <App />
    </Design>
  </StrictMode>,
);
```

4

Let the design paint the page

Replace `src/index.css` with this, and delete `src/App.css` and its import: the template’s styles would fight the design.

**src/index.css**

```css
/* The design paints the page, so all that is left here is the margin the browser adds. */
body {
  margin: 0;
}
```

## Make it yours

- **Change the colours.** `Box.palette()` in the design file points the two families at a palette name (`'emerald'`) or at a ramp of your own, and every component follows.
- **Restyle one component.** Each entry in the design’s `Box.components()` is one component, in light and dark; the [theme setup](https://www.box-kite.dev/theme-setup.md) page explains the shape.
- **Switch designs later.** Replace the design file with another one from this page and install its fonts.
- **Build with an agent.** [Start with AI](https://www.box-kite.dev/start-with-ai.md) has prompts that build a landing page and a product page on top of this.

---

_Every page: https://www.box-kite.dev/llms.txt · every prop, measured: https://www.box-kite.dev/props.md_

