UI Components
Every component in @avihq/ui, rendered live on this page — the same library Avi's dashboard and built-in apps use. In app panels, import components from @avihq/ui/panel (it injects the stylesheet into the sandboxed iframe); everything is styled through Avi's design tokens, so it all follows the host theme automatically. See App React UI for how panels are built.
Surfaces
Where the host mounts an app decides its ergonomics — the desktop sidebar wants density, a phone wants touch targets, fullscreen wants room. The host stamps data-avi-surface on the panel's document, and the library adapts through the mobile:/fullscreen:/sidebar: Tailwind variants plus the useSurface() hook. Width never decides the surface — a 400px sidebar and a phone are the same width but different surfaces. WITHIN a surface, layout steps by container breakpoints: AppShell and SplitView panes are size containers (@container), so app markup uses container-query variants (@md:grid-cols-2, @min-[480px]:flex-row) and chrome rows fold their action at the standard 480px row breakpoint. The toggle below stamps this page the way the panel bridge stamps a real panel — every demo on this page reacts.
import { useSurface } from "@avihq/ui/panel"
AppShell
THE standard app container. Chrome rows — breadcrumbs, tabs, toolbar — are slots, so they render full-bleed in the right order and body padding can never push them off the edges. The body is the panel's single scroller, padded by default (padded={false} for full-bleed tables, scrollable={false} when the body manages its own panes). Modals scope to the shell automatically, and a render crash shows a themed fallback instead of a blank panel.
import { AppShell } from "@avihq/ui/panel"
The deals tab's content — padded and scrollable by default. Chrome rows are slots, so they stay full-bleed no matter what the body does. The Save action opens a Modal that scopes itself to this shell automatically.
SplitView
Master-detail that adapts to the surface: side-by-side panes on fullscreen, a drill-in on sidebar and mobile where the open record covers the list. The covered list stays mounted — visibility, not display — so backing out is instant and lands on the exact scroll position. Select a client below, then flip the surface toggle above to watch the same state re-lay itself out.
import { SplitView } from "@avihq/ui/panel"
Navigation
Compound tabs — controlled or uncontrolled — with panels, roving arrow-key focus, and a scrollable row with edge fades when tabs overflow. The active tab scrolls itself into view. On the mobile surface, tabs grow to touch size (15px type, ≥44px targets) — the narrow desktop sidebar deliberately keeps the dense sizing.
import { Tabs } from "@avihq/ui/panel"
Panels mount only while their tab is active.
Overflowing row (280px) — edge fades signal more tabs; tap one near the edge and it scrolls into view.
Row actions. The tab row takes a structured action and kebab menu, rendered by surface — and the sidebar layout has breakpoints: like Breadcrumbs, the action shows inline while the row is at least the standard row breakpoint (480px, collapseBelow) wide and folds into the kebab below it — container width, never the viewport. On mobile everything lives in the kebab; on fullscreen the action is always inline. Inline row controls are compact (xs) and never touch the row borders. Legacy row content (chips, custom buttons) rides inline only on fullscreen and stacks inside the kebab elsewhere.
Wide row — on the sidebar surface the action sits inline (above the 480px row breakpoint).
Narrow row (420px) — below the breakpoint the sidebar folds the action into the kebab; mobile does this at any width.
Breadcrumbs
A path row with optional right-side actions. Every part except the last navigates on click. Long parts truncate automatically, earlier parts first — the last part is usually the record's title, so it keeps its full text until everything else is at its minimum. Give it a primary action and/or a kebab menu — when both are present and the component is narrower than collapseBelow (480px default), the action folds into the kebab as its first item. On the mobile surface, deep paths collapse to root / … / current with the middle parts in the ellipsis menu.
import { Breadcrumbs } from "@avihq/ui/panel"
Full width — the primary action stays a button beside the kebab.
Narrow (400px) — the action moved into the kebab automatically, and the middle path part absorbs the truncation. Flip the surface toggle to Mobile to see the middle collapse into an ellipsis.
ActionBar
The pinned bottom row of record-level actions — Reply on a thread, Save on an editor, Delete on the left. Structured props render by surface: utility icons left and labeled actions right on sidebar/fullscreen; on mobile the bar grows to touch height and the primary action stretches across the remaining width — a thumb-size target instead of a small button stranded in the corner. Flip the surface toggle. A children escape hatch keeps the chrome for flows the config can't express (armed two-step deletes, busy-state labels). AppShell takes it as an actionBar slot; inside a SplitView detail pane, render it as the pane's last row.
import { ActionBar } from "@avihq/ui/panel"
A thread's bar — danger utility left, primary right. On mobile the Reply button becomes the dominant target.
An editor's bar — secondary + primary pair.
Buttons
Variants map to the token color groups; icon sizes are square.
import { Button } from "@avihq/ui/panel"
Text fields
Inputs render bare when label-less (safe in flex rows) and grow label, error, and helper slots when you need them.
import { Input, Textarea, TagInput } from "@avihq/ui/panel"
Lowercase, kebab-case.
That doesn't look like an email.
Enter or comma commits; Backspace on empty removes the last tag.
Selection controls
Select supports groups, descriptions, disabled options, rich rows, and a borderless ghost trigger; the listbox portals above any container.
import { Select, Checkbox, Switch, RadioGroup, Slider, ToggleGroup } from "@avihq/ui/panel"
Dates
String-in, string-out (YYYY-MM-DD) — no Date objects to juggle.
import { DatePicker, DateRangePicker } from "@avihq/ui/panel"
Feedback
Alerts in three visual variants across four semantic colors; badges for statuses; menus and modals for actions; tooltips name icon-only controls, portaled so ancestor overflow can't clip them.
import { Alert, Badge, Menu, Modal, Tooltip, Spinner } from "@avihq/ui/panel"
Heads up
Data display
Cards for grouping, the compound Table with a sticky-header scroll shadow, typography primitives, and a syntax-highlighted CodeBlock.
import { Card, Table, Heading, Text, Divider, CodeBlock } from "@avihq/ui/panel"
Default card
Primary border.
Secondary
Lighter border.
Ghost
No border at all.
| Deploy | Status | When |
|---|---|---|
| api@4.2.1 | Live | 2m ago |
| web@1.9.0 | Rolling | just now |
Heading 3xl — 36px
Heading 2xl — 30px
Heading xl — 24px
Heading lg — 20px
Heading md — 16px
Heading sm — 14px
Heading xs — 12px
Body text, primary.
Secondary, small.
Tertiary, extra small.
import { Button } from "@avihq/ui/panel";
export default function Panel() {
return <Button onClick={save}>Save</Button>;
}Markdown
A compact, dependency-free read-only renderer for markdown-shaped app data — GFM tables, task lists, nested lists, code, links. It builds React elements, never HTML strings, so pathological input can't inject markup. For a writing surface, use the Editor below.
import { Markdown } from "@avihq/ui/panel"
Ships tables, task lists, raw HTML, and inline code — see App React UI.
| Area | Status | Notes |
|---|---|---|
| Panels | Shipped | Edge-to-edge borders |
| Charts | Beta | Separate entry |
- Parse GFM tables
- Nest lists by indentation
- Arbitrarily deep
- Invent new syntax
Read-only by design — the Editor below is the writing surface.
Panel chrome
Low-level pieces that AppShell (above) composes for you — PanelHeader, Toolbar, and EmptyState are still used directly inside shells. PanelShell/PanelBody remain for panels that predate AppShell; new panels should start from AppShell instead.
import { PanelHeader, Toolbar, EmptyState } from "@avihq/ui/panel"
Customers
Everyone in this project
PanelBody — the scrollable content region, and the only place padding belongs. Full-bleed rows like tables skip it (or use its flush prop) so row borders reach the edges.
No customers yet
Records your app creates show up here.
Charts
Themed recharts wrappers — a separate entry so panels that don't chart never pay for it (~480 KB minified).
import { LineChart, BarChart, … } from "@avihq/ui/charts"
DataTable
A virtualized TanStack-powered table with sorting, filtering, and pagination for big datasets — separate entry. Inside an already-bordered host (a panel, a card) pass flush: the table drops its own border so borders never double up, and the filter and pagination rows take padding instead.
import { DataTable } from "@avihq/ui/data-table"
Editor
Avi's rich markdown editor — toolbar, GFM, markdown strings in and out. Separate entry.
import { Editor } from "@avihq/ui/editor"