# Infisical Design System (v3) This document captures the v3 visual language and product voice used across Infisical. It is the single reference for engineers, designers, and AI coding agents producing new UI or user-visible copy. **Source of truth for tokens and themes:** [`frontend/src/index.css`](frontend/src/index.css) (`@theme` blocks and theme overrides). **Canonical semantic reference:** [`Badge.stories.tsx`](frontend/src/components/v3/generic/Badge/Badge.stories.tsx). **Canonical page references:** [`OverviewPage`](frontend/src/pages/secret-manager/OverviewPage) and [`AccessControlPage`](frontend/src/pages/project/AccessControlPage). **Component usage reference:** Start with the sibling story in [`v3/generic`](frontend/src/components/v3/generic) or [`v3/platform`](frontend/src/components/v3/platform) when one exists, then check source and consumers. Some exports, including `CopyButton`, `DataGrid`, and `Sidebar`, have no sibling story; the [generic](frontend/src/components/v3/generic/index.ts) and [platform](frontend/src/components/v3/platform/index.ts) barrels are the complete export catalogs. --- ## 1. Visual Theme & Atmosphere Infisical is a security tool for operators. The interface reads like infrastructure: dense, calm, and legible. Decorative treatments are reserved for brand-forward surfaces such as authentication and onboarding; the core product remains utilitarian. Dark is the default medium; the page canvas is `--color-page`. Light mode is implemented through semantic token overrides and the theme provider (with intentionally forced-dark authentication paths). Check shared surfaces in both themes rather than hard-coding dark contrast. Color carries **meaning before brand**. A danger badge is red because the action is destructive, not because red is the accent. A project-colored button signals project scope, not visual variety. Designers pick intent; hex values follow. Depth is drawn primarily with borders and surface tones; the shared v3 `Card` adds a subtle `shadow-xs`. Motion is restrained and clarifies state changes; control and collapse transitions often use 200ms, while overlays own their entry/exit timing. Respect reduced-motion behavior rather than assuming all animations share one implementation. Secret values are masked by default; revealing one is an intentional act. **Key characteristics:** - Dark default with light semantic overrides; `--color-page` page canvas - Semantic-first color (danger / success / warning / info / neutral) - Scope- and product-aware (org / sub-org / project / admin / PAM / Agent Vault) - Border-first depth with the shared `Card`'s `shadow-xs`; overlapping labels retain the shared Badge styling - IBM Plex Sans for product UI, Alliance No. 2 for headings, and JetBrains Mono for monospaced content - Secrets masked by default; reveal is an act ## 2. Color Palette & Roles All colors are defined as CSS custom properties in [`frontend/src/index.css`](frontend/src/index.css) and consumed via Tailwind v4 utilities (`bg-org`, `text-danger`, etc.). Never introduce a hex that is not in this file. ### Scope colors (hierarchy) Used to signal the scope a surface, badge, or action belongs to. | Scope | Token | | ---------------- | ----------------- | | Organization | `--color-org` | | Sub-Organization | `--color-sub-org` | | Project | `--color-project` | | Admin | `--color-admin` | PAM, Agent Vault and Secret Scanning are product identities rather than additional levels in the org/project hierarchy. Use `--color-product-pam`, `--color-product-av` and `--color-product-ss` when the current product calls for them; `PageHeader` and the org sidebar map those project types to these accents, while `Button` exposes `pam`, `av` and `ss` variants. Check each component's supported variants instead of treating all scope and product variants as interchangeable. ### Semantic colors | Intent | Token | Use | | ------- | ----------------- | -------------------------------------------- | | Success | `--color-success` | Healthy states, completed rotations | | Info | `--color-info` | Informational states, external documentation | | Warning | `--color-warning` | Attention-warranting states, stale items | | Danger | `--color-danger` | Destructive actions, errors, expired access | | Neutral | `--color-neutral` | Disabled, muted, "empty" states | ### Surface & chrome | Role | Token | | ----------------- | ------------------------- | | Page canvas | `--color-page` | | Background | `--color-background` | | Foreground text | `--color-foreground` | | Card surface | `--color-card` | | Popover / Sheet | `--color-popover` | | Container | `--color-container` | | Container (hover) | `--color-container-hover` | | Border | `--color-border` | | Focus ring | `--color-ring` | | Accent text | `--color-accent` | | Muted text | `--color-muted` | | Label text | `--color-label` | ### Product-area accents (secret-manager) Reserved for resource types in the secret management product: `--color-folder`, `--color-secret`, `--color-dynamic-secret`, `--color-import`, `--color-secret-rotation`, `--color-override`, `--color-proxied-service`. Do not repurpose these for generic UI. ### Variant treatments Choose the component variant by semantic intent or actual scope/product, not by a preferred hue. Named semantic and scope tints commonly use these recipes: - **Badge** — `bg-/15 border-/10 text-`, hover `bg-/35` (see [`Badge.tsx`](frontend/src/components/v3/generic/Badge/Badge.tsx)) - **Button** — `bg-/10 border-/25 text-foreground`, hover `bg-/15 border-/30` (see [`Button.tsx`](frontend/src/components/v3/generic/Button/Button.tsx)) `Button` product variants (`pam`, `av`, `product`) use `/25` backgrounds and `/30` borders. Neutral/default, outline, ghost, shaded, and link treatments have their own contracts; the tint recipes are not universal. Read the source for exact classes and supported variants. ## 3. Typography Typography uses three intentional families: - **IBM Plex Sans** (`font-sans`, `--font-sans`) is the default product UI face. Body copy, forms, tables, buttons, labels, and navigation inherit it. Real 400, 500, 600, and 700 weights are bundled for UI hierarchy. - **Alliance No. 2** (`font-alliance`, `--font-alliance`) is the heading face. Semantic headings and shared page, card, dialog, alert dialog, and sheet titles use its bundled Regular weight. Do not use it for arbitrary bold body text or request synthetic heavier weights. Product and project tile names are titles, not descriptions. Use `CardTitle` for them. Do not override semantic headings or shared titles with medium, semibold, or bold weights; express hierarchy with size and spacing instead. - **JetBrains Mono** (`font-mono`, `--font-mono`) is the functional and decorative mono. Use it for code, secret values, identifiers, timestamps, logs, and short technical microcopy. | Role | Class | Notes | | ----------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------ | | Page title (h1) | `font-alliance text-2xl font-normal underline underline-offset-4 decoration-/90` | In `PageHeader`; scope icon (size 26) sits inline before the title | | Page description | `text-label` | Sits under the title, separated by the header's `gap-2` | | Card title | `font-alliance text-lg font-normal leading-none` | `flex gap-1.5` so badges can sit inline | | Card description | `text-sm text-accent` | | | Body | `text-sm` | Default for table cells, form values, dialog content | | Label / meta | `text-xs text-accent` | Field labels, table column captions, metadata | | Field helper / feedback | `text-2xs leading-snug` | Defaults via `FieldDescription` and `FieldFeedback` | | Badge | `text-xs` (auto, via `Badge`) | Never override | | Button | `text-sm` (md/sm/lg), `text-xs` (xs) | Auto via `Button` sizing | On brand-forward surfaces, a short phrase may use the `text-highlight` utility to create the solid project-yellow text block. Keep the phrase short, use it as typographic emphasis rather than status or scope, and do not apply it inside routine product UI. Sentence case for descriptions, helper text, and empty states. Title Case for page titles, card titles, dialog titles, sheet titles, button labels, badge labels, and dropdown menu items. See §8 for voice rules on copy itself. ## 4. Component Stylings Use v3 components from [`frontend/src/components/v3/`](frontend/src/components/v3) for all UI. A deprecated API carries an `@deprecated` tag that names its replacement. For exact tokens, classes, and supported variants, read component source and the story when present; stories may not enumerate every implemented variant. ### Reading the stories Where present, stories may use these conventions: - **`Variant: X`** stories — one per prop-driven variant (e.g. `Variant: Outline`). - **`Example: X`** stories — composition recipes (e.g. `Example: With Header`, `Example: Inside Card / Sheet / Dialog`). - Story descriptions or component-level metadata may explain when to use a composition. Use the closest example as a starting point, not a mandatory composition. Some stories use other names or omit descriptions; check component props, exports, and representative consumers when guidance is absent. Run Storybook with `cd frontend && npm run storybook` (port 6006) to preview. ### Component inventory Use these tables to find the component for a given intent. For props, variants, sizes, and class lists, open the source and any available story. The [generic](frontend/src/components/v3/generic/index.ts) and [platform](frontend/src/components/v3/platform/index.ts) barrels also expose specialized primitives not listed here (for example `CodeBlock`, `CopyButton`, `RadioGroup`, `TagsInput`, `FileDropzone`, `DurationInput`, `ColorPicker`). #### Actions | Component | Reach for this when… | | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`Button`](frontend/src/components/v3/generic/Button/Button.tsx) | A text-bearing button — primary or secondary action. | | [`ButtonBadge`](frontend/src/components/v3/generic/Button/ButtonBadge.tsx) | A short decorative flag attached to a Button corner—such as "Last Used", "New", or "Recommended". It inherits Badge typography, sizing, radius, border, and casing; only its corner positioning and solidified semantic tint are specialized. | | [`IconButton`](frontend/src/components/v3/generic/IconButton/IconButton.tsx) | A square icon-only button — toolbars, row actions, compact triggers. Always `aria-label`. | | [`ButtonGroup`](frontend/src/components/v3/generic/ButtonGroup/ButtonGroup.tsx) | Visually join related controls — toolbars, segmented controls, split buttons, key-value chips. | | [`Dropdown`](frontend/src/components/v3/generic/Dropdown/Dropdown.tsx) | An action menu — overflow `⋯`, split-button alternates, contextual lists. | #### Forms | Component | Reach for this when… | | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | [`Field`](frontend/src/components/v3/generic/Field/Field.tsx) | Compose labeled or validated form controls with the slots they need. `FieldContent`, description, and error are optional; a standalone filter/search control need not be a form field. | | [`Label`](frontend/src/components/v3/generic/Label/Label.tsx) | Standalone form label outside a `Field`. | | [`Input`](frontend/src/components/v3/generic/Input/Input.tsx) / [`TextArea`](frontend/src/components/v3/generic/TextArea/TextArea.tsx) | Single-line / multi-line text entry. | | [`InputGroup`](frontend/src/components/v3/generic/InputGroup/InputGroup.tsx) | Input with left/right addons — search bars, prefixed values. | | [`Select`](frontend/src/components/v3/generic/Select/Select.tsx) | Non-searchable single-select with a short, known option list. | | [`Combobox`](frontend/src/components/v3/generic/Combobox/Combobox.tsx) | Searchable single- or multi-select, including grouped/rich rows and inline or dialog creation when its contract fits. | | [`Toggle`](frontend/src/components/v3/generic/Toggle/Toggle.tsx) / [`Checkbox`](frontend/src/components/v3/generic/Checkbox/Checkbox.tsx) | Boolean toggle / multi-select boolean. | | [`Calendar`](frontend/src/components/v3/generic/Calendar/Calendar.tsx) | Date / multi-date / range picker primitive. | | [`DateRangeFilter`](frontend/src/components/v3/platform/DateRangeFilter/DateRangeFilter.tsx) | Platform date-range filter with presets — for filter bars. | | [`SecretInput`](frontend/src/components/v3/platform/SecretInput/SecretInput.tsx) | Platform secret-value editor with masking and `${var}` highlighting; callers own any reveal toggle. | | [`PasswordGenerator`](frontend/src/components/v3/platform/PasswordGenerator/PasswordGenerator.tsx) | Platform password generator against project secret-validation rules. | | [`EmailInput`](frontend/src/components/v3/platform/EmailInput/EmailInput.tsx) | Platform chip field for one or more free-text email addresses. Validate it with `emailListSchema` from `@app/helpers/email`; check single addresses or picker-created invitees with `isValidEmail` rather than a local regex or `z.string().email()`. | #### Containers & overlays | Component | Reach for this when… | | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | [`Card`](frontend/src/components/v3/generic/Card/Card.tsx) | Default section container — tables, filters, forms, empty states all live in a Card. | | [`Sheet`](frontend/src/components/v3/generic/Sheet/Sheet.tsx) | Panel for larger forms or workspaces; right is the usual side. Choose `size="form"`, `"wide"`, or `"workspace"` by content, using the [size examples](frontend/src/components/v3/generic/Sheet/Sheet.stories.tsx); other sides serve specific flows. | | [`Dialog`](frontend/src/components/v3/generic/Dialog/Dialog.tsx) | Centered modal — **use for small create/edit forms** (1–2 fields, single confirmation prompt) and short interactive prompts. | | [`AlertDialog`](frontend/src/components/v3/generic/AlertDialog/AlertDialog.tsx) | Confirm an action (destructive included). Replaces `confirm()`. | | [`Popover`](frontend/src/components/v3/generic/Popover/Popover.tsx) | Anchored floating panel — filters, pickers, contextual UI. | | [`Tooltip`](frontend/src/components/v3/generic/Tooltip/Tooltip.tsx) | Small floating annotation on hover/focus. | | [`Accordion`](frontend/src/components/v3/generic/Accordion/Accordion.tsx) | Collapsible sections. | #### Data display | Component | Reach for this when… | | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | [`Table`](frontend/src/components/v3/generic/Table/Table.tsx) | Read-mostly list of records with sortable columns. Pair with `Empty` + `Pagination`. | | [`DataGrid`](frontend/src/components/v3/generic/DataGrid/data-grid.tsx) | Editable spreadsheet-style grid — copy/paste, multi-cell selection, keyboard nav. Use only when `Table` isn't enough. | | [`Pagination`](frontend/src/components/v3/generic/Pagination/Pagination.tsx) | Page controls under a Table or list. | | [`Item`](frontend/src/components/v3/generic/Item/Item.tsx) | Vertically-stacked list rows with shared spacing — when a `Table` is too heavy. | | [`Detail`](frontend/src/components/v3/generic/Detail/Detail.tsx) | Read-only label/value pairs in a detail view. | | [`Badge`](frontend/src/components/v3/generic/Badge/Badge.tsx) | Small label or chip — status, scope tag, key/value pair. | | [`SelectedActionBar`](frontend/src/components/v3/generic/SelectedActionBar/SelectedActionBar.tsx) | Floating batch actions for a selected set of records. | #### Navigation & search | Component | Reach for this when… | | ---------------------------------------------------------------------------- | ------------------------------------------------- | | [`Sidebar`](frontend/src/components/v3/generic/Sidebar/Sidebar.tsx) | Scope-aware product navigation panel. | | [`Breadcrumb`](frontend/src/components/v3/generic/Breadcrumb/Breadcrumb.tsx) | Hierarchical location trail at the top of a page. | | [`Command`](frontend/src/components/v3/generic/Command/Command.tsx) | Search-driven command palette / typeahead list. | | [`Tabs`](frontend/src/components/v3/generic/Tabs/Tabs.tsx) | Switch between related views in the same context. | | [`Stepper`](frontend/src/components/v3/generic/Stepper/Stepper.tsx) | Show progress through a staged workflow. | #### Feedback & loading | Component | Reach for this when… | | ----------------------------------------------------------------------------- | ---------------------------------------------------------------- | | [`Alert`](frontend/src/components/v3/generic/Alert/Alert.tsx) | Inline message banner inside a page or Card. | | [`Toast`](frontend/src/components/v3/generic/Toast/Toast.tsx) | Transient post-action feedback. Replaces `alert()`. | | [`Empty`](frontend/src/components/v3/generic/Empty/Empty.tsx) | Zero-state placeholder — pair with Table, list, or empty filter. | | [`Skeleton`](frontend/src/components/v3/generic/Skeleton/Skeleton.tsx) | Shimmer placeholder while data is loading. | | [`Spinner`](frontend/src/components/v3/generic/Spinner/Spinner.tsx) | Project-accented spinner by default; choose `xs`/`sm` for compact waits. | | [`Loader`](frontend/src/components/v3/generic/Loader/Loader.tsx) | Branded loading animation — pending controls and page waits. | | [`PageLoader`](frontend/src/components/v3/platform/PageLoader/PageLoader.tsx) | Centered Lottie spinner for full-page loading. | **Reduced motion.** When the operating system requests reduced motion, `Loader` and `PageLoader` hold the animation on its first frame instead of looping. The mark stays fully visible either way, because these Lotties animate only a trim-path offset over an always-drawn mark. `role="status"` is present in both states, so the wait is still announced. #### Atoms & domain | Component | Reach for this when… | | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | [`Separator`](frontend/src/components/v3/generic/Separator/Separator.tsx) | Horizontal/vertical divider. | | [`AccessRestrictedNotice`](frontend/src/components/v3/platform/AccessRestricted/AccessRestrictedNotice.tsx) | Inline restriction for a section. | | [`AccessRestrictedDialog`](frontend/src/components/v3/platform/AccessRestricted/AccessRestrictedDialog.tsx) | Page/tab access gate rendered in document flow, despite its name. | | [`ScopeIcons`](frontend/src/components/v3/platform/ScopeIcons.tsx) | `OrgIcon` / `SubOrgIcon` / `ProjectIcon` / `InstanceIcon` — use when intent is scope. | | [`SecretManagerResources`](frontend/src/components/v3/platform/SecretManagerResources/SecretManagerResources.ts) | Canonical Secret Manager resource catalog (icon, color classes, name, slug, permission subject). | | [`ProjectPermissionSubjects`](frontend/src/components/v3/platform/ProjectPermissionSubjects/ProjectPermissionSubjects.ts) | Icon and tile color for every project policy subject (`ProjectPermissionSub`). | | [`PageHeader`](frontend/src/components/v3/platform/PageHeader/PageHeader.tsx) | Canonical full-width page heading with scope semantics, description, and page actions. | | [`DocumentationLinkBadge`](frontend/src/components/v3/platform/DocumentationLinkBadge/DocumentationLinkBadge.tsx) | Inline "Documentation" link badge in `CardTitle`. | **Icons** — [`lucide-react`](https://lucide.dev). Sizing is bound by the host component; don't override unless necessary. ## 5. Layout Principles - **Page container** — `max-w-8xl` (88rem) centered, `bg-page`. - **Page header** — v3 `PageHeader` with scope/product icon + underlined `h1` + description. Import it from `@app/components/v3` and set `scope` to `org`, `namespace`, `instance`, or the applicable `ProjectType`. See [`PageHeader.tsx`](frontend/src/components/v3/platform/PageHeader/PageHeader.tsx). - **Header boundary** — `PageHeader` owns the normal 40px bottom margin (`mb-10`). Do not repeat that margin at ordinary callers or add a parent row gap between the header and body. Keep independent card/section spacing in the body layout. Use `className` for genuine compact/responsive cases, such as `mb-6 md:mb-10` for Overview; see the header's spacing stories. - **Section** — one `Card` per logical section. Title + optional `DocumentationLinkBadge` in `CardHeader`; primary action in `CardAction` (top-right). - **Tables inside Cards** — filters and search sit in the `CardHeader` above the table; pagination sits in the `CardFooter` or bottom of `CardContent`. **Empty state** — when the table has no rows (and isn't loading), hide the `Table` entirely and render `Empty` in its place; never leave a column header floating above a blank body. Add `className="border"` to `Empty` whenever it's nested in a `Card`, `Sheet`, or `Dialog` so the dashed frame is visible against the parent surface (the component ships dashed-but-borderless on purpose for page-level use). - **Forms inside Sheets/Dialog** — create / edit flows open in a Sheet or Dialog, never inline, never as a full-page route. **Pick by form size:** small forms (1–2 fields, e.g. "Add domain", "Rename") go in a centered `Dialog`; large or multi-step forms (multiple fields, scrollable detail, file uploads, wizard steps) go in a right-side `Sheet`. When in doubt, default to Dialog — Sheet is for cases where Dialog feels cramped. - **Authentication form stacks** — use `CardContent className="flex flex-col gap-4"` for the major vertical rhythm between fields, validation groups, and primary actions. Use a nested `flex flex-col gap-2` only for compact sets of equivalent actions such as social or SSO provider buttons. Use `FieldSeparator` between alternate authentication paths. Do not recreate sibling spacing with `mt-*`, `mb-*`, or `py-*`; keep a wrapper only when it owns animation, responsive visibility, conditional grouping, or tightly coupled control/error content. - **Spacing rhythm** — `gap-1.5` (intra-element), `gap-2 / gap-3` (adjacent elements), `p-4 / p-5` (section padding). Card = `p-5 gap-5`; Sheet header/footer = `p-4`. - **Sheet and Dialog footers** — actions sit right-aligned with the primary action last: `SheetFooter className="justify-end border-t"` (`DialogFooter` is right-aligned by default), then Cancel (`ghost`), then the primary button. Back navigation in a multi-step flow sits on the left with `mr-auto`, and wizard footers with status text on the left use `justify-between`. A single full-width action needs no alignment class. ## 6. Depth & Elevation Depth is conveyed primarily by layered surface tones and borders. The shared v3 `Card` includes `shadow-xs` by default; stronger shadows are reserved for elements that float (Popover, DropdownMenu, Sheet, SelectedActionBar). | Layer | Surface | Border | | --------------- | ---------------------------------------- | ----------------------------------- | | Page | `bg-background` | — | | Card | `bg-card` | `border-border` | | Popover / Dropdown main menu | `bg-popover` | `border-border` + `shadow-md` | | Dropdown submenu | `bg-popover` | `border-border` + `shadow-lg` | | Sheet | `bg-popover` | `border-border` + `shadow-lg` | | Floating bar | `bg-popover` | `border-border` + `shadow-floating` | | Row hover | `bg-container-hover` | — | | Focus | — | Component-owned focus-visible treatment (Button/IconButton use `ring-2`; default Input uses `ring-[3px]`) | | Disabled | `opacity-50 / 75`, `pointer-events-none` | — | Do not add a shadow to a Card beyond its shared `shadow-xs` default, or to a Table row, standalone Badge, or `ButtonBadge`. An overlapping `ButtonBadge` uses its solidified semantic tint—not custom content styling or elevation—to remain legible across the control edge. ## 7. Do's and Don'ts - **DO** choose Badge and Button variants by **intent** (danger / success / warning / info / neutral), not by color preference. - **DO** use scope colors (`org`, `sub-org`, `project`, `admin`) to reinforce hierarchy, and product colors (`pam`, `av`) for their own product surfaces. - **DO** mask secret values by default. Reveal must be an explicit user action and should be logged. - **DO** put large create / edit forms in a right-side Sheet; smaller forms can be in Dialogs. - **DO** pair destructive confirmations with the resource name and the consequence (see §9). - **DO** cite tokens (`bg-card`) over hex (`#xxxxxx`) in new code. - **DON'T** add ad hoc box-shadows as a depth cue — borders and surface tones do that work. The shared `Card` already has `shadow-xs`; floating elements (Popover, DropdownMenu, Sheet, SelectedActionBar) include stronger shadows. - **DON'T** invent new colors. If it isn't in `index.css` `@theme`, it doesn't belong. - **DON'T** use `project` yellow, `org` blue, or `sub-org` green as generic accents. They are scope signals; repurposing them creates false hierarchy. The documented brand-forward `text-highlight` treatment and deliberately branded `ButtonBadge` flags are narrow exceptions for authentication and onboarding surfaces. - **DON'T** mix font families arbitrarily. Use IBM Plex Sans for UI and body text, Alliance Regular for headings, and JetBrains Mono (`font-mono`) for monospaced content. - **DON'T** animate for decoration. Motion should clarify state change only. ## 8. Voice & Content Tone Copy should read as if written by an engineer for another engineer: direct, technical, specific. The domain is serious — secrets, access, compliance — and the voice reflects that. ### Stance - Direct. Active voice. Lead with the subject: "Delete this role" — not "This role will be deleted". - Specific. Name the resource, the action, the consequence. Avoid vague verbs ("handle", "manage") when a precise verb exists (`rotate`, `revoke`, `import`). - Calm. No exclamation marks. No second-person cheer ("Awesome!", "You're all set!"). No emoji. - Honest. Never claim speed, power, or ease in UI copy ("seamless", "powerful", "blazing-fast"). Those belong on the marketing site, not here. ### Shapes - **Labels & buttons** — Title Case, imperative: "Add Secret", "Revoke Access", "Rotate Key". - **Title Case applies to** — buttons, badges, dropdown menu items, card titles, dialog titles, sheet titles, page titles, tab labels, table column headers. Anything that names a thing or an action is Title Case. - **Descriptions & helper text** — sentence case, one short sentence. - **Empty states** — state what's missing, then the next action: "No secrets yet. Add your first secret to get started." - **Errors** — name the failure and the remedy. Never "Something went wrong": "Could not rotate secret — token lacks `secrets:write` permission." - **Destructive confirmation** — name the resource and the consequence "Delete "API_KEY" — this cannot be undone." - **Success toasts** — past tense, specific: "Secret "API_KEY" created". ### Secrets & sensitive values Never include a secret's value in any user-visible copy — UI, logs, toasts, errors, audit trails, or analytics. Refer to secrets by key only. Mask tokens and keys in screenshots and docs as well. ### Documentation links Use `DocumentationLinkBadge` (info variant, external-link icon). Label it "Documentation" — not "Learn more", "Read docs", "See more". ## 9. Agent Prompt Guide Pasteable prompt fragments for AI coding agents producing new UI. **Before generating UI for any component:** 1. Find the export in the v3 generic or platform barrel and read its source. 2. If a sibling story exists, consult its examples and use-when guidance. 3. Pick a supported variant by intent and current scope/product, not color preference. **Adding a section to an existing page:** > Wrap the section in a `Card` from `@app/components/v3`. Use `CardHeader` > with `CardTitle` + optional `CardDescription` + `CardAction` for the > top-right primary button (the variant for the current scope or product, > when supported). Put the table or content in `CardContent`. **A new create/edit form:** > Pick the container by form size. **Small forms (1–2 fields, e.g. "Add > domain", "Rename"):** centered `Dialog` (`Dialog`, `DialogContent`, > `DialogHeader` with `DialogTitle` + `DialogDescription`, `DialogFooter` > with the action buttons). **Large or multi-step forms (many fields, > scrollable detail, wizards):** right-side `Sheet` (`Sheet`, `SheetContent`, > `SheetHeader` with `SheetTitle` + `SheetDescription`, `SheetFooter` with > the action buttons). Use `react-hook-form` with a Zod resolver in both > cases. Compose labeled/validated inputs in `Field` with `FieldLabel` and > only the description, content layout, or feedback slots needed. Primary > button variant follows the current scope/product (`project`, `org`, > `sub-org`, `pam`, or `av`, when supported); cancel is `ghost`. **A status indicator:** > Use `Badge` from `@app/components/v3`. Pick the variant by intent: > `danger` for errors or expired access, `warning` for stale or > attention-warranting, `success` for healthy / completed, `info` for > informational, `neutral` for disabled / empty, `project` / `org` / > `sub-org` for scope references. Include a matching Lucide icon as the > first child. **A destructive confirmation:** > Use `AlertDialog`. Title: "Delete ``". Description: one > sentence naming the consequence, ending with "This cannot be undone." > Confirm button is variant `danger`. Cancel button is variant `outline`. **Unsaved changes:** > Use `DiscardChangesAlertDialog` with `useDiscardChangesGuard`. Title: > "Discard Changes?". Description names what will be lost. Confirm is > Discard (`danger`); cancel is Keep Editing. Overlay editors intercept > close with `requestDiscard`. Do not use `window.confirm`. **A documentation link in a section:** > Use `DocumentationLinkBadge` from `@app/components/v3/platform`. Place it > in the `CardTitle` next to the section name. **Refer to:** - [`Badge.stories.tsx`](frontend/src/components/v3/generic/Badge/Badge.stories.tsx) — canonical semantic reference for variant choice. - [`OverviewPage`](frontend/src/pages/secret-manager/OverviewPage) — full-page composition reference (v3 PageHeader, Card-with-table, Create Secret Sheet, filters, DropdownMenu + ButtonGroup). - [`AccessControlPage`](frontend/src/pages/project/AccessControlPage) — full-page reference (permission-gated actions, `DocumentationLinkBadge`, role badges with `ClockAlertIcon` for expired access). - §8 above for any user-visible copy. ## Appendix: Iteration Guide 1. **Run Storybook** — `cd frontend && npm run storybook` (port 6006). Open Badge, Button, Card, Table, Sheet first. 2. **Read the two reference pages** — `OverviewPage` and `AccessControlPage` render the full v3 vocabulary in production. 3. **Tokens live in `index.css`** — `@theme` blocks and theme overrides. Never introduce a hex that is not here. 4. **Adding a variant** — extend the `cva()` block in the component and add a story when appropriate. Match the existing variant family rather than imposing a tint recipe on neutral, outline, or product variants. 5. **Use v3.** Don't reach for an API marked `@deprecated`; use the replacement it names. 6. **Before merging** — `make reviewable-ui` (lint + type-check). 7. **When in doubt** — mirror `OverviewPage`.