Design Reference · Phase 2
Admin UI
Standards
Canonical patterns for five recurring UI structures in the Sixhands admin prototype. All examples use live design tokens. Follow these specs exactly — no one-offs.
Inline Alert
Top Nav Bar
PageHead + Actions
Table List
Detail Panel
01
Inline Alert
Component: InlineAlert · File: shared-blocks.jsx · Props: kind, children, action?
All kinds — live rendering
info — Contextual information visible to real users. POS constraints, export limits, read-only scope explanations.
Learn more →
warn — Warnings requiring attention before acting. Billing issues, destructive action confirmations, MFA off.
error — Active error state. Form failures, sync errors, permission blocks.
role="alert" set automatically.success — Persistent confirmation after a completed action. Save confirmed, invite sent, export ready.
note (prototype only) — Annotation for DX about scope, constraints, or deferred features. Amber surface so DX identifies these at a glance. Never in production UI.
When to use each kind
| Kind | Use when | Don't use when |
|---|---|---|
| info | Persistent context the user needs on-page — POS constraint, read-only scope, data lag warning | Content is a prototype note (→ note). Content is a confirmation (→ success). |
| warn | Attention needed before proceeding — billing degraded, destructive confirmation, time-sensitive | Flagging prototype-only scope limits for DX (→ note). |
| error | An error occurred and action is needed — form failed, sync broken, auth rejected | Something might go wrong (→ warn). Toast is better for transient errors. |
| success | Action completed, user needs persistent confirmation on the page | Transient confirmations (→ Toast). Neutral entity status (→ StatusPill). |
| note | Prototype only. Scope constraints, deferred features, DX implementation notes | Never in production UI. If real users see it, use info or warn. |
Specs
| Property | Value |
|---|---|
| Padding | 12px 16px |
| Border radius | var(--r-sm) = 8px |
| Icon | 22×22px circle · filled with fg color · cream text · always present |
| Body font | 500 14px/1.5 Inter |
| Optional action | Right-aligned · same fg color · 600 weight · single CTA only — never two actions in one alert |
| Placement | Below PageHead, above FilterBar / content. Never inside a table row or drawer header. |
| Stacking | Max 2 per page. If warn + info both present, warn goes first. |
✓ Do
- Use
notefor any text starting with "DX:" or describing prototype scope - Place alert directly below
PageHeadas first child in page body - Use
successfor persistent confirmations; Toast for transient ones - Keep body text to 1–2 lines — link out for more detail
✗ Don't
- Don't use
infoorwarnfor prototype notes — DX can't tell the difference - Don't stack more than 2 alerts on a single page
- Don't place an
InlineAlertinside aDetailDrawerheader — put it in the drawer body - Don't use
errorfor "might fail" — that'swarn
03
PageHead + Actions
Component: PageHead · File: shared.jsx · Props: title, back?, actions?
Variants
List page — title + primary action
Franchisees
Detail page — back + title + status left / action right
Edit / form page — back nav + save
← Thai Hots Group
Edit franchisee
Dashboard page — title only
Global dashboard
Rules
| Rule | Detail |
|---|---|
| Back navigation | Always use the back prop — never put a "← Back" or "Back to X →" ghost button inside actions. The back prop renders a small breadcrumb link above the title. |
| Title format | List pages: plain noun ("Franchisees", "Outlets"). Detail pages: EntityName · Type ("Thai Hots Group · Franchisee"). New record: "New [entity]". |
| Actions area | Right-aligned. Buttons only — primary rightmost, secondary left of primary. Never more than 2–3 buttons. StatusPill never goes here — use the status prop instead. |
| Action button order | Left → right: destructive (danger) · secondary · primary. Never flip this order. |
| Status in actions | Use the status prop on detail pages — renders left-aligned below the title. Never in actions. |
Specs
| Property | Value |
|---|---|
| Title font | 700 40px/1.0 Nohemi · color var(--forest) · letter-spacing -.01em |
| Back link | 600 13px/1 Inter · color var(--ink-2) · no underline · arrow prefix "←" |
| Row alignment | align-items: flex-end — back link sits above title, actions align to baseline of title |
| Gap (back → title) | 4px column gap |
| Gap (actions) | 8px between action items |
✓ Do
- Always use
back={{ label: "Franchisees", onClick: … }}prop for back nav on detail pages - Follow the title format: plain noun for lists,
Name · Typefor details - Put StatusPill leftmost in actions on detail/view pages
- Use the same
PageHeadcomponent for every screen — no custom h1 elements
✗ Don't
- Don't put
<Btn variant="ghost">Back to X →</Btn>inactions— use thebackprop - Don't put more than 3 elements in the actions area
- Don't use raw
<h1 className="h-page">— always use thePageHeadcomponent - Don't put a form Save button in PageHead if the form is inside a DetailDrawer
04
Table List
Component: Table · File: shared.jsx · Above: FilterBar · Below: Pagination, Showing
Live rendering — Franchisees table
| Franchisee | Country | Outlets | Status | |
|---|---|---|---|---|
| Thai Hots Group | 🇵🇭 Philippines | 12 | Active | |
| Manila Fresh Co. | 🇵🇭 Philippines | 4 | Pending | |
| Green Bowl PH | 🇵🇭 Philippines | 7 | Active | |
| No results match your filter | ||||
Showing 3 of 23 franchisees
Rules
| Rule | Detail |
|---|---|
| Primary column | Every table has exactly one primary column — the entity name/identifier. Style: 700 14px/1.2 Inter, color var(--forest). Always the leftmost data column. Use render: r => <span style=…> — never global inline styles repeated per-table. |
| Row click | Clicking any row opens the DetailDrawer for that record. The row must have cursor: pointer and a hover background of rgba(0,106,86,.03). Never navigate away on row click — that's what the drawer is for. |
| Row actions | Reveal on hover only (opacity 0 → 1). Max 2 icon buttons per row. Use iconbtn class for the 28×28px icon buttons. Place in rightmost column. |
| Numeric columns | Use Mono component (or font-feature-settings: "tnum") for IDs, counts, amounts. Right-align currency columns only. |
| Empty state | Filtered zero results: inline empty row with muted italic message. No records at all: use EmptyState component below the table header. |
| FilterBar | Always present above a table that can be filtered. Use FilterBar component — never custom filter UI. |
| Pagination | Always pair with Showing (record count) on the left, Pagination buttons on the right. Never one without the other. |
Specs
| Property | Value |
|---|---|
| Header font | 700 11px/1 Inter · uppercase · letter-spacing .08em · color var(--mint) |
| Header border | 1.5px solid var(--line) |
| Row border | 1px solid var(--line) |
| Cell padding | 13px 16px 13px 0 (no left padding on first col) |
| Row hover bg | rgba(0,106,86,.03) |
| Primary col style | 700 14px/1.2 Inter · color var(--forest) |
| Secondary col style | 400 13px/1.4 Inter · color var(--ink-2) |
| Numeric col style | 600 12px/1 monospace · font-feature-settings "tnum" |
✓ Do
- Always define the primary column style in the
columnsarray'srenderfunction — not repeated inline across screens - Use
FilterBarabove every filterable table - Open
DetailDraweron row click — never navigate away - Show both
Showingcount andPaginationtogether
✗ Don't
- Don't repeat
font: "700 14px/1.2 var(--font-ui)", color: "var(--forest)"inline in every table's render — extract to a shared pattern - Don't show row actions at full opacity — they reveal on hover only
- Don't add a "View" or "Details" text link in the row — row click IS the view action
- Don't use a custom sort header — use the
Tablecomponent's built-in sort
05
Detail Panel
Component: DetailDrawer · File: shared-blocks.jsx · Props: open, onClose, title, subtitle?, tabs?, actions?, children
Live rendering — Detail panel open state
Franchisees
| Franchisee | Outlets | Status |
|---|---|---|
| Thai Hots Group | 12 | Active |
| Manila Fresh Co. | 4 | Pending |
Manila Fresh Co.
franch_ph_0042
Status
Pending
Country
🇵🇭 Philippines
Contact
Ana Reyes · ana@manilafresh.ph
Outlets
4 active · 1 pending
Rules
| Rule | Detail |
|---|---|
| Width | 700px canonical. CSS class .sub-drawer sets width: 700px — this wins over the component's width prop default of 560px. Use the component prop only to override to a narrower width for simple read-only panels. |
| Trigger | Opened by row click in a table. Never opened by a button labeled "View" — the row IS the trigger. |
| Sections vs. tabs | Use Tabs when there are 3+ distinct content groups (e.g., Overview · Outlets · Audit). Use plain sections (with FormSection or KeyValueGrid) when content is a single coherent group. |
| Header | Title: entity name. Subtitle: ID or secondary identifier (e.g., franch_ph_0042). Close button top-right — always present. No status pill in the header — put it in the body. |
| Action buttons | Bottom of drawer, right-aligned. Max 2 actions. Primary rightmost. Never a destructive action without a confirm step (ConfirmModal). |
| Scrim | Always shows behind the open drawer via .sub-scrim.open. Clicking the scrim closes the drawer (onClose). ESC key also closes — handled by the component automatically. |
| Inline alert in drawer | Place InlineAlert in the drawer body, below the header — never in the title or subtitle props. |
Specs
| Property | Value |
|---|---|
| Width | 700px (CSS canonical) · override via width prop for narrow panels only |
| Background | var(--cream) |
| Inner padding | 30px all sides (.sub-inner) |
| Gap between sections | 24px column gap |
| Title font | 700 22px/1.2 Nohemi · color var(--forest) |
| Subtitle font | 400 13px/1 Inter · color var(--ink-2) |
| Shadow | -8px 0 40px rgba(0,0,0,.22) |
| Slide animation | transform: translateX(100%) → translateX(0) · 350ms cubic-bezier(.2,.8,.2,1) |
| Focus trap | Built into component — Escape closes, Tab cycles within drawer |
✓ Do
- Use the CSS width (700px) as canonical — don't pass a
widthprop unless narrowing for a simple panel - Use
Tabsfor 3+ content groups;FormSection/KeyValueGridfor single-group content - Place action buttons at the bottom right, primary rightmost
- Show a
ConfirmModalbefore any destructive action
✗ Don't
- Don't open a drawer from a "View" button — row click is the trigger
- Don't put a status pill or
InlineAlertin the drawer header props — both go in the body - Don't place form submit buttons in the
PageHeadactions when the form is inside the drawer - Don't nest drawers — one drawer at a time