@avihq/ui
v0.2.30
Published
Avi's UI component library and design tokens — one theme for the site, the dashboard, and app panels.
Readme
@avihq/ui
Avi's UI component library and design tokens — one theme for the marketing site, the dashboard, and app panels.
Everything is styled through the design tokens in src/styles/tokens.css: semantic color groups (--color-background-*, --color-text-*, --color-border-*, --color-focus-ring-*), typography scales, radius, shadows. Light values live on :root, dark values on .dark — flipping the dark class on <html> re-themes every component.
Components
| Group | Components |
|---|---|
| Form controls | Button, Input, Textarea, Select, Checkbox, Switch, RadioGroup, Slider, TagInput, DatePicker, DateRangePicker, ToggleGroup |
| Feedback | Alert, Badge, Menu, Modal (+ ModalHeader/ModalBody/ModalActions), Spinner |
| Navigation | Tabs (compound: Tabs.Tab, Tabs.Panel; controlled or defaultValue) |
| Data display | Card, Table (+ TableHeader/TableBody/TableRow/TableHead/TableCell), Heading, Text, Divider, CodeBlock (syntax-highlighted) |
| Panel chrome | PanelShell, PanelBody, PanelHeader, Toolbar, EmptyState |
Components are styled with Tailwind utility classes over the tokens and carry no dependencies beyond the React peer — except CodeBlock, which ships real syntax highlighting via react-syntax-highlighter (PrismLight with a fixed grammar set, so only the registered languages land in your bundle).
Panel layout rule — never pad the panel's outer element. PanelHeader, Toolbar, Tabs, and tables draw edge-to-edge borders and carry their own internal padding; horizontal padding on an ancestor cuts those borders short of the panel edges. Use PanelShell as the unpadded full-height root with full-bleed rows directly inside it, and keep padding in PanelBody (its flush prop drops the padding for full-bleed tables/lists):
<PanelShell>
<PanelHeader title="Customers" actions={<Button size="sm">Add</Button>} />
<Toolbar>…</Toolbar>
<PanelBody>…padded content…</PanelBody>
</PanelShell>Tree-shaking: the dist is per-module and the package declares "sideEffects", so bundlers include only the components you import (plus the injected stylesheet on the /panel entry).
Optional modules
Heavier building blocks live behind their own entry points so the core stays lean — importing the root entry never pulls these in.
@avihq/ui/charts — themed Recharts v3 wrappers (LineChart, BarChart, AreaChart, PieChart, ScatterChart, RadarChart, ComposedChart, Tooltip) plus the Recharts primitives re-exported for composition:
import { LineChart, Line, XAxis, YAxis, Tooltip } from "@avihq/ui/charts";
<LineChart data={data} height={300}>
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Line dataKey="revenue" />
</LineChart>Panels get the chart CSS automatically (it's baked into the injected stylesheet). Tailwind surfaces add @import "@avihq/ui/styles/charts.css"; where they use charts.
@avihq/ui/data-table — virtualized DataTable on TanStack Table v8 + TanStack Virtual, with sorting, filtering, pagination, and row expansion (ColumnDef is re-exported for typing). Expansion composes from renderSubRow (return null to leave a row inert), expandIndicator (a leading chevron-node gutter with a dashed thread into the sub-row), isRowExpandable (rows can expand with no sub-row, purely to drive in-row rendering — cells read table.options.meta.isRowExpanded(row.id)), and rowClickable (per-row onRowClick gate). Presentation knobs: hideHeader (headerless, keeps column sizing), rowSeparator ("full" | "inset" | "none"), flush, compact:
import { DataTable } from "@avihq/ui/data-table";
<DataTable columns={[{ accessorKey: "name", header: "Name" }]} data={items} sortable virtualized />@avihq/ui/editor — Avi's rich markdown editor (Milkdown): formatting toolbar, GFM, undo history, plain-text paste (Shift+Mod+V), mod+click link opening. Markdown strings in/out:
import { Editor } from "@avihq/ui/editor";
<Editor value={markdown} onChange={setMarkdown} placeholder="Write…" />Panels get the editor CSS automatically; Tailwind surfaces add @import "@avihq/ui/styles/editor.css"; (and an @source for ui/src/editor so the toolbar's utility classes are generated).
Consuming
App panels (no CSS pipeline):
import { Button, Card } from "@avihq/ui/panel";The /panel entry injects the precompiled stylesheet into the document on import — required inside Avi's sandboxed panel iframes, where CSS cannot be bundled separately.
Tailwind v4 surfaces (site, web):
@import "tailwindcss";
@import "@avihq/ui/styles/tokens.css";
@import "@avihq/ui/styles/theme.css"; /* Tailwind bridge: bg-bg-*, text-txt-*, border-bdr-*, ring-ring-*, heading-* */
@import "@avihq/ui/styles/reset.css";
@import "@avihq/ui/styles/components.css"; /* component CSS that utilities can't express */
@source "../../ui/src/components"; /* generate the utilities the components use */import { Button } from "@avihq/ui";theme.css resets Tailwind's default color palette — every color goes through a token. It also owns the dark custom variant; don't redeclare it.
The marketing site consumes the library like web does (components.css + @source): nav/CTA buttons, badges, and the docs renderers (CodeBlock, Table, Divider) are library components — Button as="a" covers the link CTAs — and the docs gallery (avi.run/docs/ui-components) renders the full library live. Only genuinely marketing-specific art (extrude cards, the animated dashboard mock, sheet/accordion) stays bespoke.
Anything else: import @avihq/ui/styles.css (the same precompiled sheet the panel entry injects).
Optional embedded font: the package ships Plus Jakarta Sans (variable 200–800, latin + latin-ext, ~100 KB, SIL OFL — see fonts/OFL.txt). Import @avihq/ui/styles/fonts.css to load it and switch --font-sans to it; leave it unimported to keep the system font stack. Bundled surfaces need a CSS pipeline that resolves url() assets; panels get the font automatically — the /panel entry's injected stylesheet carries the faces base64-inlined (~100 KB, since connect-src 'none' rules out fetching), and Avi's panel CSP allows font-src data:.
Styling by hand
Use the tokens directly — they are plain color values, never wrap them in hsl():
<div style={{ borderBottom: "1px solid var(--color-border-primary)", color: "var(--color-text-secondary)" }} />Building
npm run build # tsup (ESM + types) → @tailwindcss/cli (dist/styles.css) → emit-panel-styles.mjs (dist/panel-styles.js)The library must build before the site/web/app builds that consume it (npm run build at the repo root orders this correctly).
Publishing
Automatic: bump version in package.json and merge to main — .github/workflows/publish-ui.yml publishes any version that isn't on the registry yet. Non-version changes to the package never trigger a publish.
Auth is npm trusted publishing (OIDC) — no token secret. One-time bootstrap: publish the first release manually (npm publish --access public from this directory), then register the workflow as a Trusted Publisher on npmjs.com (@avihq/ui → Settings → Trusted Publisher → GitHub Actions: avi-hq / avi / publish-ui.yml).
Provenance
The core components, charts, and data-table were ported from Creature's open-mcp sdk-ui (MIT, Serverless Inc.) and re-tokened onto Avi's design system. The editor, Modal, and the compound Table/Tabs extensions are Avi originals.
