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,destructiveraw-colors.stylex- the real colour values the roles point atspace.stylex- spacing, fromxsat4pxtoxxxlat48pxradius.stylex- how round a corner isfont-family.stylex,font-size.stylex,font-weight.stylex,line-height.stylex- the textcontrol-height.stylex- the height of a button or an inputshadow.stylex- the shadowscolors-darkandshadow-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:
sortableto sortfilterto searchpaginationto split rows into pagesselectionfor check boxes and actions on the selected rowsdetailto open a row and show morecolumnVisibilityto hide columnsresizableandreorderablefor the columnscsvExportto download the rows as a filevirtualizationto 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 - 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