Patterns

Web Application

A complete product surface assembled from the package: the Tier 3 application frame, a page header and container, KPI cards, a members table with search, sorting, selection and paging, tabbed content, create and edit forms, and a settings area whose tabs are real routes.

Nothing on this page is a drawing of a component. Every demo is the real export, wired to real state — the sidebar navigates, the table sorts, the filters filter, and the confirmation dialogs actually remove rows.

Application Shell

AppShell is the whole frame: sidebar, header, workspace switcher, product launcher, user menu, mobile menu and the ⌘K palette slot, around a scrolling <main>. You pass data once at the layout; every part reads it back from the shell context.

Overview

Open tasks

248

+12vs last week

Closed this week

96

+18%vs last week

Recent activity

What the team changed today.

BIL-198 · Refund webhook retries
DS-072 · Striped DataTable
ONB-118 · Welcome email
BIL-214 · Proration off by one

Move around the sidebar and the screen changes. Switch workspace at the top of the sidebar, switch product from the launcher in the header — the two move independently. The search field opens the ⌘K palette; CRM is locked, so choosing it calls onUpsell instead of navigating.

App Header

Three zones — identity, a centred search pill that opens the command palette, and the action cluster. The notification panel is deliberately not in the package: only the bell is, because the payload of a notification differs per product.

Page content starts here. The header keeps three zones: identity on the left, the search pill dead centre regardless of how long the left zone gets, and the action cluster on the right.

Open the bell for the app-owned panel, the grid for the product launcher, the avatar for the user menu, or click the search field to raise the command palette.

Stat Cards

StatCard carries the label, the figure, an icon tone, a delta and an optional footer. Colour comes from direction times polarity, so “overdue is up” reads red while “spend is down” reads green. Flip the switch to see the loading skeleton the package ships.

Open tasks

248

+12vs last week

Closed this week

96

+18%vs last week

Overdue

7

+3vs last week

Spend to date

$89.4K

-4.1%vs budget

Renews 01 Oct 2026

Data Table

The list screen every product needs: SearchFilterBar + DataTable + TablePagination, with a column toggle, row selection, a bulk action behind ConfirmDialog and two different empty states. Search, sort, filter, select and remove rows — all of it is live.

Actions
Bình Phan[email protected]OwnerActive14
Dana Whitfield[email protected]AdminActive9
Hà Vũ[email protected]AdminActive11
Grace Okafor[email protected]AdminActive8
Sara Lindqvist[email protected]MemberActive7

Content with Tabs

A page header with a primary action over a tabbed list of resource cards. These panels live in one document, so this is Tabs — the moment a tab becomes its own URL, it is RouteTabs instead.

Projects

Everything the workspace is currently shipping.

Billing rebuild

BIL · 18 open · due 12 Sep 2026

On track
+1
Updated

Onboarding v3

ONB · 26 open · due 05 Sep 2026

At risk
+1
Updated

Design system 1.0

DS · 5 open · due 19 Sep 2026

On track
+1
Updated

Create Dialog

A short create flow in a modal, laid out with FormSection and FormField. Spread the control argument into the input and label, description, error text and required state are wired for you.

The submit button stays disabled until name and email are valid.

Edit Drawer

The same fields in a side sheet — better for editing a record you want to keep in view, and it has room for a long form without the scrolling body a small dialog forces on you.

The drawer keeps its draft between opens — edit the role, save, then reopen it.

Settings Page

Four settings sections behind RouteTabs: real links carrying aria-current="page", drawn from the same class table as Tabs so the two are indistinguishable on screen. The header and the strip belong in the settings layout, not in each page.

Settings

Preferences for Acme Corp.

Workspace

Everyone in Acme Corp sees this.

Current route: /settings

Multi-app Navigation

Comitor navigation has two perpendicular axes: workspace and product. Here the frame runs layout="header-first", so the workspace switcher moves into the header and the launcher sits beside it. A product outside the plan shows a padlock and calls onUpsell rather than landing the reader on a 403.

Workspace
Acme Corp · Business
Product
Tasks
Route
/tasks

The two axes are perpendicular: changing workspace keeps you in the same product, and changing product keeps you in the same workspace. Chat carries its own accent, so the sidebar highlight and the icon tints follow the product you are in.

Open the grid icon in the header for the product launcher, and the workspace name on its left for the workspace list.

Best Practices

Do

  • Pass workspaces, apps, nav and user to AppShell once, in the layout, and let the parts read the context.
  • Override every labels object — the defaults are Vietnamese, accessible names included.
  • Reset to page 1 whenever a filter changes, or the reader lands on an empty page.
  • Write two empty states: “nothing here yet” and “nothing matched” need different next actions.
  • Put irreversible actions behind ConfirmDialog, single-row deletes included.

Don't

  • Do not rebuild DataTable out of <table> — you lose sorting, selection, pinned columns, skeletons and the empty state.
  • Do not use Tabs when each tab is a URL: the roles it announces describe a panel swap that is not happening.
  • Do not omit LinkComponent on Next — every tab and nav item becomes a full page load.
  • Do not render a launcher tile for a product the workspace has not bought.
  • Do not colour a delta by its arrow alone; state the polarity of the metric.

Accessibility

  • The frame supplies the landmarks: <header>, <aside> with a labelled <nav>, and the scrolling <main>.
  • Give every table an aria-label, and every icon-only row action one naming the record it acts on.
  • LetterAvatar already carries the name as screen-reader text — mark it aria-hidden when a visible name sits next to it, or the name is read twice.
  • A Progress bar is a mute picture; name it after the thing it measures.
  • Where a label is both visible text and an accessible name, translate both halves. An English aria-label over Vietnamese visible text fails WCAG 2.5.3, Label in Name.
  • Three components on this page emitted Vietnamese with no prop to reach it through 0.9.1, and every one of the strings is sr-only or an aria-label, which is exactly why they survived a visual review. 1.0.0 closes all three — two of them need a prop from you, the third fixed itself:
    • Combobox — the clear button (Xóa lựa chọn), rendered only once clearable has something to clear. Pass clearLabel, added in 1.0.0 to both Combobox and MultiCombobox — the multi-select one defaults clearable to true, so it carried the string without anything being switched on.
    • CommandPalette — both the dialog title and its description. 1.0.0 adds a labels prop carrying title and description; both are announced every time the palette opens.
    • MobileMenu — the off-canvas sheet’s close button. Through 0.9.1 it fell back to the SheetContent default (Đóng), which MobileMenuProps did not forward, so neither mobileMenuProps nor labels.closeMenu reached it. 1.0.0 wires it to labels.closeMenu, so this one needs no new prop — an app that already fills in ShellLabels is fixed by the upgrade alone. See AppShell.
  • Wherever the page owns the container, the label is reachable — and easy to miss: closeLabel on DialogContent and SheetContent is a standalone prop, not part of any labels bag, so it is the one that gets forgotten.