Skip to content
zap.ts
Esc
navigateopen⌘Jpreview
On this page

UI

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:

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.

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:

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:

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>:

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.

  • TanStack Start - it puts the theme, the toasts and the styles into the page for you
  • Web - an app that uses these components
  • Testing - how the browser tests of a component run

Last updated on September 22, 2026

Was this page helpful?