@strait/ui is Strait's design system — a custom React component library built on
Tailwind CSS v4, Base UI, and a set of
semantic design tokens. It ships 120+ accessible, composable components spanning forms,
overlays, data display, navigation, and rich application patterns, all themeable through a
single stylesheet.
The repo is a Bun + Turborepo monorepo.
📖 Live docs: the Strait UI documentation site — a Next.js + Fumadocs app
(apps/docs) with live previews, copyable code, and auto-generated props tables,
deployed on Vercel (see Documentation site). Storybook is
kept as the internal reference for visual and interaction testing.
- Custom design language — warm-stone neutrals with an orange accent,
an
oklchcolor system, and a tuned radius/typography scale. Not a stock shadcn theme. - Semantic tokens everywhere — components style themselves with intent tokens
(
bg-success/10,text-destructive-accent,var(--chart-1..5)), so re-theming is a tokens-only change and dark mode comes for free. - Composable by default — every component forwards
className, exposesdata-slotsub-parts for compound pieces, takes data via props, and uses Base UI's polymorphicrenderprop where an atom needs to change its element. - Per-component entry points — import only what you use via
@strait/ui/components/<name>; the build emits one tree-shakeable module per component. - Typed icons — icons are Hugeicons (
IconSvgElement), never loose SVG components. - Tested three ways — Vitest unit tests, Storybook interaction tests, and Chromatic visual regression.
| Concern | Choice |
|---|---|
| Styling | Tailwind CSS v4 (@theme tokens, @config), tw-animate-css |
| Primitives | Base UI, React Aria Components, Radix-free |
| Variants | class-variance-authority + tailwind-merge (cn helper) |
| Icons | @hugeicons/react + @hugeicons/core-free-icons |
| Forms | @tanstack/react-form |
| Tables | @tanstack/react-table |
| Charts | recharts |
| Toasts | sonner (themed with next-themes) |
| Dates | date-fns, react-day-picker, @internationalized/date |
| Code/Syntax | shiki |
| Animation | motion |
| Build | bunchee |
| Types | React 19, TypeScript |
bun add @strait/ui
# react + react-dom 19 are required peer dependencies
bun add react react-domImport the global stylesheet at the root of your app. It bundles Tailwind v4, the design tokens, and component keyframes:
// app entry (e.g. app/layout.tsx, main.tsx)
import "@strait/ui/css";The library is built with an Inter + JetBrains Mono font pairing. Install the fonts your design calls for, e.g.:
import "@fontsource-variable/inter";
import "@fontsource-variable/jetbrains-mono";Tokens flip on a .dark class on any ancestor (@custom-variant dark (&:is(.dark *))).
Toggle it however you like — next-themes is the recommended driver:
import { ThemeProvider } from "next-themes";
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>;--brand is the single source of truth for the accent. Set it to any color
format after importing the stylesheet and the rest of the brand system —
--brand-foreground (contrast-flipped text on the fill), --brand-accent
(legible text for soft/outline variants), the first chart color, and the active
sidebar rail — derives from it automatically:
@import "@strait/ui/css";
:root {
--brand: #6366f1; /* hex, rgb(), or oklch() — that's the whole rebrand */
}The same value applies to light and dark mode, and every derived token stays
individually overridable. (--brand is the accent; --primary is the warm-ink
default-button color.) See the Theming story in Storybook for the full
token reference.
If your own app uses Tailwind and you want to consume the same tokens, the package exports its config and PostCSS setup:
// @strait/ui/tailwind.config — the shared Tailwind config
// @strait/ui/postcss — the shared PostCSS configEach component has its own subpath export. Import only what you need:
import { Button } from "@strait/ui/components/button";
import { Card, CardHeader, CardTitle, CardContent } from "@strait/ui/components/card";
import { Badge } from "@strait/ui/components/badge";
export function Example() {
return (
<Card>
<CardHeader>
<CardTitle>Deployments</CardTitle>
</CardHeader>
<CardContent className="flex items-center gap-2">
<Badge variant="success">Live</Badge>
<Button size="sm">Open</Button>
</CardContent>
</Card>
);
}The cn class-merging helper used internally is also exported:
import { cn } from "@strait/ui/utils";These patterns hold across the library, so once you learn one component the rest are predictable:
classNamepassthrough — every component merges your classes last (viacn), so you can always override.data-slotsub-parts — compound components (Card,Dialog,Field, …) expose each internal element as adata-slot="…"div you can target or restyle.- Intents & sizes — interactive components share an intent vocabulary
(
success | warning | info | destructive) and a size scale (xs | sm | default | lg | xl) where it makes sense. - Polymorphism — atoms that may render as a different element accept Base UI's
renderprop (e.g.<Button render={<a href="…" />}>), notasChild.
120+ components, grouped the same way they appear in Storybook. Import any of them from
@strait/ui/components/<name>.
button · button-group · copy-button · kbd · toggle · toggle-group
activity-feed · aspect-ratio · avatar · badge · bar-list · bulk-action-bar ·
card · carousel · chart · charts · code-block · code-block-command ·
config-row · data-table · description-list · execution-trace-bar · filter ·
item · json-viewer · leaderboard · metric-card · qr-code ·
radial-gauge · relative-time-card · status-badge · table · tag-group ·
timeline · tracker · tree
alert · banner · chart-empty-state · empty · feature-lock · notice-banner ·
progress · skeleton · spinner · toast
calendar · checkbox · combobox · field · file-upload · form · input ·
input-group · label · multiselect · native-select · radio-group · rating ·
secret-input · select · select-native · slider · switch · textarea
accordion · collapsible · direction · resizable · scroll-area · separator ·
shell
breadcrumb · command · command-menu · menubar · navigation-menu ·
navigation-rail · pagination · sidebar · stepper · tabs
alert-dialog · context-menu · detail-sheet · dialog · drawer · dropdown-menu ·
hover-card · popover · sheet · tooltip
Higher-level, opinionated compositions built from the primitives above:
calendar-rac · calendar-with-presets · card-checkbox · checkbox-tree · credenza ·
date-input · date-picker · date-picker-with-month-year · date-range-picker ·
date-range-picker-with-presets · datefield-rac · id-cell · input-otp ·
input-password-with-strength-indicator · input-with-addons ·
input-with-inline-button · input-with-inner-tags · input-with-loader ·
input-with-show-hide-password · input-with-start-icon ·
number-input-percentage-with-chevrons · number-input-with-buttons ·
number-input-with-chevrons · password-input · phone-input · preview-card ·
range-calendar-with-presets · select-with-search · select-with-search-and-button
| Export | Purpose |
|---|---|
@strait/ui/css |
Global stylesheet (Tailwind v4 + tokens). |
@strait/ui/globals.css |
Alias of @strait/ui/css. |
@strait/ui/utils |
The cn class-merge helper. |
@strait/ui/tailwind.config |
Shared Tailwind config. |
@strait/ui/postcss |
Shared PostCSS config. |
| Path | Name | Description |
|---|---|---|
packages/ui |
@strait/ui |
The React component library (this README). |
packages/config |
@strait/config |
Shared TypeScript configs. |
apps/storybook |
@strait/storybook |
Storybook 10 host for browsing components. |
bun install # install all workspace dependencies
bun run storybook # start Storybook at http://localhost:6006| Script | What it does |
|---|---|
bun run storybook |
Start Storybook in dev mode. |
bun run build-storybook |
Build the static Storybook site. |
bun run build |
Build every package (@strait/ui via bunchee). |
bun run typecheck |
Type-check every package. |
bun run lint |
Lint with Biome. |
bun run format |
Format with Biome. |
bun run test |
Run unit tests across packages. |
bun run chromatic |
Publish Storybook to Chromatic (visual). |
bun run verify |
Full gate: Biome + conventions + build + typecheck + test + Storybook. |
bun run clean |
Remove build artifacts and node_modules. |
bun run --filter @strait/ui build # bundle to dist/ with bunchee
bun run --filter @strait/ui dev # rebuild on change
bun run --filter @strait/ui typecheck # tsgo --noEmit
bun run --filter @strait/ui test # Vitest (jsdom)The build is driven by the
exportsmap inpackages/ui/package.json:buncheemirrors each./components/<name>entry tosrc/components/<name>.tsx. When you add a component, add its source file and anexportsentry.
Component stories are colocated with their components in
packages/ui/src/components/*.stories.tsx. Storybook globs them automatically. Each story
file sets title: "Category/Name", which is what drives the catalog above.
Three layers, all run in CI:
| Layer | Command | What it covers |
|---|---|---|
| Unit | bun run --filter @strait/ui test |
Components in jsdom via Vitest + Testing Library. |
| Interaction | bun run --filter @strait/storybook test-storybook |
Every story rendered in headless Chromium; play functions asserted. |
| Visual | bun run chromatic |
Visual regression snapshots via Chromatic. |
Visual snapshots run on Chromatic. To enable them:
- Create a project on Chromatic and copy its project token.
- Add it as a GitHub Actions secret named
CHROMATIC_PROJECT_TOKEN(Settings → Secrets and variables → Actions). - Add a repository variable
CHROMATIC_ENABLEDset totrueto switch theChromaticworkflow on.
Run it locally against your project with
CHROMATIC_PROJECT_TOKEN=<token> bun run chromatic.
The public documentation site is a Next.js (App Router) + Fumadocs
app in apps/docs. It renders every component with live previews and a props
table generated from the component source, plus theming, blocks, and an
LLM-friendly reference. Run it locally with:
bun run docs:dev # http://localhost:3000It deploys on Vercel via the repo's Git integration (build
turbo run build --filter=@strait/docs, configured in vercel.json). No GitHub
Actions deploy is involved; Storybook is no longer published publicly but still
builds in CI for Chromatic and interaction tests.
See LICENSE.