---
title: UI
description: The components, the design tokens and the theme that every app of the repository shares.
---

`@zap-ts/ui` holds the parts of the screen that every app shares: buttons,
forms, dialogs, tables, colours and spacing. You import them instead of
writing them again in each app.

It is built on two libraries. `@base-ui/react` gives the behaviour of each
component, such as how a dialog traps the keyboard. StyleX gives the styles.
StyleX is a tool that turns styles written in TypeScript into a real CSS file
at build time. So there is no styling work while the app runs.

The big table also uses `@tanstack/react-table` for sorting and paging, and
`@tanstack/react-virtual` to draw long lists fast.

## Tokens

A token is a named value for one design choice, like a colour or a size. You
use the name, never the value. Change the token once and every component
changes.

The tokens live in `@zap-ts/ui/tokens/*`:

- `colors.stylex` - the colours by role: `bg`, `fg`, `card`, `border`, `input`, `ring`, `primary`, `secondary`, `muted`, `accent`, `destructive`
- `raw-colors.stylex` - the real colour values the roles point at
- `space.stylex` - spacing, from `xs` at `4px` to `xxxl` at `48px`
- `radius.stylex` - how round a corner is
- `font-family.stylex`, `font-size.stylex`, `font-weight.stylex`, `line-height.stylex` - the text
- `control-height.stylex` - the height of a button or an input
- `shadow.stylex` - the shadows
- `colors-dark` and `shadow-dark` - the values the dark theme uses instead

You read a token like any other object:

```ts
import { colors } from "@zap-ts/ui/tokens/colors.stylex";

const BAR_COLOR = colors.primary;
```

## The theme

`@zap-ts/ui/theme-provider` handles light and dark.

`ThemeProvider` keeps the current theme and puts the dark class names on the
`<html>` element. Put it near the top of your React tree.

```tsx
import { ThemeProvider } from "@zap-ts/ui/theme-provider";

const App = () => (
  <ThemeProvider>
    <Routes />
  </ThemeProvider>
);
```

`useTheme` reads and changes the theme. It throws an error if you call it
outside `ThemeProvider`.

`darkClassNames` is the list of class names of the dark theme. You need it to
stop the page flashing white before React starts:

```ts
import { darkClassNames } from "@zap-ts/ui/theme-provider";

document.documentElement.classList.add(...darkClassNames);
```

## Global styles

There are two pieces, and they do different jobs.

`@zap-ts/ui/global-styles` exports `bodyProps`. It is the class name and style
of the `<body>` element: the background colour, the text colour and the font.
Spread it on the body:

```tsx
import { bodyProps } from "@zap-ts/ui/global-styles";

<body {...bodyProps}>{children}</body>;
```

`@zap-ts/ui/styles/app.css` is a plain CSS file. Link it from the head of the
page. It sets `box-sizing`, the look of the scroll bars, the focus ring, the
text selection colour, and the `.prose` class for long text. It also turns
animations off when the reader asks the system for less motion.

## The components

Each component has its own entry point, `@zap-ts/ui/components/<name>`:

```tsx
import { Button } from "@zap-ts/ui/components/button";

<Button variant="destructive" size="sm" onClick={remove}>
  Delete
</Button>;
```

These files exist today:

- Layout: `screen`, `container`, `stack`, `separator`, `scroll-area`
- Text: `text`, `link`
- Forms: `form`, `field`, `fieldset`, `input`, `number-field`, `otp-field`, `checkbox`, `radio-group`, `select`, `switch`, `toggle`, `toggle-group`, `button`
- Panels: `card`, `accordion`, `tabs`, `dialog`, `alert-dialog`, `drawer`, `popover`, `menu`, `tooltip`
- Feedback: `alert`, `progress`, `toast`
- Data: `table`, `data-table`, `avatar`

Some files export one component, like `Button`. Others export a group of
parts under one name. `Table` is an object with `Root`, `Caption`, `Header`,
`Body`, `Row`, `Head`, `Cell`, `Empty` and `Spacer`. `Toast` works the same
way, and also exports `createToastManager` and `useToastManager`.

## The data table

`data-table` is the largest component. It takes `rows`, `columns`, a
`caption` and an `emptyLabel`, and every other feature is one optional prop:

- `sortable` to sort
- `filter` to search
- `pagination` to split rows into pages
- `selection` for check boxes and actions on the selected rows
- `detail` to open a row and show more
- `columnVisibility` to hide columns
- `resizable` and `reorderable` for the columns
- `csvExport` to download the rows as a file
- `virtualization` to draw only the rows you can see

`sortable`, `filter` and `pagination` each work in two ways. By default the
table sorts, filters and pages the rows you gave it. Pass the server form
instead, and the table only asks your code for the next page or the new
order. That matters once a table holds more rows than one page can hold.

Every label is a prop. No English text is hidden inside the component, so an
app can translate all of it.

## Related

- [TanStack Start](/packages/tanstack-start) - it puts the theme, the toasts and the styles into the page for you
- [Web](/apps/web) - an app that uses these components
- [Testing](/guides/testing) - how the browser tests of a component run
