Skip to main content

Base UI kit

@backplane-labs/backplane-ui supplies 47 shadcn/Radix primitive modules, page compositions and platform building blocks. Import its style.css alongside @backplane-labs/backplane-theme/style.css. Register englishResources under the backplane.ui i18next namespace. Both themes use the same semantic CSS tokens and focus behavior.

Styles and tokens​

The visual language is a dense operator console: 13px text, 28–32px controls and rows, 2–6px radii, 1px borders and no nested cards. Surfaces go from back to front as background (page) → chrome (header, navigation) → card (panels) → raised (table headers, hover). Color carries meaning only: success, warning, destructive, info, primary and chart-1…5. Light theme status colors keep WCAG AA contrast on their tinted badges.

style.css is one stylesheet for the whole kit family: its Tailwind build also scans platform-ui, schema-forms, observability-ui, editors and charts. Apps and modules that use Tailwind themselves import the preset @backplane-labs/backplane-ui/theme.css and should also @source the kit sources, so that both stylesheets agree on utility order. Modules without their own Tailwind build compose pages from kit components only; kit components never rely on classes the module would have to generate.

Page compositions​

CompositionUse
PageHeaderPage title, description, meta line and actions
EntityHeader, TabBar, TabItemEntity title with icon, status badge, meta line, actions and a router-agnostic tab bar (active via aria-current)
PanelTitled section with count, actions and footer. The body scrolls itself: fill stretches the panel over the remaining height, maxBodyHeight caps it; such bodies are keyboard scroll regions
StatRow, StatTileMetric tiles with tone
StatusBadge, StatusDot, StatusTextOne status vocabulary: success, warning, danger, info, neutral, accent
KeyValueList, MetaList, MetaItem, Count, SectionLabelMetadata lists, inline meta lines, counters, caps group labels
EntityCard, Figure, MeterEntity cards with figures and proportion bars
EmptyState, ViewToggle, DetailDrawerEmpty/failure blocks, segmented view switch, right-side detail drawer (md, lg, xl, full)
FilterComboFilter picker over known values (static or loaded on open) with search and optional free input
Timestamp, Duration, formatRelative, formatDuration, nanosToDateRelative time with absolute tooltip on one shared ticker; compact durations

Composition APIs​

FamilyPublic APIContract
Layout/textStack, Grid, Text, TextLink, Toolbar, IconButtonSemantic HTML, token colors, keyboard toolbar movement
ActionsConfirmAction, ClipboardButtonAbortable confirmation with optional fields (a note, a reason) between description and buttons, retained failure state, clipboard success/failure
TextSearchInput, SecretInput, AutoSizeInput, AutoSizeTextarea, AutoSaveInputClear/reveal, masked readonly secrets, size limits, blur-save with explicit retry
NumbersNumberInput, stepDecimalControlled decimal strings and exact bigint arithmetic; units, bounds, readonly
SelectionSelectControl, Combobox, TagsInput, CascaderThemed single choice with keyboard/typeahead; static/async and multiple selection; literal string IDs; cancellation and retry
FormsFormControl plus Field componentsReact Hook Form Controller, field labels/descriptions/errors and native RHF rules
TimeDateTimeInput, TimeRangeControl, RefreshControlISO calendar/time controls; relative/absolute ranges, timezone, week start; no overlapping refreshes
FilesFileUploadType/size/count limits, drag/drop, progress, retry and cancellation; caller-owned upload transport
FeedbackStateMessage, Toaster, toastEmpty/error/offline/partial state and token-styled notifications. The console shell mounts the one Toaster; plugins call toast (the kit is shared) and never mount their own
TablesDataTable, DataColumnStable IDs, exact values, sorting, global filter and column filters behind a Filters toggle, visibility/order/pinning/resize, selection, hierarchy, optional paging
TreesResourceTree, TreeNodeUnique IDs, flattened virtual hierarchy, levels/positions, keyboard selection and expansion

Schema-driven forms are a separate package. Editors, visualizations and platform-aware compositions have separate package boundaries.

Use SelectControl for a simple dropdown: supply value, onValueChange and options: { value, label, disabled? }[]. Empty string is a valid choice. The trigger accepts IDs, refs, ARIA attributes, size and className; name includes the actual value in form serialization. Unknown values show the placeholder; validation belongs to the surrounding form. SelectControl and the lower-level Select* primitives share the kit's menu surface, borders, selection and focus styles in both themes. Menus fit the viewport and return focus on dismissal. Configuration, schema forms, editors, time controls, table pinning and platform filters all use this composition. NativeSelect remains available for callers that intentionally need the browser's native picker; its popup geometry is browser-owned.

Large data and lifecycle​

Tables and trees use TanStack Virtual and render only the viewport plus overscan. The catalog exercises 10,000 rows/nodes. IDs must remain stable across data updates; array indexes are not identities. Table selection is retained by ID, including across filters and data refreshes. Consumers decide when removed IDs should be discarded. Trees accept an acyclic hierarchy with unique IDs.

Table arrows/Home/End navigate rows; Space selects; Enter activates. Column resize supports keyboard arrows, and column order/pinning have explicit controls. Tree arrows expand/collapse/move to parent/child, Home/End jump, Space selects, Enter activates and typing searches visible labels.

Async choices abort obsolete searches and ignore late results. Mutating controls do not replay failed requests automatically. Upload callbacks must honor the signal; cancelling suppresses further progress/result changes even if the transport completes late. A retry starts a separate attempt.

TimeRangeControl lazily loads Grafana behind a public adapter; raw values are strings and Grafana types stay internal. DateTimeInput itself represents calendar text: callers must apply a timezone before converting it to an instant. Refresh intervals are milliseconds; zero disables automatic refresh. Only one refresh may run at a time, and unmount aborts it.

Verification​

make web-check builds packages and fixtures, checks TypeScript, runs unit and browser acceptance, builds Storybook and checks it in dark/light themes. Catalog families cover primitives, composed controls, form validation/failure recovery, large tables/trees, uploads, time controls, unavailable/readonly/empty states and schema forms. Tests include keyboard/focus interactions and WCAG AA scans.

Foundations and runtime helpers​

Icon, UserAvatar, UsersIndicator, FeatureBadge, FilterPill and Segment use the same tokens and controls as other families. ErrorBoundary isolates a failed subtree and supports explicit reset or resetKey. useClickOutside accepts multiple refs and respects composed event paths; useDelayedSwitch cleans up its timers. The Foundations stories cover utility recovery and token/ typography inspection in both modes.

Authoring guidance​

Use semantic tokens for every surface, border, focus ring and status. Keep English text in a namespace; package defaults are English and modules install module.<service> resources. Use short action verbs, explicit nouns for labels, and actionable errors that preserve the user's input. Do not present internal transport or deployment settings as product tasks.

Label controls, preserve upstream keyboard behavior, and make overlays return focus. Virtual tables support pointer activation and Enter independently of row selection; nested buttons/inputs do not also activate the row. Graphs provide exact-value tables and textual status alongside colors. Automated browser checks use axe in both themes; this is an acceptance baseline, not a claim that every possible author composition is automatically accessible.