Shared Tailwind 4 React UI primitives, layout components, and global theme styles for the platform packages workspace.
@moritzbrantner/ui is the low-level design-system package. It owns shared tokens, primitives, composed components, theme metadata, Storybook coverage, and package-consumption guarantees.
Keep product workflows in higher-level app packages. Complete frontend surfaces, pages, navigation composition, roles, auth/session state, admin/account/settings/profile workflows, and other app-specific behavior should compose @moritzbrantner/ui instead of living in it.
Generic visual affordances such as AccountMenu and NotificationMenu live in this package only when they stay state-free and contract-free. Apps own the menu content, routing, auth/session state, notification state, and backend behavior.
bun add @moritzbrantner/uiThe package is published to the public package registry for the @moritzbrantner scope, so consumers can install it with their normal Bun registry configuration.
This repository uses Bun as the package manager and task runner. CI currently installs Bun 1.3.12 and Node 24.
Set up the package from a clean checkout:
bun install --frozen-lockfileFor daily component work, run Storybook:
bun run devFast local tests:
bun run testFormat and static checks:
bun run format:check
bun run lintProduction build:
bun run buildFull local confidence check:
bun run verifybun run verify reports repository hygiene first, then runs the release verification contract. The heavy checks include Storybook tests, coverage, Playwright visual tests, Unlighthouse performance and usability audits, the consumer example build, benchmarks, build-size checks, and a Bun package dry run. Install the local Playwright browser before running visual or Unlighthouse tests for the first time:
bunx playwright install chromiumRun the Storybook-based Unlighthouse audit by itself with:
bun run test:unlighthouseThe audit builds Storybook, serves the static output locally, checks representative component stories, and writes the generated report to unlighthouse-report/.
bun run test:coverage runs the local coverage helper. When Bun's node shim cannot expose V8 coverage APIs, it falls back to the unit suite and reports that coverage was not measured; CI runs the same release contract with the configured runtime.
Run bun run bench by itself, not in parallel with Storybook, Playwright, or other browser-heavy checks. The benchmark verifier retries failed samples once to filter transient host load, but repeat failures should be treated as release signals.
Import exactly one UI stylesheet for the app. Theme tokens are global CSS custom properties, so different UI themes are not intended to coexist on the same page.
import "@moritzbrantner/ui/styles.css";Use Zleek globally when the app should use the glass-styled theme:
import "@moritzbrantner/ui/zleek/styles.css";The Bobba subpath is an alias for the default stylesheet:
import "@moritzbrantner/ui/bobba/styles.css";Additional visual systems are available for specific product surfaces:
import "@moritzbrantner/ui/atlas/styles.css";
import "@moritzbrantner/ui/studio/styles.css";
import "@moritzbrantner/ui/paper/styles.css";Use atlas for dense dashboards and analytics, studio for creative tooling, and paper for document or research-heavy interfaces.
Token metadata now lives in src/token-metadata.ts. CSS is still manually authored in this release; see docs/token-source-of-truth.md for the current source-of-truth status and the deferred generation plan.
Components are organized into support tiers:
stable: primitives and low-level controls with the strongest API expectations.patterns: state-light composed components for common app layouts and workflows.data: data grids, resource lists, filters, search fields, and selection toolbars.shell: app chrome, page shell, navigation, account, and notification surfaces.social: state-light social, chat, feed, action, and profile summary components.media: image cropper and filter editing surfaces.labs: experimental components that are public only through explicit labs paths and can change faster.
Root imports expose only stable and patterns components, plus cn and theme client APIs:
import { Button, Card, CardContent } from "@moritzbrantner/ui";
export function Example() {
return (
<Card>
<CardContent>
<Button>Refresh</Button>
</CardContent>
</Card>
);
}Component subpaths are preferred for bundle-sensitive comprehensive apps:
import { Button } from "@moritzbrantner/ui/components/stable/button";
import { DataGrid } from "@moritzbrantner/ui/components/data/data-grid";
import { Chat } from "@moritzbrantner/ui/components/social/chat";
import { cn } from "@moritzbrantner/ui/server";Focused component tiers are not root-exported. Import them deliberately:
import { PageShell } from "@moritzbrantner/ui/components/shell/app-layout";
import { ImageCropper } from "@moritzbrantner/ui/components/media/image-cropper";
import { Timeline } from "@moritzbrantner/ui/components/labs/timeline";Data, shell, social, and media components are exposed through @moritzbrantner/ui/<tier> and @moritzbrantner/ui/components/<tier>/*; they are not root-exported.
Migration examples:
| Old | New |
|---|---|
@moritzbrantner/ui/components/button |
@moritzbrantner/ui/components/stable/button |
@moritzbrantner/ui/components/data-grid |
@moritzbrantner/ui/components/data/data-grid |
@moritzbrantner/ui/components/chat |
@moritzbrantner/ui/components/social/chat |
@moritzbrantner/ui/components/image-cropper |
@moritzbrantner/ui/components/media/image-cropper |
Use ActionMenu for button-triggered command menus. Use ContextActionMenu for right-click or contextual target actions. Use ActionSheet for touch-first mobile action menus. Use ResponsiveActionMenu when the same trigger should open a desktop dropdown and a mobile slide sheet. Use HoverPreview for read-only hover and focus previews.
Use low-level DropdownMenu, ContextMenu, Menubar, NavigationMenu, Popover, Sheet, or MobileSlide for custom structures beyond the composed APIs, including nested submenus.
Row actions: put ActionMenu on a desktop table row action button, and wrap the row or row affordance with ContextActionMenu for right-click actions. Keep mutations, fetching, permissions, analytics, and route changes in the app through callbacks.
Mobile filter and action sheets: use ActionSheet with checkbox and radio menu items. Keep URL state, persistence, and backend query contracts in the consuming app.
Responsive overflow menus: use ResponsiveActionMenu with the same items array for desktop and mobile. Force mode only in tests and stories; use the default "auto" mode in app code.
Hover previews: use HoverPreview for person, file, and status summaries. Do not hide required actions inside hover-only UI.
Use the root import for compatibility and examples:
import { Button, PageShell, DataGrid } from "@moritzbrantner/ui";Use the explicit client entrypoint as a convenience client barrel. It mirrors the root component policy, so focused tiers and labs remain excluded:
import { Button, CommandPalette, Dialog } from "@moritzbrantner/ui/client";Use component subpaths for bundle-sensitive app surfaces:
import "@moritzbrantner/ui/atlas/styles.css";
import { Button } from "@moritzbrantner/ui/components/stable/button";
import { DataGrid } from "@moritzbrantner/ui/components/data/data-grid";
import { uiTheme } from "@moritzbrantner/ui/atlas/server";
import { cn } from "@moritzbrantner/ui/server";Use the server entrypoint for cn, themeConfig, createUiTheme, and theme metadata in server code:
import { cn, createUiTheme, themeConfig } from "@moritzbrantner/ui/server";Every app should import one stylesheet, usually @moritzbrantner/ui/styles.css. Theme-specific stylesheets such as @moritzbrantner/ui/atlas/styles.css replace that default when a product surface needs a different visual system. UiTheme and themeConfig provide metadata classes and attributes; they do not fetch data or switch global CSS by themselves.
Keep app-specific behavior in consuming packages:
- Auth and session flows.
- Route-aware menus.
- Settings, admin, account, and profile pages.
- Upload execution and transport logic.
- Data fetching, cache policy, and API contracts.
- Product-specific onboarding, empty-state copy, or workflow decisions.
- App shell: combine
Navbar,PageShell,PageHeader,PageContent,Surface,CommandPalette,NotificationMenu,AccountMenu,LanguageSwitcher, andThemeModeSwitch. - Data page: use
SearchField,DataGrid,StateView, and app-owned server state for sorting, filtering, pagination, loading, empty, and error states. - Filtered data page: use
FilterBarfor search and active filter display,DataGridfor rows, andQueryBuilderonly when the app needs nested boolean filters; keep fetching, URL state, and backend query contracts in the app. - Validated form: use
FormSection,Field,FieldError,ValidationSummary, andFormActions; keep validation rules and submit side effects in the app. - Upload queue: use
DropzoneandUploadQueue; keep file storage, retries, cancellation, and transport in the app. - Row actions: use
ActionMenufor click or ellipsis menus andContextActionMenufor right-click menus; keep mutation and fetch logic in the app callbacks. - Mobile filters and actions: use
ActionSheetwith checkbox or radio items; keep URL state and backend query contracts in the app. - Responsive overflow menu: use
ResponsiveActionMenuwith a shareditemsarray, and reserve forcedmodevalues for tests and stories. - Hover preview: use
HoverPreviewfor read-only summaries; keep required commands available through click, focus, context, or touch surfaces. - Command palette: pass app-owned actions to
CommandPalette; keep routing and permissions outside this package. - Workflow editor: use
@moritzbrantner/workflow-editor/reactfor graph editing surfaces; keep execution, persistence, permissions, and run state in the app. - Theme switching: wire
ThemeModeSwitchto the app theme provider and import exactly one UI stylesheet. - Non-happy paths: compose
EmptyState,LoadingState,ErrorState, andOfflineStatewith app-owned messages and retry callbacks.
Public components should accept className, forward standard DOM props, expose stable data-slot hooks, and use variants for intentional design choices. Avoid arbitrary visual props such as color, rounded, shadow, or custom spacing knobs; those decisions should come from design tokens and named variants.
<Button variant="secondary" size="sm">
Save
</Button>ComponentEditorProvider, EditableComponent, ComponentEditorPanel, and buildJsxSnippet support interactive integration galleries. Wrap preview components with metadata, render the panel beside them, and users can click a preview, adjust supported props with inspector controls, then copy JSX.
import {
Button,
ComponentEditorPanel,
ComponentEditorProvider,
EditableComponent,
buildJsxSnippet,
type EditableComponentDefinition,
} from "@moritzbrantner/ui";
const buttonDefinition: EditableComponentDefinition = {
id: "primary-button",
label: "Button",
importName: "Button",
importFrom: "@moritzbrantner/ui",
controls: [
{ id: "variant", label: "Variant", type: "select", value: "default" },
{ id: "label", label: "Label", type: "text", value: "Save" },
],
buildSnippet: (values) =>
buildJsxSnippet({
importName: "Button",
importFrom: "@moritzbrantner/ui",
props: { variant: values.variant },
children: String(values.label),
}),
};
export function IntegrationGallery() {
return (
<ComponentEditorProvider defaultSelectedId="primary-button">
<EditableComponent definition={buttonDefinition}>
{(values) => <Button variant={values.variant as "default"}>{values.label}</Button>}
</EditableComponent>
<ComponentEditorPanel />
</ComponentEditorProvider>
);
}The package also includes reusable state-light patterns for common application surfaces:
StateView,EmptyState,LoadingState,ErrorState, andOfflineStatefor consistent non-happy-path panels.SearchField,SelectionToolbar,FilterBar,ResourceList, andDataGridthrough@moritzbrantner/ui/datafor data-page composition.PageShell,Navbar,AccountMenu, andNotificationMenuthrough@moritzbrantner/ui/shellfor reusable app chrome.FormSection,FieldGrid,FormActions, andValidationSummaryfor repeated form structure.TagInputfor editable string lists with keyboard and remove affordances.QueryBuilderfor nested boolean filter editing while it remains in labs.ViewHeader,DetailsPanel,DisclosurePanel, andConfirmActionfor reusable view, detail, disclosure, and confirmation surfaces.CommandPalette,UploadQueue,ShortcutList,ShortcutHelpDialog, andWorkbenchLayoutfor generic tool surfaces.Chat,SocialActionGroup,SocialPost, andProfileSummarythrough@moritzbrantner/ui/socialfor reusable social surfaces.ImageCropperandImageFilterEditorthrough@moritzbrantner/ui/mediafor reusable media editing surfaces.
These components render UI state and slots only. Keep fetching, routing, upload execution, auth/session state, and product-specific workflows in consuming packages.
UiTheme, BobbaTheme, ZleekTheme, AtlasTheme, StudioTheme, and PaperTheme add theme metadata classes and data-ui-theme attributes around a subtree. They do not scope CSS tokens by themselves; the active visual theme still comes from the single stylesheet imported by the app.
import { UiTheme, type UiThemeName } from "@moritzbrantner/ui";
export function Shell({ theme }: { theme: UiThemeName }) {
return (
<UiTheme theme={theme} className="contents">
<main>Application content</main>
</UiTheme>
);
}Theme metadata is also available from subpaths. The full theme subpaths are client/full-package convenience entrypoints:
import { themeConfig, uiThemeNames } from "@moritzbrantner/ui/themes";
import { uiTheme as zleekTheme } from "@moritzbrantner/ui/zleek";
import { uiTheme as bobbaTheme } from "@moritzbrantner/ui/bobba";
import { uiTheme as atlasTheme } from "@moritzbrantner/ui/atlas";
import { uiTheme as studioTheme } from "@moritzbrantner/ui/studio";
import { uiTheme as paperTheme } from "@moritzbrantner/ui/paper";Use metadata-only theme server subpaths for server code and bundle-sensitive theme metadata:
import { uiTheme as zleekTheme } from "@moritzbrantner/ui/zleek/server";
import { uiTheme as bobbaTheme } from "@moritzbrantner/ui/bobba/server";
import { uiTheme as atlasTheme } from "@moritzbrantner/ui/atlas/server";
import { uiTheme as studioTheme } from "@moritzbrantner/ui/studio/server";
import { uiTheme as paperTheme } from "@moritzbrantner/ui/paper/server";Storybook uses the same theme registry for its design-system toolbar and for the per-style component catalog stories.
Storybook is deployed to GitHub Pages from .github/workflows/pages.yml on every push to main, and can also be deployed manually with the workflow_dispatch trigger.
In the repository settings, set Pages > Build and deployment > Source to GitHub Actions. The workflow builds storybook-static with bun run build-storybook and publishes that directory as the Pages artifact, including the grouped Storybook sidebar.
See CONTRIBUTING.md, docs/design-system.md, and docs/release.md for package boundaries, component requirements, and release checks.
Publishing goes directly to the public package registry. For a local publish, authenticate for the registry, run the release checks, then publish:
bun run publish:registryThe .github/workflows/publish.yml workflow can also publish on v* tags or manual dispatch when the repository has an NPM_TOKEN secret with publish access.
Before tagging, dispatching, or publishing manually, run:
bun run verify:releaseTo inspect the package contents without publishing:
bun run pack:dry