Overview
The @otfdashkit/ui component surface — primitives, composites, blocks, and advanced.
@otfdashkit/ui is the web side of the OTF design system. It bundles ~85 components in
four buckets, all built on Radix UI + Tailwind and styled via the shared @otfdashkit/tokens
CSS variables.
Buckets
| Bucket | What's there | When to reach for it |
|---|---|---|
| Primitives | 50 unstyled-but-themed building blocks (Button, Input, Dialog, Tabs, …) | You're composing your own UI |
| Composite | 16 opinionated pieces (Persona, Stat, Banner, DataGrid, …) | You want a finished pattern, not a Lego brick |
| Blocks | ~25 product-shape blocks — sidebars, modals, dashboard layouts | You're standing up a new app screen and want a working starting point |
| Advanced | 7 power-user pieces — Kanban, Gantt, Rich Editor, Command Bar, Filters, Bulk Actions | You're building an internal-tool-grade interface |
Importing
Every export sits at the package root:
import {
Button,
Input,
Dialog, DialogContent, DialogTitle,
Stat,
Persona,
CommandBar,
} from '@otfdashkit/ui'There's no submodule split (e.g. @otfdashkit/ui/blocks) — the bundler tree-shakes unused
exports just fine, and a single import surface is what AI tools handle best when they
write code against your kit.
Theming
Every component reads its colors from the tokens published by @otfdashkit/tokens. To switch
themes at runtime, swap the class on <html>:
document.documentElement.className = 'theme-warm dark'See Tokens / Themes for the four shipped themes (theme-slate,
theme-warm, theme-cosmic, theme-terminal) and how to author your own.
Charts
Chart, BarChart, LineChart, AreaChart, BarList, Sparkline, Heatmap, and
ActivityHeatmap all live in @otfdashkit/ui and consume the same chart tokens. Always
pass theme tokens for colors:
<BarChart
data={data}
dataKey={['issues', 'completed']}
colors={['hsl(var(--chart-1))', 'hsl(var(--chart-3))']}
stacked
/>When passing data to recharts-backed charts, deep-clone first — recharts mutates the array, and Storybook's frozen args throw:
<BarChart data={data.map((d) => ({ ...d }))} ... />Toasts
Toasts ship inside @otfdashkit/ui/styles. To render them, mount <Toaster /> once at
the root of your app and call toast(...) imperatively from anywhere:
import { Toaster, toast } from '@otfdashkit/ui'
export default function RootLayout({ children }) {
return (
<>
{children}
<Toaster />
</>
)
}A11y + linting
@otfdashkit/eslint-plugin-otf-design ships rules that catch:
- Hardcoded hex / RGB outside
lib/theme.ts - Inline
style={{ background: '...' }} - Missing
data-slotattributes on custom components
Add it to your ESLint config and your kit will inherit the same conventions the OTF kits themselves are linted against.
What's NOT in the box
Some pieces are deliberately not shipped:
- Charts library. We re-export recharts wrappers, not a custom charting engine.
- Form library.
Form,FormFieldare thin Radix primitives; bring your own resolver (zod / valibot / yup) via@hookform/resolvers. - Date library.
Calendaris a wrapper aroundreact-day-picker; format dates with whatever you already use. - State management. The blocks accept controlled props; pick TanStack Query / Zustand / Redux yourself.
This keeps the bundle small and the API surface honest.