# Privy Application Design Guidelines

Implementation guidance for the shared HTML catalogue. The visual reference is **Official Account Hermod**, baseline `cb0a806`, reviewed on 26 October 2026. This describes the application UI; it does not replace the company's proposed brand guideline.

<!-- REVISION:start -->
Last synchronized: **2026-10-29**. Change: Verify the account-menu Download Skill action and restrict its ZIP to the five maintained skill files
<!-- REVISION:end -->

## Getting started

Read [Getting started](GETTING_STARTED.md), open [the catalogue](index.html), and choose the relevant specimen. The HTML guide is also available at [getting-started.html](getting-started.html). All catalogue interface copy and companion documentation use English. Preserve real names, company names, product names, and code identifiers.

## Sources and precedence

1. Preserve the project's original logo assets without redrawing them.
2. Edit canonical colors and dimensions in `tokens.json`; generate `tokens.css`.
3. Inspect `index.html`, the component stylesheets, and their companion scripts for actual behavior.
4. Update this guideline whenever a reusable visual or interaction rule changes.

Sources include `app/assets/styles/main.css`, `AppIcon.vue`, `PrivyLogo.vue`, `SupportResources.vue`, `SupportConfirmDialog.vue`, `SupportErrorNotice.vue`, `FormattedMessageInput.vue`, and `StatusBadge.vue`. The PNG is the project's wordmark; the SVG is its original symbol. Chat patterns come from the project assets.

The application uses **DM Sans**, while the original brand proposal mentions Google Sans Flex. This kit follows the application. The standalone kit bundles the original DM Sans normal/italic variable fonts locally under the SIL Open Font License. A single shared `@font-face` definition in `design-system.css` supplies the catalogue, guide, embedded HTML preview, and all five example pages. Font weights 400/500/600/700 and italic come from actual font files. The system-font fallback is used only while loading or if a font asset fails. The main product still uses its hosted Google Fonts configuration; this change applies to the standalone kit.

Use primitive → semantic → component aliases. Prefer `--pv-heading`, `--pv-surface`, `--pv-control`, and other semantic roles instead of introducing almost-identical literal colors. The filled danger companion follows the project's design proposal; the application stylesheet primarily exposes semantic error colors.

## Scope and integration boundaries

The catalogue covers common HTML elements and application patterns, not every web-platform tag or every Persona API. Deprecated elements, arbitrary custom elements, image maps, MathML, and plugin embeds require a specific use case. Native date, time, color, file, and media controls have browser-specific internals.

All business interactions are local demonstrations. No account creation, message delivery, session refresh, or signing request is sent. Only theme preference is stored in `localStorage`; files, credentials, drafts, and signatures are not persisted or uploaded. Synthetic fixtures are not production data.

Companion examples such as meters, datalists, camera capture, and PDF placement reuse the same tokens without implying that they are already shipped in the product. Audio contains two generated tones; the video specimen is a control shell with a poster, not a complete video asset. Media and icons have local fallbacks where supplied.

The stylesheets include global resets and generic classes such as `.button` and `.field`. Review collisions before integrating them into an existing application. Reuse the required tokens and patterns in the product's framework; do not import catalogue scripts as a production SDK.

## Typography and layout

- Body, labels, and buttons: 14px/20px. Metadata: 12px/18px. Page heading: 24px/32px. Section: 20px/28px. Subheading: 16px/24px.
- Catalogue hero headings use 28–42px for presentation; this is not a new dashboard heading size.
- Body weight is 400; primary headings and labels use 600. Reserve uppercase and letter spacing for short eyebrows.
- Form controls use 16px text at <=640px for readability and to avoid automatic mobile Safari zoom.
- Application sidebar specimens use a width of 240px, with 8px between items. Hover and active backgrounds must remain separated.
- Use `minmax(0,1fr)` grid tracks. Form groups become one column at <=640px. The catalogue's grouped top navigation collapses at <=1279px; its closed mobile region is hidden and `inert`.
- Use spacing values 4, 8, 12, 16, 20, 24, 32, and 48px. Card padding is 24px on desktop and 20px on mobile.
- Put 20px between a card description and its following button/action row. Do not rely on line height for this separation.

## Colors and themes

Light is the default. Set `data-pv-theme="light|dark"` on `html`; components keep the same semantic variable names in either theme. Use Privy Red for primary actions, burgundy for headings, and beige for secondary surfaces. Distinguish success, information, warning, and error with readable labels, not color alone.

The following snapshot is generated. Edit `tokens.json`, not this table or `tokens.css`.

<!-- TOKENS:start -->
| Token | Light | Dark override |
| --- | --- | --- |
| `--pv-red` | `#e60034` | `Inherited` |
| `--pv-red-hover` | `#c4002c` | `Inherited` |
| `--pv-red-pressed` | `#aa0027` | `Inherited` |
| `--pv-burgundy` | `#430a23` | `Inherited` |
| `--pv-beige` | `#f2e8dd` | `Inherited` |
| `--pv-white` | `#ffffff` | `Inherited` |
| `--pv-warm-50` | `#fcfaf7` | `Inherited` |
| `--pv-warm-100` | `#f5f1ed` | `Inherited` |
| `--pv-warm-200` | `#e8e0d9` | `Inherited` |
| `--pv-warm-300` | `#d4c8c0` | `Inherited` |
| `--pv-warm-500` | `#8b7774` | `Inherited` |
| `--pv-warm-600` | `#705c60` | `Inherited` |
| `--pv-warm-900` | `#271b23` | `Inherited` |
| `--pv-warm-950` | `#1b1419` | `Inherited` |
| `--pv-canvas` | `var(--pv-warm-50)` | `var(--pv-warm-950)` |
| `--pv-surface` | `var(--pv-white)` | `#271d24` |
| `--pv-raised` | `var(--pv-white)` | `#342731` |
| `--pv-muted` | `var(--pv-warm-100)` | `#41303c` |
| `--pv-brand-soft` | `var(--pv-beige)` | `#342731` |
| `--pv-brand-strong` | `var(--pv-burgundy)` | `Inherited` |
| `--pv-text` | `var(--pv-warm-900)` | `#faf5f1` |
| `--pv-heading` | `var(--pv-burgundy)` | `#faf5f1` |
| `--pv-secondary` | `var(--pv-warm-600)` | `#d6c5cc` |
| `--pv-subdued` | `var(--pv-warm-600)` | `#bcabb3` |
| `--pv-link` | `var(--pv-red-hover)` | `#ff8fa9` |
| `--pv-border` | `var(--pv-warm-200)` | `#41303c` |
| `--pv-control` | `#b9aaa3` | `#715d69` |
| `--pv-border-strong` | `var(--pv-warm-600)` | `#bcabb3` |
| `--pv-selection-bg` | `#fff0f3` | `#4a2130` |
| `--pv-selection-fg` | `var(--pv-red-hover)` | `#ff8fa9` |
| `--pv-focus` | `var(--pv-red)` | `#ff8fa9` |
| `--pv-focus-gap` | `var(--pv-white)` | `var(--pv-warm-950)` |
| `--pv-disabled-bg` | `var(--pv-warm-100)` | `#342731` |
| `--pv-disabled-fg` | `var(--pv-warm-600)` | `#bcabb3` |
| `--pv-disabled-border` | `var(--pv-warm-300)` | `#41303c` |
| `--pv-info-bg` | `#eef5f9` | `#183140` |
| `--pv-info-fg` | `#205b78` | `#acd3ea` |
| `--pv-chat-bg` | `#f2f2f4` | `#14161b` |
| `--pv-chat-pattern` | `url('./assets/pattern-doodle-light.svg')` | `url('./assets/pattern-doodle-dark.svg')` |
| `--pv-chat-bubble` | `#fffdfb` | `#342731` |
| `--pv-chat-agent-bubble` | `#f6eee8` | `#4a2130` |
| `--pv-chat-message-fg` | `var(--pv-text)` | `#faf5f1` |
| `--pv-warning-bg` | `#fff3e6` | `#402a18` |
| `--pv-warning-fg` | `#854300` | `#ffc18f` |
| `--pv-error-bg` | `#fff0ed` | `#48201d` |
| `--pv-error-fg` | `#b42318` | `#ffb4a9` |
| `--pv-success-bg` | `#edf8f0` | `#173523` |
| `--pv-success-fg` | `#17643a` | `#96deae` |
| `--pv-secondary-hover` | `var(--pv-warm-200)` | `#41303c` |
| `--pv-secondary-pressed` | `var(--pv-warm-300)` | `#58434b` |
| `--pv-button-bg` | `var(--pv-red)` | `Inherited` |
| `--pv-button-fg` | `var(--pv-white)` | `Inherited` |
| `--pv-card-bg` | `var(--pv-surface)` | `Inherited` |
| `--pv-input-bg` | `var(--pv-surface)` | `Inherited` |
| `--pv-select-chevron` | `url('./assets/chevron-down-light.svg')` | `url('./assets/chevron-down-dark.svg')` |
| `--pv-font-sans` | `'DM Sans', system-ui, -apple-system, 'Segoe UI', sans-serif` | `Inherited` |
| `--pv-motion-fast` | `140ms` | `Inherited` |
| `--pv-motion-standard` | `200ms` | `Inherited` |
| `--pv-motion-overlay` | `240ms` | `Inherited` |
| `--pv-motion-ease` | `cubic-bezier(.2,.8,.2,1)` | `Inherited` |
| `--pv-sidebar-on-brand` | `#f7eaf0` | `Inherited` |
| `--pv-sidebar-brand-hover` | `#65263f` | `Inherited` |
| `--pv-sidebar-brand-border` | `#87576c` | `Inherited` |
| `--pv-sidebar-brand-indicator` | `#ff8fa9` | `Inherited` |
| `--pv-editor-bg` | `var(--pv-warm-900)` | `#1b1419` |
| `--pv-editor-fg` | `#f5ede7` | `#faf5f1` |
| `--pv-editor-border` | `#8b7774` | `#715d69` |
| `--pv-editor-muted` | `#d4c8c0` | `#d6c5cc` |

| Dimension | Value |
| --- | --- |
| `--pv-radius-pill` | `999px` |
| `--pv-radius-card` | `16px` |
| `--pv-radius-dialog` | `18px` |
| `--pv-radius-textarea` | `20px` |
| `--pv-control-height` | `48px` |
| `--pv-button-height` | `44px` |
| `--pv-compact-height` | `36px` |
| `--pv-sidebar-width` | `240px` |
| `--pv-sidebar-collapsed-width` | `72px` |

| Text role | Size / line height / weight |
| --- | --- |
| page | 24px / 32px · 600 |
| section | 20px / 28px · 600 |
| subheading | 16px / 24px · 600 |
| body | 14px / 20px · 400 |
| label | 14px / 20px · 600 |
| metadata | 12px / 18px · 400 |
<!-- TOKENS:end -->

## Buttons and icons

Primary buttons are red, secondary buttons beige, outline buttons use a border and link color, and ghost buttons have no permanent border. The danger-filled example uses the error role. Standard buttons are 44px high with 10px/24px padding and a 999px radius. Compact buttons are 36px with 7px/16px padding; avoid making compact controls the only small target in a touch workflow.

Buttons in one action group share a height. Width follows the label: Create Enterprise Account and Sync Data do not need identical widths. Icon-only buttons are 40×40px with an accessible name; decorative icons are 20px with a 1.7px rounded stroke and `aria-hidden`.

Keep action text during loading, mark the action `aria-busy`, and prevent duplicate submissions. Disabled labels remain readable. Use buttons for actions and links for navigation. Text links within paragraphs or written steps are always underlined so color is not their only distinguishing feature.

## Forms and selection

Single-line inputs, search, single-select, and file controls use a 999px radius and a minimum height of 48px. This standardizes the current Support pattern; some older product controls still use a 12px radius. Textareas use a 20px radius and vertical resize. Content-authoring textareas have a minimum height of 320px without forcing page overflow.

Keep labels visible. Place required asterisks on the same line, mark them decorative, and set native `required`. Use 8px between label and control, 24px between fields, and 24px margin/padding around a form-footer divider. Search uses one surface and one border; an inner input must not create another white pill.

Errors use `aria-invalid`, a message connected through `aria-describedby`, and focus on the first invalid field after submission. Static error specimens do not all need live alerts. Readonly controls remain selectable; disabled controls communicate unavailability. Backend authorization and validation remain mandatory in real integrations.

Single-select fields use a custom searchable combobox with a 16px SVG chevron and 48px text clearance. The popup uses semantic surface, rose selection, branded focus and a selected checkmark instead of the operating system option menu. The original select remains a hidden form-value source and a no-JavaScript fallback.

Checkboxes and radios retain native semantics with a red accent. Related choices use `fieldset`/`legend`. Topics uses a labeled group of checkboxes with rose selected surfaces instead of a native multi-select's blue OS selection. Users can choose multiple topics without Ctrl/Cmd.

Switches use a checkbox with `role=switch`. The track is fixed at 40×24px (`flex:0 0 40px`) and the knob is 18px. The label can wrap. `.specimen > label.switch` must remain flex; a generic label rule must not collapse the track. The input stays keyboard-focusable and the track displays its focus ring.

### Input groups, passwords, and PIN

Input groups place the prefix, field, and action inside one border. Password visibility uses a pressed-state button and never stores the entered value. The strength meter is an illustrative heuristic, not a production password estimator.

PIN uses **six visual digit cells backed by one native input**. This preserves keyboard editing, paste, and OTP autofill. Each cell is 52px high, radius 12px, gap 8px; at <=400px use 48px, 10px, and 6px. Filled cells use beige and the current cell shows the semantic focus outline. Visual cells are `aria-hidden`; the native input owns the label, `inputmode=numeric`, `autocomplete=one-time-code`, and six-digit validation. Paste strips separators/non-digits and accepts up to six digits. Do not replace this with six independent fields that break autofill.

## Navigation, data, and feedback

Active navigation uses soft rose, red text, and a positional indicator. Hover uses a muted background. Breadcrumb navigation has a clear label and `aria-current=page`. Tabs use tablist/tab/tabpanel roles, roving focus, Left/Right and Home/End, and `hidden` for inactive panels.

Use semantic tables with captions and header cells. Only the table container may scroll horizontally on mobile; the page must fit the viewport. Cards use surface backgrounds, subtle borders, and a 16px radius. Reserve shadows for overlays and previews. Avatars keep their proportions and provide initials when an image is unavailable. Removable chips name the specific removal action.

Broadcast accepted, scheduled, and completed badges use the information role as in the project; accepted does not imply delivered. Unknown/blocked uses warning, rejected uses error, and success requires a confirmed result. Dismissible notices provide a named X button. Dismissal does not resolve the underlying failure or authorize resending an uncertain action. Toasts use a live region; information that requires action also remains available in the page.

### Application navbar and menus

The application navbar is separate from the catalogue's search toolbar. Include the original logo, enterprise switcher, workspace search, product switcher, language control, notifications, and account menu. Wrap controls on narrow screens. The English catalogue keeps EN available and marks the Indonesian option unavailable rather than introducing mixed-language copy.

Menus use native `details`, close on an outside click or Escape, and restore focus to their summary. Only one popover is open at a time. On mobile, an open navbar menu expands into normal layout flow so it cannot clip at the screen edges. Global search opens a dialog linking to the five example pages. Escape is handled explicitly so Firefox's search input does not merely clear its value and leave the dialog open.

### Searchable dropdowns

Combobox inputs use `role=combobox`, `aria-controls`, `aria-expanded`, and `aria-activedescendant`. Arrow Up/Down moves the active option, Enter selects, and Escape/Tab closes the list. Focus stays on the input. Multi-select adds removable chips with specific accessible names. Separate loading, empty, error, and disabled states; an empty result is not an error. Preview-state controls let reviewers exercise each state without a backend.

### Member lists

A member row is a content container, not a fixed-height pill button. Use `height:auto`, `min-height:72px`, 12px/14px padding, a 12px radius, and 6px between rows. Avatars are 40px with `flex:0 0 40px`. Name and ID are stacked with a 4px gap and 1.5 line height; long names wrap with `overflow-wrap:anywhere`.

The scroll container is a flex column and each item has `flex:0 0 auto`. Never allow grid/flex sizing to compress rows until text overlaps. Selection uses `aria-pressed`, a rose background, and a checkmark. A full-row keyboard focus ring remains visible.

### Interactive table

The example supports name/ID search, status filtering, name sorting with `aria-sort`, five rows per page, page-level select-all, indeterminate selection, and local bulk archive. Filter changes clear selection; pagination may retain it and the total selected count stays visible. Loading/empty/error states do not display fabricated data. The Persona Table Flex mapping is a responsive semantic-table equivalent, not API compatibility.

## Dialogs, drawers, and wizard

Use native `dialog.showModal()` for modal focus containment. Standard dialogs have a maximum width of 760px, confirmations 480px, and authoring dialogs the viewport minus 32px. Product reader dialogs may use different documented widths. Radius is 18px, padding 24px/20px mobile, with a translucent dark backdrop and internal scrolling.

Name the dialog with `aria-labelledby`; name its close button. Do not outline the entire dialog container, but retain visible focus for controls. Escape cancels, destructive actions provide Cancel, and focus returns to the opener.

The **Logout confirmation** companion at `#logout-confirmation` in Dialogs & disclosure is searchable by logout, log out, sign out and session. Use the shared 480px confirmation shell, a named 36px close control, a description explaining that signing in again is required, secondary Cancel and primary Log out actions. Cancel receives initial focus; the native modal prevents keyboard focus on underlying page controls. Cancel, Escape, close and backdrop dismiss without confirming, and every dismissal returns focus to the opening Log out button. Only explicit Log out confirmation updates the persistent specimen status and polite toast; reopening resets the status so a previous confirmation is not mistaken for a new action. The View HTML snippet includes the modal and trigger; implement behavior in the consuming framework.

This standalone example does not change the catalogue account, clear storage, redirect or invoke authentication services. Existing account-menu signed-out/sign-in previews remain independent. It is an additive companion pattern, not a change to shipped application logout behavior. Reuse semantic surfaces, DM Sans and existing motion/reduced-motion rules in both themes. The four dialog cards use two columns on wide screens and one at <=640px; the modal keeps 16px viewport insets, internal scrolling, 24px/20px padding and wrapping footer actions.

The detail drawer is a right panel up to 480px with 16px insets on desktop. At <=640px it becomes a bottom sheet with 8px insets and maximum height 85dvh. Dirty drawer/wizard forms ask before closing through Escape, backdrop, or Cancel. Keep editing receives initial focus in the discard confirmation. Discard resets edits; successful local save marks the form clean.

The wizard has Account, Preferences, and Review steps. Validate only the visible step, focus invalid fields, preserve values when going Back, and render review values through `textContent`. Local submit closes the demo without a request. Production integrations must disable repeated submission while pending and reconcile uncertain results before retrying.

## Uploads, dates, and scheduling

Upload accepts at most five files, PDF/JPEG/PNG, from 1 byte to 10 MB each. MIME and extension checks are UI assistance, not server validation. Drag/drop has an equivalent labeled file input for keyboard use. Local progress can fail and retry; removing a file stops its timer and revokes its object URL. Filenames are text, not HTML. The visually hidden input is constrained to 1px so global full-width field styling cannot create page overflow.

Date ranges are inclusive calendar dates in Asia/Jakarta (UTC+7). Today and Last 7 days populate the controls; Apply updates the result. End before Start is invalid. Reset restores All dates. The separate inline calendar supports day/week arrow navigation, Home/End, and Enter selection. Time controls always state the relevant timezone.

Calendar day buttons are **square**, with a 1:1 aspect ratio, maximum 36×36px, and `justify-self:center`. Columns may grow; the circular selected/today indicator must not stretch with them. At narrow widths both dimensions shrink together to fit seven columns.

## Skeletons and connection state

Match placeholders to the eventual content: avatar plus two lines for member lists, columns for tables, media plus text for cards/details, and alternating bubbles for chat. `.pv-skeleton` avoids collision with the original grouped skeleton wrapper. Hide decorative placeholders from assistive technology, mark the content region `aria-busy`, and announce loading once.

Shimmer uses a 1.5-second cycle with muted/border tokens. Reduced-motion preference and the preview toggle disable animation. Replace placeholders with real content or an actionable error when loading ends; do not leave a permanent skeleton.

Connection states include connected, offline, reconnecting, session expired, and unconfirmed outcome. Disable sending until the state permits it. Dismissing an unknown-outcome notice only hides the message; it does not unlock Send. Retry reconciles/read-checks the result first rather than automatically repeating a mutation. Preserve drafts on connection loss. Demo reconnection/sign-in does not modify a real session.

### Ticket enterprise identity

In Support ticket lists and conversation headers, show the customer's enterprise name beside the briefcase when the ticket provides a nonblank name. Prefer `requester_enterprise_name`, with `enterprise_name` as a fallback; never substitute the active workspace name. Retain the icon alone when only the enterprise marker is available, and omit the group for personal tickets without either value.

Use 12px/18px regular text with `--pv-secondary` and a 4px icon-to-label gap. In the ticket list, keep the identity on one line and cap the enterprise group at 50% of the available identity width; truncate the enterprise text with an ellipsis while preserving its full DOM text for assistive technology. The selected conversation header shows the full name and wraps long text, including unbroken names, within the available width. Keep the briefcase fixed in size and decorative; the group retains its Enterprise customer title. This metadata adds no tab stop and preserves the ticket button's keyboard focus ring. The Support example includes a named enterprise and a personal ticket in both themes.

### API Integration in the existing Support UI

Keep the existing Integration navigation item, integration cards, editor dialog, and Test actions. Integration card names use DM Sans at 16px/24px and weight 600 in both themes and at all viewport sizes, so they remain proportional to the endpoint and metadata. Do not add a separate Helpdesk/API Integration tab bar. Enterprise authorization is independent of the helpdesk profile; use the existing unavailable/setup context to expose the integration entry when no helpdesk exists. The backend remains authoritative for SysAdmin and optionally enabled helpdesk-admin access.

The Support example includes an API Integration companion using the same form, dialog, status, and button patterns. It makes no network requests and retains no credentials. Production sends requests only after Test, through the backend. GET, HEAD, and OPTIONS force No body; DELETE may use JSON or form fields. An unnamed draft can be tested but requires a name to save. Keep ordinary custom header values visible on edit; authentication secrets stay blank with a masked hint (`••••a1b2`) and are only sent when replaced. Changing authentication type requires its new secret. Form limits follow the contract: 120-character name, 2048-character URL/header value, 20 headers, 50 form fields, and 64 KiB JSON.

Show field validation once in a compact error panel using translated form labels, not API field paths or validator tags. Merge messages for the same field and omit a redundant generic error banner. Use 12px/16px padding, a 12px radius, 14px/20px text, semantic error colors, and 8px between messages. Each message is a full-width text button that focuses its input; underline on hover and retain a visible keyboard focus ring. Connect inputs with `aria-invalid`/`aria-describedby`, focus the first invalid control, and clear field errors when the editor closes. General service errors retain their normal notice. Preserve edits after failure. Show HTTP status and body even when a target returns a non-2xx response; distinguish no-response network failures, binary bodies, and truncated bodies. Masked sent-request and response-header details use native disclosure controls; render text rather than raw HTML. A Retry-After countdown disables both Test actions while leaving Save available. Do not automatically retry Test. Disabled integration status does not disable Test in the current API contract.

Do not add an HTTP hosts panel or host-management controls to the existing integration UI. Do not fetch the HTTP allowlist when opening this page. The backend continues to enforce its allowlist; explain blocked HTTP URLs with HTTPS or administrator guidance. Confirm permanent integration deletion; server audit history is retained. Restore focus after closing the editor; secrets are cleared on close. Use semantic success/error roles in both themes, wrap action groups on narrow screens, and constrain response text and tables inside their containers. At mobile widths, application header actions wrap within the available width instead of overflowing the page.

## Composer, formatting, and help

Place the macro action to the left of the reply placeholder. `/` opens macros only when the composer is empty; do not intercept ordinary slashes in text. Arrow keys move through options, Escape closes, and selecting a macro restores the caret to the composer without sending. Sending requires an explicit action.

The original formatted-message specimen uses the project's inline markers: `**bold**`, `++italic++`, `__underline__`, `~strike~`, and backticks for code. Render previews through safe DOM construction, not raw user HTML. This restricted demo parser is not a replacement for the product's complete parser/sanitizer for nested markup, code blocks, lists, or links.

The separate richtext specimen has bold, italic, list, and clear-formatting actions, text-only paste, and an allowlisted preview DOM. It uses legacy `execCommand` for native browser undo in this demo; production should use a maintained editor and server-side sanitization.

Tooltips appear on hover/focus and dismiss with Escape. Put interactive help in a popover instead. Truncated content has an explicit Show more action for keyboard and touch users.

## Additional Persona patterns

[PERSONA_COVERAGE.md](PERSONA_COVERAGE.md) maps all 80 entries in the [official component navigation](https://privy-open-source.github.io/design-system/components/accordion/) to local equivalents and explicit boundaries. `persona-coverage.json` is the source and synchronization rejects missing destination anchors. This is pattern coverage, not complete Persona Vue feature/API parity.

- Carousel navigation is manual, with labeled previous/next/dot controls and a slide count. Charts include textual values and an equivalent data table. Ring progress exposes its accessible value.
- Camera access begins only after Enable and browser permission. Stop/unload stops media tracks. Denied/unavailable hardware directs users to local file selection; HTTPS or localhost may be required.
- Cropper processes a local JPEG/PNG with zoom and x/y controls, then exports a 320×320 PNG. Rotation, arbitrary ratios, and Persona API compatibility are not implemented. Object URLs are released.
- Signature Draw supports pointer input, Clear, and PNG export. Signature Text supplies a keyboard alternative. These are visual previews, not identity verification or digital signing.
- The QR asset encodes the public Persona documentation URL, with a text-link alternative. It contains no credentials or login token.
- PDF Viewer embeds a synthetic local PDF and provides open/download fallbacks. Object/Text/Helipad use a separate placement surface: click/drag to add, pointer/arrow keys to move, Shift+arrow for larger steps, and a range control for width. Coordinates are bounded percentages. The demo does not modify PDF bytes, implement PDF.js rendering/search, embed fonts, or sign documents.
- Tour is a three-step modal walkthrough with Back/Next/Skip and focus restoration; it does not position a spotlight on page elements. Spread maps to a flex alignment specimen and Steps to wizard state.

## Complete page examples

`examples/enterprise.html`, `knowledge.html`, `teams.html`, and `support.html` reuse shared styles and behaviors. Support focuses the composer after ticket selection; choosing a macro inserts a draft without sending. Knowledge/macro previews use semantic formatting. All page data is synthetic and there are no backend routes.

[STATE_MATRIX.md](STATE_MATRIX.md) is generated from `states.json`. It records requirements and non-applicable states, not a claim that every variant is shipped in the main application. [VALIDATION.md](VALIDATION.md) records actual checks and manual limitations.

## Accessibility and verification

Use one primary heading, consistent heading order, programmatic labels, a skip link, visible focus, and meaningful alternatives. A miniature preview heading may be styled text instead of introducing a heading-level jump. Sticky headers must not obscure focused content.

Check 320, 390, 768, 1024, and 1440px, both themes, keyboard-only navigation, focus restoration, reduced motion, long text, and zoom. Do not convey information only through icons, charts, or color. Media requires appropriate captions/transcripts; iframes need a title and only the permissions they actually need.

Automated checks do not certify complete accessibility. VoiceOver/NVDA, real Safari/iOS, physical camera behavior, and native PDF/plugin internals require separate manual verification. Test WebKit is not equivalent to testing Safari itself.

## Component and element inventory

Generated from `components.json` and actual `index.html` elements. Escaped source snippets are not counted as additional DOM elements.

<!-- CATALOG:start -->
| Section | Usage |
| --- | --- |
| [A familiar foundation.](index.html#overview) | A working reference for interfaces that feel like Privy. Built from the components in Official Account Hermod. |
| [Getting started](index.html#getting-started) | Open the catalogue, reuse a pattern, and keep implementation and guidance synchronized. |
| [Color & geometry](index.html#foundations) | Primitive → semantic → component. Change a role once, and every component follows. |
| [Typography & content](index.html#typography) | DM Sans is the implemented project font. The older brand proposal names Google Sans Flex; this kit follows the running application. |
| [Buttons & iconography](index.html#buttons) | Match heights within an action group. Reserve the red primary action for the main next step. |
| [Inputs & forms](index.html#forms) | Single-line controls use a full pill radius. Textareas keep a readable 20px radius and can grow vertically. |
| [Selection & native controls](index.html#selection) | Use native controls first, with visible labels and a consistent focus treatment. |
| [Navigation](index.html#navigation) | Give selected and hovered items room to breathe. Navigation names should match page titles. |
| [Tables, cards & identity](index.html#data) | Keep the object, owner, status, and next action visible. Use a scroll region for tables on small screens. |
| [Status & feedback](index.html#feedback) | Use words as well as color. Errors explain recovery; unknown outcomes never pretend to be success. Broadcast delivery separates acceptance from job progress and overlapping receipt counts. |
| [Dialogs & disclosure](index.html#overlays) | Dialogs share the same shell, action spacing, and close control. Use full width for content authoring. |
| [Support composer](index.html#composer) | Macros sit to the left of the reply placeholder. Press / in an empty composer to open the macro picker. |
| [Media & embedded content](index.html#media) | Preserve aspect ratios, provide alternatives, and let the user start playback. |
| [HTML element coverage](index.html#html-reference) | Native elements are grouped by purpose. Deprecated tags and unrelated platform APIs are not presented as product components. |
| [Top navbar & menu bar](index.html#top-menu-bar) | Horizontal page navigation with grouped links, active-page styling, keyboard interaction, and a collapsible mobile menu. |
| [Application navbar](index.html#app-navbar) | Workspace and product navigation with search, language, notifications, and account actions. |
| [Searchable dropdowns](index.html#comboboxes) | Filter options with the keyboard and remove selections individually. |
| [Detail drawer](index.html#drawers) | Keep context visible while viewing or editing a customer. Unsaved edits require confirmation. |
| [Interactive data table](index.html#interactive-table) | Search, sort, filter, paginate, select rows, and archive selected demo records. |
| [Compact page-heading summaries](index.html#heading-summaries) | Three to five labeled role counts near a page heading, with responsive wrapping and distinct loading, empty and error values. |
| [Table pagination & remote states](index.html#table-pagination) | Rows-per-page footer, full-result search/sort, simulated remote requests, skeleton/empty/error states and framework integration guidance. |
| [File upload](index.html#uploads) | Validate type and size, preview images, and retry a failed upload. |
| [Date range filter](index.html#date-filters) | Use a preset or an inclusive date range in Asia/Jakarta. |
| [Tooltip & popover](index.html#context-help) | Short guidance on hover or focus; richer information in a dismissible popover. |
| [Multi-step form](index.html#stepper) | Validate each step, review the details, and confirm before discarding edits. |
| [Connection & session](index.html#connection-states) | Keep failures actionable and never resend an unconfirmed mutation automatically. |
| [Member lists](index.html#member-lists) | Rows grow with their content. Avatars, metadata, selection, and focus remain aligned. |
| [Skeleton & shimmer](index.html#skeletons) | Match loading placeholders to the content layout and announce loading once. |
| [Complete page examples](index.html#page-examples) | Inspect the patterns together at desktop and mobile sizes. |
| [Input groups, password & PIN](index.html#advanced-inputs) | Connected controls, fixed prefix/suffix units, password visibility and a single accessible OTP field. |
| [Dashboard input patterns](index.html#dashboard-inputs) | Formatted money, phone country codes, URL feedback, numeric ranges, freeform tags and clearable search. |
| [Search with leading filters](index.html#filtered-search) | Search with a custom dropdown on the left, status and field-scope variants, combined results, clear and reset. |
| [Advanced form patterns](index.html#form-patterns) | Repeatable and conditional fields, dependent selects, selection cards, availability checks, number steppers and reviewed saves with a sticky action footer. |
| [HTML & JSON editor](index.html#code-editors) | Dark code fields, Code/Preview tabs, typed template variables, JSON formatting and an isolated static preview. |
| [Calendar & time picker](index.html#calendar-time) | An inline calendar with keyboard navigation and a time control with an explicit timezone. |
| [Button groups, nested menus & tour](index.html#extra-navigation) | Grouped actions, contextual controls, nested disclosure, and an introductory walkthrough. |
| [Carousel, chart & progress](index.html#carousel-charts) | Manual slide navigation, accessible data charts, circular progress, and expandable text. |
| [Camera & cropper](index.html#camera-cropper) | Capture only after permission, or choose a local image. Preview and export a square crop. |
| [Signature & QR code](index.html#signature-qr) | Draw or type a signature preview, and scan a real QR code linking to the public documentation. |
| [PDF viewer & placement](index.html#pdf-patterns) | A local sample PDF plus a placement surface for text and signature fields. |
| [Rich text editor](index.html#richtext) | Editable formatted content with a keyboard-accessible toolbar and plain-text paste. |
| [Applied filters](index.html#applied-filters) | Combine filters, remove individual criteria, and reset the result set without losing context. |
| [Tree navigation](index.html#tree-navigation) | Navigate nested knowledge categories using arrow keys, Home/End, and type-ahead. |
| [Activity timeline](index.html#activity-timeline) | Show who changed what and when, with a filter and explicit timestamps. |
| [Notification center](index.html#notification-center) | Review read and unread notifications, mark individual items, and recover from loading failures. |
| [Reorderable list](index.html#sortable-list) | Reorder macro steps by dragging or using Move up/Move down. Changes are announced and can be reset. |
| [Customer satisfaction rating](index.html#customer-rating) | Collect a clear one-to-five rating with optional feedback and an explicit submit action. |
| [Resizable split pane](index.html#split-pane) | Resize a desktop detail panel with the pointer or keyboard. On small screens, panels stack vertically. |
| [Page states & recovery](index.html#page-states) | Error pages for missing content, access restrictions and service interruptions, plus empty and loading states. |
| [Dashboard cards & overview](index.html#dashboard-cards) | Reusable KPI, trend, chart, progress, status, activity, team, quick-action, and service-health cards. |
| [Reporting, charts & downloads](index.html#reporting) | Eight chart patterns with linked filters, data tables, CSV/SVG/PNG downloads, print, and local export history. |
| [Advanced data table](index.html#advanced-grid) | Choose and pin columns, expand records, and edit names with validation. |
| [Saved views & advanced filters](index.html#saved-views) | Apply AND/OR criteria, save a named view, and restore its filters. |
| [CSV import wizard](index.html#csv-import) | Map columns, review invalid or duplicate rows, and import only valid records. |
| [Background job center](index.html#background-jobs) | Track queued and running jobs, cancel or retry failures, and download completed exports. |
| [Roles & permission matrix](index.html#permission-matrix) | Preview changes to feature permissions and data scope before applying them. |
| [Approval workflow](index.html#approval-workflow) | Review a request, approve or reject with a reason, and retain the decision history. |
| [Recurring scheduler](index.html#recurring-scheduler) | Preview explicit timezones and upcoming runs, then pause or resume a schedule. |
| [Report drill-down & saved reports](index.html#report-drilldown) | Select a metric and channel to inspect matching rows; save and restore the report definition. |
| [Drafts, publishing & version history](index.html#content-lifecycle) | Autosave a local draft, publish snapshots, compare versions, and restore a version into a draft. |
| [Kanban work queue](index.html#work-queue) | Assign work and move cards between columns with capacity limits and keyboard alternatives. |
| [Integration operations](index.html#integration-operations) | Inspect webhook delivery attempts, explicitly retry failures, and rotate a demo API key. |
| [Profile, preferences & sessions](index.html#workspace-settings) | Save profile and notification preferences, choose a timezone, and revoke other demo sessions. |
| [Conflicts, partial results, undo & stale data](index.html#recovery-patterns) | Preserve user work and distinguish confirmed failures from unconfirmed outcomes. |
| [Recruitment workspace](index.html#recruitment-workspace) | Team-scoped candidate table, KPI definitions, stage transitions, pipeline, profile drawer and separate interview reviews. |
| [Job openings & forms](index.html#recruitment-jobs) | Scoped job templates, opening editor and Draft/Published/Closed visibility. |
| [Talent pool & saved pipeline](index.html#recruitment-talent) | Separate future consent, factual follow-up, versioned candidate events and irreversible archive states. |
| [Contract repository & workspace](index.html#clm-workspace) | Scoped repository, saved views, lifecycle detail, reporting and local CSV/document-bundle exports. |
| [Contract requests, templates & metadata](index.html#clm-authoring) | Conditional request forms, approved templates/clauses and evidence-linked metadata confirmation. |
| [Contract review & approval](index.html#clm-review) | Version comparison, clause comments, sequential/parallel approvals and version invalidation. |
| [Signature preparation & tracking](index.html#clm-signing) | Signer order, keyboard field placement, readiness checks and explicit provider-event simulations. |
| [Obligations, renewals & amendments](index.html#clm-operations) | Evidence-backed obligations, notice dates, owner decisions and immutable related agreements. |
| [Bot messages & request recovery](index.html#bot-patterns) | Bot availability, readable conversations, one-time secret rotation, stable request recovery and Retry-After feedback. |
| [Build with consistency](index.html#guidance) | Keep the implementation and its written contract together. A design change is not complete until both are updated. |

Actual inventory: **102 HTML/SVG tags**, **22 input types**, **74 sections**.

| Family | Elements in the HTML |
| --- | --- |
| Document | `html`, `head`, `title`, `meta`, `link`, `script`, `noscript`, `template` |
| Structure | `body`, `header`, `footer`, `main`, `nav`, `section`, `article`, `div`, `span` |
| Text | `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `p`, `a`, `br`, `wbr`, `hr`, `strong`, `em`, `b`, `i`, `s`, `small`, `mark`, `abbr`, `dfn`, `q`, `blockquote`, `cite`, `code`, `pre`, `kbd`, `samp`, `var`, `sub`, `sup`, `time`, `data`, `bdi`, `bdo`, `ruby`, `rt`, `del`, `ins`, `address` |
| Lists | `ul`, `ol`, `li`, `dl`, `dt`, `dd`, `menu` |
| Forms | `form`, `label`, `input`, `textarea`, `select`, `option`, `optgroup`, `datalist`, `fieldset`, `legend`, `button`, `output`, `progress`, `meter` |
| Tables | `table`, `caption`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td` |
| Disclosure | `dialog`, `details`, `summary` |
| Media | `img`, `picture`, `source`, `audio`, `video`, `track`, `canvas`, `iframe`, `svg`, `path` |
| Other | `circle`, `figcaption`, `figure`, `rect` |

Input types: `button`, `checkbox`, `color`, `date`, `datetime-local`, `email`, `file`, `hidden`, `image`, `month`, `number`, `password`, `radio`, `range`, `reset`, `search`, `submit`, `tel`, `text`, `time`, `url`, `week`.
<!-- CATALOG:end -->

## Maintenance and CI

### Shared skill for consuming applications

`skills/privy-design-system/SKILL.md` provides framework-independent adoption guidance backed by this live checkout. Its generated `references/catalog.md` indexes components, guideline headings, relevant files and domain guides; `references/source-state.json` fingerprints maintained source files. The skill reads canonical assets and tokens rather than maintaining duplicated brand files. Source hashing excludes the skill directory and synchronization snapshot to avoid recursive fingerprints; the normal sync check still tracks every skill file.

Every `scripts/sync.py --note` run regenerates the skill references from the final documentation and token outputs in the same pass. `--check` rejects stale, missing or edited generated references as well as undocumented source changes. The skill's `scripts/resolve_source.py --check` resolves the real checkout through symlinks and validates synchronization before returning paths and the source fingerprint. A copied skill needs `--source` pointing to the complete checkout. It is not a standalone runtime UI package.

Install locally with `python3 scripts/install_skill.py`; it links the maintained folder into `~/.agents/skills/privy-design-system`. `--skills-dir` supports a different agent or project skill directory; `--check` verifies installation without writing. The installer is idempotent for the matching link and refuses to overwrite another installation. Linked skills see updates directly; a remote machine needs its own updated checkout and link. No background watcher or automatic migration of consuming applications is installed. Consumers record the adopted source fingerprint and relevant patterns in their existing design documentation and validate their own framework implementation.

Getting started in HTML/Markdown documents installation, invocation and update commands. Maintain those instructions with any setup changes. Use the skill for requested Privy adoption, without rebranding unrelated applications or treating demonstration scripts as production integrations. Automatic skill selection remains enabled, with `$privy-design-system` available for explicit invocation.

Every token, markup, style, behavior, or state change must update the relevant written guideline in the same task:

1. Edit the canonical source and its specimen.
2. Explain the changed usage, behavior, responsive rules, or constraints here.
3. Run `python3 scripts/sync.py --note "Describe the change and its reason"`.
4. Run `python3 scripts/sync.py --check` and the checks relevant to the change.
5. Keep source, generated documentation, changelog, and fingerprint together when committing.

`AGENTS.md` requires the same workflow. There is no background watcher and the generator cannot infer good explanatory prose. Fingerprints detect unsynchronized changes, not documentation quality. `sync-state.json` contains file hashes, not credentials. Never edit generated output merely to make a check pass.

`npm ci` installs locked development dependencies; viewing HTML does not need npm. `npm test` runs catalogue, advanced, workspace, reporting, and menu checks. `DS_BROWSER` selects chromium/firefox/webkit; `DS_CHROMIUM_EXECUTABLE` can override the Chromium binary. Screenshots go to a temporary folder or `DS_SCREENSHOT_DIR`.

Basic checks use file URLs. Advanced accessibility checks use a loopback HTTP server so WebKit permits stylesheet inspection; it binds only to 127.0.0.1 and closes afterward. Camera denial is simulated in tests, not proof of successful physical-camera access.

The standalone `.gitlab-ci.yml` runs synchronization before a three-browser matrix. It becomes active only after this folder is committed to a repository with GitLab CI enabled. No remote pipeline or branch protection was configured here. Require successful pipelines in repository settings if this must block merges. A sibling folder on a developer's computer is not automatically available to the Hermod runner.

Synchronization excludes dependencies, Git internals, caches, and test artifacts while tracking CI configuration and the lockfile.

## Usage example

```html
<link rel="stylesheet" href="tokens.css">
<link rel="stylesheet" href="design-system.css">
<link rel="stylesheet" href="advanced.css">
<div class="field">
  <label for="office">Office name <span class="required" aria-hidden="true">*</span></label>
  <input id="office" name="office" required autocomplete="organization">
</div>
<button class="button primary" type="submit">Create account</button>
```

Import only the components and behavior appropriate to your application. Catalogue scripts remain demonstration code, not a production SDK.

## Workspace patterns added in v1.2

The follow-up [coverage audit](COVERAGE_AUDIT.md) records missing patterns separately from the existing Persona mappings. These additions are local UI examples; they do not implement production notification, authorization, or audit APIs.

### Applied filters

Editing criteria does not change results until Apply. Applied chips show each active criterion and can remove it independently; removal updates the relevant field and returns focus there. Reset clears both fields and applied state. Results count is announced. A no-match result offers a reset hint rather than masquerading as a service failure.

### Tree navigation

Use tree/treeitem/group roles, one roving tabstop, expanded branch state, and selected leaf state. Up/Down moves through visible items; Right expands or enters a branch; Left collapses or returns to its parent; Home/End jumps; typing finds a matching label. Enter/Space activates. Disabled archive items remain discoverable but cannot activate. Parent items have explicit labels so child text is not included in their accessible names.

### Activity and notifications

Timeline entries include actor/action, description, and an ISO `datetime` rendered with explicit Asia/Jakarta timestamps. Type filtering applies to loaded entries; Load earlier reveals additional synthetic entries and disables at the end. This is not a production audit log.

Notification center distinguishes read/unread with text as well as a surface. Individual read actions update the unread count; unread-only filters immediately remove newly read rows and restore focus to an available control. Mark all is disabled when unnecessary or when data is unavailable. Loading uses `aria-busy`; empty and error have different copy; Retry restores the local fixture.

### Reordering and satisfaction

Sortable macro steps support pointer drag/drop, named Move up/down buttons, and Alt+Up/Down on a focused handle. Boundary buttons are disabled; focus follows the moved item and a live region announces its new position. Only the local step payload is accepted during drag/drop. Reset restores the fixture order.

Rating uses native radios for one-to-five values, visible focus, and a descriptive label for every score. Do not preselect a score. A missing score prevents submission and focuses the first radio. Optional feedback has a character count. After local submission the score is disabled, comment is readonly, and Reset allows another preview. No real satisfaction response is sent.

### Split panes and page recovery

The desktop separator exposes orientation, minimum/maximum/current values, and its controlled queue. Pointer drag and Left/Right resize from 25–60%; Shift uses larger increments, Home/End select the limits, and Reset restores 35%. At <=640px panels stack, the separator leaves the tab sequence, and resizing is unavailable. Announce pointer changes after the drag ends rather than flooding a live region.

Page-state examples cover first use, no matches, loading, service failure, access required, not found, and ready. Each recoverable state has a working local action. Access guidance does not grant permissions or pretend success. Loading hides actions, announces the state once, and uses decorative skeletons.

The **Error pages** companion at `#error-pages` adds 400, 401, 403, 404, 408, 429, 500, 502, 503 and 504 designs plus an offline state. Find them through Page states & recovery or catalogue search by code, error, offline or maintenance. Preview selectors are native buttons with `aria-pressed`, visible keyboard focus and a named group; selection keeps focus on its button and announces one short status. The default is 404. The preview has a real Privy wordmark, a distinct decorative SVG icon for each error, explanatory heading and recovery actions. Do not add guidance buttons or a “What can I do?” disclosure; keep the explanation in the visible description. The readable footer identifies the HTTP code; offline is explicitly a network state without an HTTP response.

Distinguish authentication (401) from permission denial (403), request timeout (408) from gateway timeout (504), invalid upstream response (502) from an internal failure (500), and rate limiting (429) from temporary unavailability (503). A 503 may reflect maintenance or overload; do not invent an outage cause or restoration time. Follow service-provided retry instructions in integrations; the 429 companion explains waiting in its visible description without a fabricated countdown or immediate retry. Missing content returns to the local dashboard example through Back to workspace. The 400/401/403/404/429 variants show only Back to workspace; they do not expose sign-in/access/request guidance actions or change permissions, drafts or sessions. Never automatically resend an uncertain save or send after an error.

Try again and Check connection run a 600ms local simulation: the action keeps its label, becomes disabled and `aria-busy`, then re-enables with explicit demo feedback while retaining the error. Selecting another variant cancels pending feedback. There are no network requests, redirects, storage changes or simulated success claims from these buttons; the workspace link navigates normally to the existing local example. The shared HTML snippet includes the default error page and selectors; reuse the behavior in the consuming framework. This is an additive catalogue companion and does not change shipped application error handling.

Use semantic surface, brand-soft, selection and text colors with DM Sans in both themes. Keep error-page headings at 24px/32px and body text at 14px/20px; oversized decorative codes range from 56–104px. Selector cards use six columns above 1000px, three through 1000px and two at <=640px. The preview illustration and copy share a two-column layout with 48px spacing, reducing to 24px and stacking at <=640px. Mobile actions fill the available width, text wraps and no page-level horizontal scroll is introduced. Static error copy is not an alert; one polite status region reports preview changes and action feedback. Icons use the existing rounded 1.7px stroke, a 24×24 viewBox and semantic secondary color; display them at 64px on desktop and 48px on mobile. Use a document warning (400), lock (401), shield with X (403), document search (404), retry clock (408), hourglass (429), server warning (500), broken link (502), maintenance tools (503), server clock (504) and disconnected Wi-Fi (offline). Keep the SVG decorative with `aria-hidden`; visible titles and codes carry the meaning. Replace icon geometry when changing variants, without animation or an additional focus stop.

### Completed behavior gaps

Drawer Save updates the local reset baseline; discarding a later edit returns to the last saved values, not the initial fixture. Signature export buttons stay disabled while their respective drawing/text is empty and are disabled again after clearing.

### Font consistency

All example pages receive local font definitions through the shared stylesheet. Body text uses 14px/20px, page headings 24px/32px, section headings 20px/28px, subheadings 16px/24px, and metadata 12px/18px. Member names use weight 600. Buttons/inputs inherit the same family; small-screen fields retain the documented 16px size. Canvas chart labels redraw after font readiness. Code samples intentionally remain monospace, and signature text intentionally uses cursive. Native PDF text is a document font, not application typography. Font provenance, license, and hashes are documented in `assets/fonts/README.md`.

## Dashboard cards

The [dashboard guide](DASHBOARD_GUIDE.md) defines the recommended hierarchy, card purposes, responsive grid, metric semantics, and integration boundaries. `dashboard.css` and `dashboard.js` are shared between the catalogue gallery and `examples/dashboard.html`. There are four headline KPI cards plus chart, progress, queue, activity, team, quick-action, service-health, and attention cards. Card headings are 16px/24px; KPI values 32px/40px with tabular numerals. Semantic heading levels differ between the gallery and standalone page without changing their visual style.

Period changes update related synthetic metrics together. Chart and queue totals reconcile with the created count. Rate deltas use percentage points. Neutral trend text avoids implying that increasing volume is automatically positive. Error and empty states do not retain apparently current KPI data; loading uses matching skeletons and `aria-busy`. Retry restores the local fixture. Full-page examples now include Dashboard Overview alongside the original four pages.

## Reporting charts and downloads

`reporting.css`/`reporting.js` provide eight chart-card patterns, shared period/channel controls, three reporting summaries, accessible aggregate tables, and real local downloads. See [DASHBOARD_GUIDE.md](DASHBOARD_GUIDE.md#reporting-workspace) for chart selection and the data/export contract. This is a separate synthetic delivery-report domain from the support overview. Its snapshot ends on 25 October 2026; all related charts derive from the same filtered rows, including the preceding equal-length comparison window.

Use line/area for chronological counts, grouped bars for comparisons, stacked bars for mutually exclusive outcomes, donut for a few shares, horizontal bars for ranked volume, heatmap for time buckets, and funnel for nested cohorts. Supply exact values in semantic data tables and descriptive SVG names. Keep colors supplemental with labels or line styles. Card layouts become one column at <=800px; summaries become one column at <=640px. Tables scroll within their cards. Retain 14px/20px body and 16px/24px card headings; chart labels use DM Sans at 10–12px inside the SVG viewBox.

Downloads must match the currently applied filters. CSV includes explicit metadata and safe field escaping; SVG is editable and references DM Sans; PNG uses loaded DM Sans for stable exported labels. A generated download is not proof that the user saved it. Recent downloads retain original filter context and support download/removal; their URLs and entries are local to this tab. Export failure offers retry or CSV. Loading/empty/error hide stale visuals and disable new exports. Print explicitly invokes the browser dialog, expands tables, and restores the selected screen theme afterward. Scheduled exports, emails, and production export APIs are not implemented by these demonstrations.

Stacked bars use one rounded outer silhouette. Internal segment joins are straight and continuous; never apply a separate corner radius to each segment. The base fill extends behind the upper segment to avoid antialiasing gaps. Keep the same geometry in screen, print, SVG, and PNG exports.

## Top navbar and page menu bar

The [top menu bar](index.html#top-menu-bar) extends the application header with horizontal page navigation. Keep logo/workspace/search in the header row and page destinations in a separate menu row. Use 14px/20px DM Sans labels, 44px minimum menu targets, 8px between items, and a clear active state using `aria-current="page"`, weight, surface, and underline. Dropdowns use 14px corners and 8px internal padding; item targets remain at least 44px. Keep page actions in the content heading rather than hiding them in navigation.

This is document navigation: use `nav`, lists, links, and native `details`/`summary`, not an application `menubar` role. Tab follows normal document order; Enter/Space toggles a group. Arrow Down/Up from a summary opens its first/last link; within a group, Arrow Up/Down and Home/End move among enabled links. Escape closes a group and returns focus to its summary. Opening another group, clicking outside, or moving focus away closes the previous group. Disabled destinations are visibly labeled unavailable and are not interactive.

At <=760px, the Menu button controls a hidden navigation region with `aria-controls` and `aria-expanded`. Open groups enter normal document flow to avoid clipped popovers. Escape first closes a group; a subsequent Escape collapses the mobile navigation and returns focus to Menu. On a transition to the mobile layout, close navigation and recover focus if the previously focused item would become hidden. Preserve 8px between the active item and hovered neighboring items. Long workspace names wrap. Links in this specimen open real local examples; no backend or production navigation changes are made.

## Grouped catalogue navigation

The catalogue itself uses seven top-navbar groups instead of a flat sidebar: Start, Foundations, Forms, Navigation, Data & reports, Workspace, and Media. Every section remains directly addressable by its original hash. Keep all section links inside exactly one group. Search opens a result dropdown below Find a component and announces the match count; all catalogue groups, links, and sections remain available. A visible section marks its link with `aria-current="location"` and highlights the parent group without automatically opening it.

Desktop dropdowns open on click or keyboard, one group at a time. Tab remains native; Arrow Up/Down, Home/End, and Escape follow the disclosure behavior described above. Clicking outside or tabbing out closes the group. At <=1279px, the component-groups button reveals the grouped navigation in normal layout flow; the hidden region is inert and contains no reachable controls. Mobile dropdowns remain in flow and long groups scroll within their container. Keep the search and theme controls available when the groups are collapsed. The combined sticky header reserves anchor scroll space; print omits it.

## Partial Broadcast outcomes

The [batch-result specimen](index.html#partial-broadcast-result) uses a warning badge for `partial`, with explicit wording that only some recipients were accepted for processing. Acceptance never implies delivery or reading. Display attempted counts and status per batch; show known rejected-recipient counts and expose returned recipient errors in a native disclosure as text. Do not invent rejection counts for unknown results. One unknown batch makes the overall outcome unknown. Keep recovery tied to the persisted operation and do not offer an automatic resend or a resend-entire-audience action in the result summary.

### Chart motion and value inspection

Load `chart-interactions.css` and `chart-interactions.js` before the dashboard/reporting scripts. All eight reporting charts and the ticket-volume chart expose category, series, exact count, and applicable share/timezone through a shared value tooltip. Hover a mark or tap it; the tooltip stays available while hovered and Escape dismisses it. Tooltips remain inside the viewport in both themes. Charts have one keyboard tabstop: arrow keys move between values, Home/End jump, and Escape dismisses. Keep the semantic data table as an equivalent way to inspect every value. Line and area charts include targets for every date, including the 30-day view; do not fall back to a generic native SVG title tooltip.

Entrance animation runs once when each freshly rendered chart becomes visible, and repeats when filters replace its data. Bars grow from the baseline, ranked/funnel bars from the left, and lines/areas/heatmap/donut use a brief reveal. Animate an entire stacked column together to preserve its continuous internal join. Motion lasts 520ms with at most 180ms stagger; no continuous animation or hover replay. Respect `prefers-reduced-motion`, including changes during animation. Charts keep their final base geometry, so exports always contain complete values. Printing stops animation and hides tooltips; SVG/PNG exports omit interactive hit targets and active-point styles. Clear overlays/listeners when replacing or hiding chart data.

### Single-row navbar alternative

The Top navbar & menu bar specimen includes both two-row and single-row headers. `.top-menu-inline` places the logo, workspace identity, Overview/Workspace/Support/Reports navigation, and icon search in the same top header. Use the two-row variant when there is insufficient space for the identity and destinations. The single-row specimen collapses at 1100px (the two-row specimen remains at 760px); its Menu control reveals destinations below the identity in normal flow. Each variant has a unique navigation ID and accessible name. Retain the same keyboard disclosure behavior, active-page styling, and local links. Do not force the desktop row to overflow on tablets.

The catalogue shows its component/group count beneath Find a component, aligned with the result labels using 12px/18px secondary text. The same status region announces matching counts while searching. Version/font metadata stays removed, and no separate metadata footer is reserved.

Opacity-only chart animations must not set `transform-box` or `transform-origin`. Donut segments retain their SVG rotation around the chart centre; changing the CSS transform origin can displace the ring outside the viewBox. Verify actual painted-arc pointer hits, not only dispatched events or data-table values.

### Left-aligned menus and account actions

The third navbar option uses `.top-menu-leading`: page destinations follow the workspace identity on the left; search and the account disclosure align right. It collapses at 1200px; mobile keeps Menu and account controls accessible while page links expand below. The account disclosure shows the sample name/role, Profile, and Log out, with the same arrow-key and Escape behavior as page menus. Profile opens a named read-only dialog; Log out opens a confirmation with Cancel as the initial focus. Closing returns focus to the account trigger. Confirming logout only changes this specimen to a signed-out state with Sign in to restore the sample account. No authentication API, credentials, or real session are changed. Account dropdowns remain anchored inside the viewport; names collapse to an accessible avatar control on narrow screens.

Application navbar controls share a 44px height: workspace/product/account disclosures, language selector, search, and Notifications. Disclosure triggers use horizontal padding only; the notification count is a 24px-high badge centred inside the trigger, so it cannot increase the control height. This applies to the catalogue and all examples that use `advanced.css`.

Sidebar items include Enterprise and Reports disclosures with indented submenu links. Keep 8px between rows, at least 44px targets, a subtle nested guide line, and an active child marked with `aria-current=page`. Native details/summary exposes expanded state; Tab remains native, Enter/Space toggles, Arrow Down/Up enters or traverses child links, Home/End jumps, and Escape collapses with focus restored to the group trigger. These are navigation links, not an ARIA application menu. Keep the active child's group open initially.

### Collapsible icon sidebar

The additive `#collapsible-sidebar` companion in Navigation starts as a 72px icon rail (`--pv-sidebar-collapsed-width`) and expands to the existing 240px sidebar token. A circular 44px hamburger button in the shared header toggles labels, exposes `aria-expanded`/`aria-controls`, and changes its accessible name between Expand sidebar and Collapse sidebar. Keep focus on that button when toggling; Escape from expanded navigation collapses it and returns focus to the toggle. State is local to the open page and is not persisted.

Keep all nine navigation links available in either mode, with 20px decorative SVG icons, 44px minimum targets, 8px gaps and explicit accessible names. Collapsed labels appear on pointer hover and keyboard focus; Escape dismisses them without activating a link. The label popup is hoverable, wraps within the specimen and adds no tab stop. Active Documents keeps its soft rose surface and positional indicator when collapsed. Links open related catalogue sections or existing local example pages; the sample documents do not invoke business actions. Preserve the real Privy wordmark, DM Sans and semantic light/dark colors.

Above 640px the sidebar and flexible content sit in one row. At <=640px, the collapsed rail still sits beside content; expanding moves content below a sidebar capped at 240px and the available width. This avoids an overlay or horizontal page overflow. Resizing retains the chosen state and keyboard focus. Width changes are immediate, including under reduced motion; no delayed focus or hidden navigation controls are introduced. Copyable HTML includes the sidebar shell and both sample document cards. This companion does not modify the existing static sidebar variants or consuming application navigation.

### Catalogue search suggestions

Find a component is a combobox with a listbox below the input. Match section titles, component subheadings, and keywords; show the section name plus its group and matching subheading. Searching never hides navigation or catalogue content. Click/tap or Arrow Up/Down followed by Enter selects a destination, closes the dropdown, clears the query, scrolls to the section, and focuses its heading. Input focus stays in the combobox while navigating options through `aria-activedescendant`; Tab closes without trapping focus. Escape clears the query and closes, and an outside click dismisses. No results is a message inside the dropdown, not an empty page. The popup scrolls internally; at <=1279px it enters flow below the field within the mobile header so results remain visible.

## Operational workflow patterns

The 13 workflow sections in `workflow-patterns.css`/`.js` are composed dashboard patterns, not production services. See [Operational workflow guide](OPERATIONAL_PATTERNS.md) for each interaction and its boundary. Every record, request, permission, key, job, schedule and session in these sections is synthetic and lasts only for the current page. No authentication, webhook, broadcast, import or publishing request is sent. Local CSV downloads contain only the demonstrated data. Do not describe these previews as persisted backend features.

Use semantic tables with scroll containers, labeled native form controls, visible status messages, and named action buttons. Table column settings must not remove row identity or action access. Inline Save validates before updating; Cancel restores the previous value. Changes to permission inputs invalidate a previous review, so Apply cannot submit an outdated preview. Unknown bulk outcomes must be checked rather than resent. A failed refresh retains explicitly labeled stale data and its successful snapshot time. An edit conflict preserves the user's draft until the user chooses which version to keep.

Import validates the file size, headers, quoting, row count, mapped columns, required fields, supported roles, and duplicate emails. Invalid rows remain reviewable and downloadable; Import only processes valid rows and becomes unavailable after completion. New file text or mapping changes invalidate prior validation. Apply CSV formula escaping to generated exports. Production validation must also happen server-side.

Job states distinguish queued, processing, completed, failed, cancelled and expired. Only completed, unexpired jobs expose downloads; Retry is restricted to failed or cancelled jobs. Scheduled run previews use only UTC or Asia/Jakarta; do not extrapolate these fixed-offset examples to daylight-saving timezones. Paused schedules expose planned dates without implying execution. Kanban capacity is enforced by the same move operation for dragging and native Move to controls. Preserve input, pending changes and actionable messages after errors.

Draft autosave is debounced by 500ms into memory, not localStorage. Published versions are immutable snapshots; restoring creates a draft and requires Publish to create another version. Comparisons show both versions as text. API key examples are visibly nonfunctional samples, displayed once after a reviewed local rotation and removed on Hide. Session revocation cannot target the current demo session. Quiet hours use the selected timezone and allow overnight intervals.

Workflow panels use 20px padding, 16px/24px subheadings and 14px/20px body text. Grids collapse to two columns at 800px and one at 640px; Kanban becomes a vertical stack below 800px. Horizontal scrolling is confined to tables. Keep synthetic-state controls visibly labeled so they cannot be mistaken for real operational settings.

### Pagination alignment and focus treatment

The breadcrumb specimen's pagination footer places the page summary at the far left and navigation controls at the far right, vertically centred in one row. At very narrow widths, compact page controls keep the footer inside the card. The live page summary continues to update when navigating.

The semantic focus color is now Privy Red in light mode and a contrasting rose in dark mode. Native keyboard `:focus-visible` rings remain visible, including after Escape restores focus from a dialog or menu. Pointer-focused buttons, links and summaries suppress browser-default outlines when they are not `:focus-visible`. Macro hover shows a surface only; the focus ring is exclusive to keyboard focus. Never remove all focus indication or blur a restored control merely to hide its outline.


### Font consistency, field borders and notification refinement

The shared `--pv-font-sans` token explicitly supplies DM Sans to body text, buttons, inputs, selects and textareas. The overview preview uses the same 14px/20px/600 button text as standard actions; it no longer shrinks those labels to 12px. Compact buttons and metadata keep their intentional documented sizes. Code remains monospace and visual signature text remains cursive. Decorative search/check icons use SVG where a text glyph would fall back to an OS font. Verify rendered fonts, not only the computed family declaration.

Default control borders use warm `#b9aaa3` in light mode and muted rose `#715d69` in dark mode. Focus retains the semantic red/rose treatment, and disabled/error states remain distinct. Workflow forms put 24px between their fields and action rows; status badges and standalone actions keep their intrinsic width.

Notification center uses separate surface cards with 12px between rows, 16px/18px padding and a 14px radius. An unread item has a thin red inset marker, a small tinted SVG icon and a labeled unread dot; avoid a solid rose background across the whole card. Titles are 14px/20px at weight 600, body text 14px/20px, metadata 12px/18px. Each fixture includes a semantic timestamp in Asia/Jakarta. The read/unread action is a quiet ghost button aligned right on desktop and below the copy on small screens. Keep count, unread filtering, focus recovery, loading/error/retry and empty states working.

Every `[data-global-search]` trigger must open the shared workspace-search dialog, including the two-row, single-row and left-aligned account navbar variants. Bind all triggers rather than the first match. Move focus to the search input and select any existing query; Escape/Close returns focus to the exact opening button. Triggers expose `aria-haspopup=dialog` and `aria-controls=workspace-search`.


### Main single-row catalogue header

The catalogue account dropdown includes **Download Skill**, after Guidelines and before the logout divider. Use a native link to `downloads/privy-design-system-skill.zip` with the `download` attribute and the same menu-row styling. It participates in arrow/Home/End navigation; Arrow Down/Up from the account trigger enters the first/last item once without skipping an item through event bubbling. Activation starts the browser download, closes the dropdown and restores its trigger focus. No authentication or backend action is required. The extra row must remain reachable in both themes and narrow/wide viewports.

The ZIP contains only `skills/privy-design-system/`, packaged as `privy-design-system/` with SKILL.md at its root, UI metadata, references and the source-resolution helper. Do not include application source, catalogue pages, stylesheets, fonts or other design-system assets. Extract the skill folder into the target agent’s skill directory. This is a source-linked skill: keep the design-system checkout accessible and use `scripts/resolve_source.py --source /path/to/design-system --check` from the extracted skill. Every `scripts/sync.py --note` rebuilds the ZIP after updating skill references and fingerprints. Serve the generated `downloads/` asset with the catalogue. `python3 scripts/package_skill.py --check` verifies that the ZIP exactly matches the maintained skill files. A downloaded ZIP is a snapshot, not a live update subscription.

The catalogue now uses the left-aligned single-row layout: Privy identity, the seven component groups, then component search, theme and sample account actions. At <=1279px Menu reveals groups below the header; links remain hidden and inert while collapsed. Search is an icon that opens a field with its component count beneath it; `/` opens and focuses it. Suggestions retain every catalogue section and group. Escape clears the query, closes the search panel and restores its trigger; selecting a result closes it and focuses the destination heading. The account menu provides Guidelines, a read-only Profile, and a local-only Log out confirmation/Sign in state. This does not authenticate or end a real session.

### Motion recommendations and implementation

Use motion to explain a change caused by the user, not to animate the entire catalogue while scrolling. `motion.css`, included in the catalogue and five examples, applies the shared timing tokens: 140ms for hover/color/focus-border feedback, 200ms for switches, chevrons, dropdowns, sidebar submenus, tooltips and tab content, and 240ms for dialogs, drawers and toasts. Menus move only 4px; dialogs 8px, side drawers 20px and mobile bottom sheets 16px. Focus, keyboard input and native closing remain immediate; there is no exit delay or height animation that shifts surrounding content. Inactive panels remain hidden and native dialogs retain focus containment. Keep card bodies and table rows stationary for scanning and selection. Existing chart entrance motion and loading shimmer retain their specialized rules. Do not animate progress/count values to imply unconfirmed work. Reduced motion (including a live preference change) and printing disable transitions/animation; content remains fully visible without motion.

The interactive data table uses a 16px two-chevron SVG, an 8px label gap and `aria-sort` on the header. The active chevron follows ascending/descending order, with the inactive chevron muted. The sort button's accessible name announces the current and next order; the decorative SVG is hidden from assistive technology.

### Toast and API-key presentation

Toasts use a surface card with a subtle border/shadow, a 20px informational SVG, 14px/20px text and a 36px transparent close control. Hover/focus gives the close control a muted surface with contrasting text, not a pale circle with a white glyph. Long messages wrap, the live region stays polite, and the 6.5-second dismissal pauses while hovered or keyboard-focused. Dismissal restores focus when it was inside the toast. Do not use a transient toast as the only record of an actionable failure.

API-key preview separates its description, labeled monospace value and Copy/Hide toolbar with 16px gaps. The value wraps within a muted bordered surface; narrow actions wrap without overflowing. Rotation confirmation uses the same spacing. Status text stays below the preview. Rotation/copy/hide remain synthetic, and hiding removes the displayed value.


### Close-control sizing

Close and dismiss actions in dialogs, drawers, search, profile, confirmations, notices, uploads and toasts use a consistent 18px SVG X with a 1.7px rounded stroke inside a non-shrinking 36×36px button. Use SVG geometry rather than a small typographic multiplication/cross character. Dialog close buttons have a subtle semantic border; hover adds the muted surface and keyboard focus retains the red/rose ring. Compact chip removal is the exception: an 18px SVG inside a 32px target. Preserve accessible labels, button types, cancellation values and focus restoration. These rules apply to the catalogue and all five example pages in both themes.


### Back to top

The long component catalogue exposes a fixed Back to top button after 400px of document scroll. Use a 24px arrow inside a 48×48px circular primary button, inset 24px from the bottom/right (16px on small screens) plus device safe areas. Hide it near the top and when printing. Toasts move above it with a 16px gap so both actions remain usable. Its accessible label is Back to top. Activation focuses the catalogue brand link without jumping, then scrolls smoothly to the document top; reduced motion uses immediate scrolling. The hidden button is removed from keyboard navigation. Scroll handling is passive and updates at most once per animation frame.


Close icons use a block SVG, zero button padding and grid centering. Do not combine a fixed SVG size with padding that leaves less room than the icon after borders; that shifts the icon off-centre. The compact 36px button retains visible focus and a subtle dialog border.

### Language segmented toggle

The application navbar uses an EN/ID segmented control instead of a native select. The outer pill is 44px high, matching adjacent controls, with a filled active segment and a semantic light/dark palette. Use a labeled group and buttons with `aria-pressed`; there is no native OS dropdown. This English-only catalogue selects EN and keeps ID visibly disabled with an unavailable label until translated content exists. Do not switch an active-language indicator without translating the associated interface. Keyboard focus remains visible on the available option. The same pattern is included on all five example pages.


### Mobile verification for compact controls

Keep the close control visually 36×36px with an 18px centered icon. On touch devices, a transparent 4px extension on each edge provides a 44×44px hit area without enlarging its visible circle. Preserve spacing around adjacent actions. Mobile catalogue navigation collapses into disclosure groups; search results enter normal flow. Dialogs scroll within the dynamic viewport and drawers become bottom sheets. Forms/cards stack and tables retain internal horizontal scrolling. Verify 320–430px phones, 768px tablet and landscape in both themes, including open search/dialog states, tap dismissal and the fixed Back to top action.


### Responsive preview workspace

Open `responsive-preview.html` from the catalogue's phone icon, account menu, Getting started, or page examples. The preview embeds the real catalogue or one of five example pages with a true iframe viewport. Presets cover small/mobile/large phones, tablet and desktop; users may apply custom dimensions from 280 to 2560px, rotate, select light/dark, reload, or open the chosen page separately. Fit to space scales the frame visually without changing its CSS viewport; 100% preserves actual CSS-pixel display and confines overflow to the preview stage. Toolbar controls wrap on small screens. Constrain form grid tracks and clip native select text with an ellipsis to prevent WebKit from extending the page beyond the mobile viewport. The local theme bridge accepts only light/dark messages from its parent window and does not persist the host's choice. This previews CSS layouts and interactions, not device hardware, touch-pointer emulation or a software keyboard; use browser device tools or a real device for those. No business API is called by the preview host.

Search-result counts sit below the search input and above the list, aligned with result text. Use the singular “1 matching component” and plural for other counts. Keep the full input width and existing combobox keyboard behavior.

### Burgundy sidebar option

The Navigation gallery includes a separate burgundy sidebar with the same submenu destinations and keyboard disclosure behavior as the default sidebar. `sidebar-burgundy` uses the canonical burgundy background, light labels, a lighter burgundy hover/selected surface, a rose active-page indicator and rose focus rings. The branded surface stays burgundy in either outer theme. Use `sidebar-on-brand`, `sidebar-brand-hover`, `sidebar-brand-border` and `sidebar-brand-indicator` tokens for contrast; do not inherit dark-theme body labels into this surface. Submenus retain their guide line and minimum 44px rows, and the surrounding gallery stacks on narrow screens.


### Custom searchable selects

Load `custom-select.css` and `custom-select.js` in the catalogue, responsive preview and five examples. Every single-select is enhanced into an editable combobox; multi-select checkbox groups retain their existing interaction. Clicking the field or chevron opens the complete list and a Search options placeholder; typing filters case-insensitively by option text and group. Empty search results provide an inline message. Optgroup headings, disabled groups/options and selected checkmarks remain visible. Arrow Up/Down, Home/End move the active option, Enter commits, Escape dismisses without committing, and Tab closes while continuing normal focus order. Keyboard focus stays in the input through `aria-activedescendant`.

The original select keeps its ID, name, value, form data, required validation and existing input/change handlers. Only committing a changed option dispatches input/change; typing a search does not change the selected value or mark the form dirty. External value/selectedIndex assignments, option-list updates, disabled state and form reset synchronize the visible control. Invalid required selections focus the visible input. Dynamically inserted workflow selects receive the same behavior. Native backing fields are excluded from accessibility navigation; labels point to the visible combobox.

Popups use the browser's top layer when available and a dialog-local fallback otherwise. Their custom list remains inside the viewport, flips upward when needed and scrolls internally; changes to the visual viewport reposition it above a mobile keyboard. Only one opens at a time. Option targets are at least 44px, mobile input text is 16px, and closing a dialog also closes its select popup. Without JavaScript, the original native select remains usable.

Preserve the option list through pointer-down/up so touch taps can commit before focus changes dismiss it. Observe visibility/disabled changes only for the select or its ancestors; unrelated popups must not rebuild options mid-gesture. Keep validation errors synchronized on the backing field and clear internally generated errors after a valid selection or form reset.


Grouped select lists use a distinct noninteractive section heading: 11px/16px semibold uppercase text with modest letter spacing, 6px/10px padding and a muted background. Options keep 14px/20px text and at least 44px targets, inset 8px from the group heading. Separate successive groups with a subtle divider and 10px spacing. Group headings never receive option hover, selection, keyboard focus or click actions. Keep semantic colors in both themes and preserve the original group names for search and accessible labels.


### Inputs with attached units

Use `.input-group.input-group--unit` for an editable number with a fixed unit, such as retention in days, a percentage, or a currency prefix. The input and muted unit panel share one pill-shaped outer border; clip the panel to the outer radius and use only one internal divider. Keep the control at least 48px high, the editable field flexible, and the unit nonshrinking. `.input-unit--prefix` moves the divider to the right for currencies. Units are text, not buttons or selectable values. Connect them through `aria-describedby`, retain a visible field label and appropriate numeric min/max/step, and use tabular numerals. Focus outlines the whole group. Disabled, readonly and invalid states follow semantic tokens in both themes. The three examples stack on mobile.


### Dashboard input recommendations

The Dashboard input patterns section adds six working local examples. Prefer formatted currency for budgets, country-code plus phone for contacts, URL feedback for endpoints, min/max pairs for numeric filters, freeform tags for categories, and clearable search for directories. Keep existing password, OTP, date/range, upload and custom-select patterns for their established use cases.

Formatted amount uses whole IDR units from 0 to 999,999,999,999. Format grouping with the explicit English locale on blur; edit plain digits on focus and preserve malformed input for correction. Accept plain digits or correctly grouped commas, reject decimals and out-of-range values, and keep a hidden plain numeric form value. Never silently round a malformed amount.

The phone example combines a searchable country-code select with a `tel` input. Remove presentation spaces, hyphens and parentheses only; require a national number without a leading zero and a maximum of 15 combined digits. This is a format preview, not country-specific validation, number ownership verification or a message send. Production requirements must define their numbering rules.

The URL example adds HTTPS when a protocol is missing and accepts only HTTP/HTTPS URLs without whitespace or embedded credentials. Render normalized addresses as text; do not navigate or request them. Valid format does not establish reachability or authorize a webhook. Numeric ranges accept inclusive whole counts from 0 to 1,000,000 and require minimum <= maximum; preserve both values on error and focus the field needing attention.

Freeform tags accept Enter/Add tag and comma-separated input, deduplicate case-insensitively, and limit the list to five tags of 24 Unicode characters each. Validate the whole addition before changing the list. Render user text with `textContent`; named remove buttons restore input focus. Backspace on an empty field focuses the last remove button without deleting a tag. Clearable search filters a local fixture, distinguishes empty results, hides Clear when empty, and restores input focus on Clear. Escape clears an active query.

Use visible labels, linked helper/error text and `aria-invalid`. Clear stale results on edit; reset restores the initial values. Keep 16px between grouped controls, 20px above each form and a single-column layout on small screens; phone groups stack below 800px and range pairs below 400px. No examples call business services or persist entered values.

### Advanced form patterns

`#form-patterns` combines eight reusable patterns in a local notification-rule form: repeatable contacts, conditional delivery fields, company/division/member dependent selects, radio/checkbox cards, asynchronous ID availability, bounded number steppers, a before/after review dialog and sticky form actions. The fields are grouped by task, with visible labels and brief helper text. None of the lookup, check or save states invokes an API or persists data beyond the open page.

Repeatable groups retain stable control IDs, permit one to five rows and focus the next remaining row after removal. Adding focuses the new name field. Conditional fields preserve draft values, disable the inactive channel controls and omit them from the saved snapshot. Assignment changes clear downstream selections immediately; loading, empty and failed lookups are distinct. Versioned responses ignore stale work after a parent changes or Cancel restores the baseline. Explicit Retry repeats only the failed lookup.

Use native radios/checkboxes inside full-card labels and fieldset/legend groupings. Icons are decorative; selection is communicated by the native checked state as well as the semantic rose surface. The number stepper permits direct editing, disables actions at valid bounds and preserves invalid input for correction instead of silently clamping it.

Validate a field on blur, then while correcting it. Failed submit presents a linked error summary and focuses that summary; each link focuses its control or the available parent/retry control for a disabled dependent field. Rule ID availability is debounced after the first blur and guarded against outdated responses. `taken` and `offline` are explicit unavailable/error fixtures; an incomplete or failed check cannot authorize saving. The simulated backend is not an authorization or uniqueness guarantee for production.

Review shows only differences from the last successful save, including removed conditional values, through text nodes. Escape/Keep editing returns to the draft without changes. Saving locks the editable group and rejects repeated submissions. Failed save retains the entire draft and offers Retry; successful save establishes a new baseline. Cancel restores the last saved values and cancels pending lookups/checks. Edits after a failure invalidate the retry status and require a fresh review.

Comfortable density uses 24px group/column spacing and 48px fields. Compact uses 20px group spacing, 16px gaps and 40px fields on desktop; mobile retains at least 44px controls and 16px input text. Density is scoped to the specimen, does not alter values and is not saved. Fields stack at 640px; dependent selects and contact rows stack at 800px. The action footer sticks inside the form, reserves its own layout space and includes safe-area padding. When an editable field is focused and the visual viewport shrinks for a soft keyboard, the footer returns to normal document flow. Physical-device keyboard behavior still needs integration testing.

### HTML and JSON editor

`#code-editors` offers HTML Content Code/Preview tabs, Variables (JSON), Sample Payload (JSON), independent JSON formatting, validation and reset. Use semantic `editor-bg`, `editor-fg`, `editor-border` and `editor-muted` tokens for the dark code surface in both themes. Surrounding labels, tabs and actions retain DM Sans; code uses a system monospace stack for alignment. Editors use 8px corners, 16px padding and a 280px minimum HTML area. At 640px and below, input text is 16px and the layout remains a single column. Tab leaves a textarea normally; the tablist supports Left/Right/Home/End with a single tab stop and does not change other catalogue tabsets.

Variable definitions are an array of unique names with a string/number/boolean type and an explicit required flag. Sample payload must be an object; required values and provided value types are checked. Malformed JSON and schema errors preserve input and connect inline errors with `aria-describedby`/`aria-invalid`. HTML is limited to 100,000 characters and each JSON field to 20,000. Browser HTML parsing is forgiving: validation is not HTML linting, syntax highlighting, a production template engine or a complete email-client preview.

The preview is a sandboxed, scriptless iframe with a restrictive CSP and a static element allowlist. Forms, links, embeds, external assets, scripts and document metadata are omitted; supported inline styles remain. Variables are substituted into text nodes after parsing, never into raw HTML or attribute values. Preview content uses an isolated system font; it does not load external fonts. This limits the demo renderer and must not be reused as a general-purpose HTML sanitizer. Changed or invalid input clears the old preview, so a stale rendering cannot be mistaken for current data. Theme changes update a valid visible preview. All actions remain local.

Form blur validation waits until a pointer action completes before inserting error text, so validation does not move an Add/Remove target between pointer down and click. The catalogue’s floating Back to top and toast positions move above the sticky form footer while it is visible.

### Search with a leading filter

`#filtered-search` demonstrates status filtering and field-scoped search using a custom dropdown at the left of a query input. Both segments share one rounded outer border; the filter has a muted fill and one divider. Labels remain distinct and accessible, keyboard focus identifies the active segment, and custom-select search/commit/cancel behavior is unchanged. The status variant combines selected status with a name/email/ID query; the scope variant searches only the chosen field. Text matching is case-insensitive and preserves entered text. IME composition completes before filtering.

Results update locally with a singular/plural count, text status badges and an explicit empty state. Clear and Escape clear only the query and preserve the filter; Reset all restores both and focuses the query. At the default state Reset is disabled. Enter never navigates or submits a business request. At <=480px the filter stacks above the query inside the same control, preserving 48px controls and 16px text. Result rows wrap on mobile. The pattern uses existing tokens and no new brand colors.

### Recruitment domain patterns

The catalogue now includes Recruitment workspace, Job openings & forms, and Talent pool & saved pipeline, plus [a full-page example](examples/recruitment.html). Follow [RECRUITMENT_GUIDE.md](RECRUITMENT_GUIDE.md) for the exact stage matrix, role/team scope, candidate privacy, future-opportunity consent, cohort metrics and fixture limitations. These patterns address the supplied recruitment gap brief and engineering workflow rules without adding new color tokens.

The workspace combines scoped KPI cards, candidate table/search/filters/sort/pagination, stage board, profile drawer and explicit-WIB interviews with separate reviews. Keyboard Move stage and drag-to-review share the same transition and revision checks. Hired requires an accepted current offer; Rejected/Withdrawn are alternative terminal outcomes. Verified identity is read-only; HR notes, private rejection reasons and DOB are excluded from the interviewer presentation. Candidate-visible text is constructed separately from private fields.

Job templates and openings remain in permitted team scope; only Published accepts applications. Talent membership is distinct from an application. Pending future consent blocks outreach; Declined/Withdrawn archives with no ordinary HR reopening. Contacted/Replied require a factual note, and a grant alone never changes outreach stage. Role/offer/consent controls are explicitly labeled demo simulations, not real permission or identity events. Server authorization and transactional version/ownership checks remain mandatory in integrations.

Ready/Loading/Empty/Error/Permission-limited/Stale/Updated apply across the recruitment specimens. Non-data states hide fixture surfaces; Stale is labeled and disables mutations. Tables and board scroll internally, cards/fields stack below 640px, and the modal profile becomes a bottom sheet. Reuse the shared 36px close control, DM Sans and light/dark semantic states. Follow-up values and notes render through text nodes. All actions are local and synthetic.

### EBS table footers and compact heading summaries

`#table-pagination` adds a reusable Rows per page control **inside `.pagination-footer`**, alongside the pagination group and outside the table scroller. Keep the result range on the left; place page size before page navigation on the right. The select is 88px wide and 48px high, with an 8px label gap and 24px before pagination. Use the visible/accessibility label “Rows per page” and unique IDs. Supported demo sizes are 10/20/30/40/50; changing size, sort or filters returns to page 1. Below 768px, wrap the range and controls; below 440px, use separate page-size and navigation rows.

The local remote-data simulation uses a single query snapshot, full-result filtering/sorting and a newest-request guard. Loading skeletons replace rows; unknown totals are never shown as zero. Error and successful empty results are distinct. Retry preserves query, old responses cannot replace newer data, and size selection remains available with empty/error results. Page buttons maintain focus when regenerated. Named row actions open a local detail dialog and restore focus. Reduced-motion skeleton behavior follows the existing shared rule.

`#heading-summaries` provides a compact `dl` of three to five labeled counts next to a page heading. Use 16px/24px semibold values, 12px/18px labels, 12px/16px metric padding, 8px gaps, 12px corners and roughly 106px minimum desktop width. At narrow widths, move the title above a two-column metric grid. These counts are read-only. Loading uses skeletons, successful empty uses zero, unavailable uses an em dash and Retry. State the aggregation scope; overlapping role counts are not a unique-user total and must not be derived from one table page.

[FRAMEWORK_TABLE_GUIDE.md](FRAMEWORK_TABLE_GUIDE.md) maps the actual EBS Vue/Persona wrapper props and slots, client versus remote pagination, request cancellation/version guards, server sort/filter ownership, state rendering, row-action accessibility and theme tokens versus component configuration. It also records adoption gaps in the current wrapper; the shared catalogue does not silently modify the consuming application or its API.

The EBS table uses one live request-status region; the visible range is plain text to avoid duplicate loading announcements. At <=360px, pagination gaps reduce to 2px so Previous, five page numbers and Next remain on one row. A page activation explicitly retains focus through skeleton replacement, including browsers that do not focus buttons on pointer activation. Retry moves focus to the persistent table region before replacing its button.

### Contract lifecycle management

Five CLM specimens and `examples/clm.html` cover repository/reporting, intake/templates/extraction, version review/negotiation/approvals, signing preparation and obligations/renewals/amendments. Follow [CLM_GUIDE.md](CLM_GUIDE.md) for the fourteen-pattern map, exact transition rules and integration limits. All records/events are synthetic and use 27 October 2026 WIB; all UI copy is English.

Keep workflow, signature and effective-term statuses separate. Save and metadata confirmation create versions and invalidate prior approvals; a pending/unknown envelope or signed original locks editing. Unknown signing results require reconciliation before further action. Internal comments are absent from counterparty comment previews and downloads. Scope applies consistently to tables, metrics, relationships and exports. Signed snapshots remain immutable while operational evidence and linked drafts evolve. Notice deadlines and expiry dates must be separately labeled; recording an intent never claims notice delivery.

Use shared DM Sans, semantic theme tokens, custom searchable selects, 20px field gaps and 24px action spacing. Detail/placement panels stack at 1000px; form controls stack at 640px and use 16px mobile input text. Tables have named internal scroll regions; no page-level overflow. Comparison provides textual added/removed cues and keyboard change navigation; signature placement has numeric coordinate controls. Dialogs use the existing 36px close control, Escape and visible-trigger focus return. Width transitions respect reduced motion; precise report values remain visible.

Loading/empty/error/permission states hide dependent data; stale/read-only states disable mutations. Dirty document switches prompt before discarding; revision conflicts preserve text for download and manual merge. The rest of intake/operation fields are temporary form state, not autosaved drafts. UI role previews do not provide backend authorization. The metadata extractor, plain-text redline, signer provider events and document bundle intentionally do not claim production AI, document rendering, electronic signing or durable audit functionality.

CLM renewal calendars show the notice and expiry months as labeled, read-only tables with circular marked dates. Exact ISO dates and rule text remain outside the calendar. A signed, effective Supersedes relationship marks the original term Superseded while retaining its immutable snapshot.

Before entering Approval, the fixture source clauses must match counterparty, currency, value, effective/end dates and notice period. Missing/mismatched values require source correction or explicit extraction confirmation. Cancelling a dirty access/state switch restores the latest rendered selection, including after a programmatic Refresh.

### Daily trends filter visibility

In the consuming Support reports page, Daily trends keeps the period/date controls visible below its heading and omits the Show/Hide filters action. These date controls define the chart context and must remain available even if a previous report view had collapsed its filters. Other report views retain their existing disclosure behavior. Shared reporting specimens already present their filter controls directly; this page-specific change does not introduce a global disclosure rule.

### Bot messaging and recoverable operations

`#bot-patterns` documents the reusable parts of the consuming OA Bot page: disabled bots retain readable conversations; compose/reply actions require an active bot; incoming and outgoing messages use distinct left/right surfaces with sender and time labels. At narrow widths, bot/room lists and the thread stack without page overflow. Use DM Sans, shared semantic colors and 36px close controls. Render unfamiliar payloads as escaped text or explicitly safe HTTPS links, never arbitrary HTML.

Secret creation/rotation uses a confirmation and a one-time value panel with Copy and Close and hide. Remove the displayed value on close, context change and unmount; do not persist application keys, webhook secrets or recipient scope tokens in browser storage. A lost secret-response is uncertain and cannot be silently replayed. The catalogue uses visibly nonfunctional demo values.

Persist a Broadcast review ID and exact schedule intent before dispatch, scoped to user/enterprise/draft. Recovery must replay that same intent even after review expiry; an uncertain request never creates a fresh review automatically. Without verifiable tab storage, block sending and explain how to enable recovery. A normal unsubmitted expired review offers a fresh snapshot and clears confirmation. The catalogue recovery fixture demonstrates stable identifiers locally; the production consumer uses sessionStorage and backend idempotency.

Before schedule mutations, reload the latest record. Changed version/status requires renewed confirmation; preserve optimistic version checks on the mutation itself. Display structured rejection reasons separately from transport errors. Only poll a submitting operation while visible; stop on accepted/partial/rejected/unknown and provide manual Refresh. Rate-limit feedback uses the server Retry-After duration, keeps the draft, prevents premature requests and displays a non-live countdown plus concise status announcements.

Bot sends preserve their idempotency key for identical ordered recipients and text. The consumer stores only key/payload digest, never recipient tokens or message contents. If inputs differ while a prior request is uncertain, require reconciliation before starting a new message. Per-recipient 2xx/failure results remain visible even when the overall HTTP request succeeds. Production bot polling is visibility-aware, stops on unmount and reconciles by message ID; the catalogue conversation remains local.

Recovery feedback uses warning colors for unconfirmed outcomes, error colors for rejections and informational colors for acceptance; acceptance is not delivery confirmation.

## Broadcast delivery snapshots

The [delivery specimen](index.html#broadcast-delivery-result) separates saved request acceptance from live job progress. Keep Operation status unchanged when the job is queued, running, completed or failed. A failed job uses an error badge/notice without changing an accepted request badge. Completion means fan-out processing has finished; it does not mean every message was delivered.

Show Total recipients, Sent, Failed to send, Delivered and Read as five labeled counts. Delivered already includes Read; never add these two values or present them as disjoint chart segments. The processing progress uses `(sent + failed) / total_recipients`, clamps its visual fill to 100%, and avoids division by zero. Keep returned raw counts visible. Show nullable started/completed times and checked_at with seconds and an explicit timezone; do not replace server check time with browser time.

Delivery is detail-only whole-enterprise job enrichment. Null, absent or malformed snapshots show unavailable wording, not zero metrics, without hiding the saved operation. Selected recipients retain their batch outcomes; test/jobless sends do not claim delivery tracking. On a failed detail refresh, mark retained figures as last-known; a fresh null response replaces them with the unavailable state.

Poll submitting operations and queued/running jobs every five seconds only while visible/online, without overlapping requests. Honor Retry-After, abort on leaving/changing request scope, and stop automatic polling at terminal job states or unavailable enrichment. Manual Refresh remains available because delivered/read receipts can change after completion. Never replay sends as part of refreshing. The catalogue state selector/refresh uses synthetic local snapshots only.

Use semantic surface/error/info roles, DM Sans and tabular count numerals. Five metrics wrap to three then two columns; timestamps stack on phones. Use a labeled native progress element, textual status and native keyboard controls; do not announce the entire metric panel on every poll. The consumer retains its existing ID/EN localization.

### Delivery job in request activity

Show the current delivery job status as a single row after the persisted request timeline. Omit the separate Latest status card. The row uses completed_at for completed/failed jobs and started_at for running jobs. If that timestamp is absent (including queued jobs), show checked_at explicitly labeled Last checked; do not present it as a completion time. Missing delivery means no synthetic job event. Refresh replaces the current job row, never duplicates it or writes it into the saved request timeline. Request acceptance remains distinct from delivery completion.

Place one Refresh button at the top right of Delivery results beside its job badge, wrapping within the card on mobile. Remove the duplicate request badge and Refresh status action from the page header; keep Add to New Broadcast there. Refresh reads the existing detail endpoint, updates metrics and activity together, and is disabled while loading/offline. Preserve drafts and Retry-After behavior. Omit the instructional sentence about manually refreshing after completion; the labeled control provides the action directly. Both successful and unavailable result states retain the Refresh control.

Use DM Sans and semantic timeline dots, metadata and error roles. Timestamp text includes seconds and timezone. The shared specimen demonstrates these rules with local fixtures only.

Keep the result card concise: omit the long Sent/Delivered/Read explanatory paragraph and the parenthetical processing formula from its visible copy. Show only the processed/total count and “recipients processed.” The underlying calculation and overlapping receipt semantics documented above remain unchanged.

Place the request-status notice inside Delivery results below the title/actions, including partial/unknown/rejected guidance. Remove the “Sending progress and message receipts from PrivyChat.” subtitle. The page header does not repeat this status notice or the Refresh control.

### Result wording and pagination geometry

Broadcast UI uses Request completed, Request failed and Delivery request; backend job field names are unchanged. Hide the accepted-request notice when delivery is completed, including after refresh. Keep other outcome notices and failure guidance.

Pagination buttons must have equal width/height, zero padding and centered content so numbered, arrow and disabled states are circular. EBS buttons are 44×44px on larger screens and 32×32px below 440px; compact breadcrumb controls are also square. Do not combine a narrower width with a taller minimum height. Rows per page keeps its 88px wrapper width after custom-select enhancement.

In consuming recruitment views, load job templates on initial entry and when an authenticated HR role becomes available, including cached sessions and HR non-admins. Admin-only loaders must not block HR-authorized resources. Coalesce overlapping reads; retain the existing loading/error states and manual refresh.
