Skip to content

Latest commit

 

History

History
480 lines (384 loc) · 37.4 KB

File metadata and controls

480 lines (384 loc) · 37.4 KB

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 (@theme blocks and theme overrides). Canonical semantic reference: Badge.stories.tsx. Canonical page references: OverviewPage and AccessControlPage. Component usage reference: Start with the sibling story in v3/generic or v3/platform when one exists, then check source and consumers. Some exports, including CopyButton, DataGrid, and Sidebar, have no sibling story; the generic and platform 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 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 and Agent Vault are product identities rather than additional levels in the org/project hierarchy. Use --color-product-pam and --color-product-av when the current product calls for them; PageHeader maps the PAM and Agent Vault project types to these accents, while Button exposes pam and av 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

The mineshaft-* scale (50–900) is the underlying neutral ramp; see index.css for the full list. Prefer semantic tokens (card, border, accent) over raw mineshaft values.

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-<c>/15 border-<c>/10 text-<c>, hover bg-<c>/35 (see Badge.tsx)
  • Button — bg-<c>/10 border-<c>/25 text-foreground, hover bg-<c>/15 border-<c>/30 (see 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-<scope>/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

Prefer v3 components from frontend/src/components/v3/ for new UI. The v2 library is legacy; use it for new work only when v3 does not cover the required behavior. Existing v2 consumers marked blocked in the lifecycle ledger need consumer-specific parity checks before replacement, even on mixed pages.

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 and platform barrels also expose specialized primitives not listed here (for example CodeBlock, CopyButton, RadioGroup, TagsInput, FileDropzone, DurationInput, ColorPicker).

Actions

Component Reach for this when…
Button A text-bearing button — primary or secondary action.
ButtonBadge 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 A square icon-only button — toolbars, row actions, compact triggers. Always aria-label.
ButtonGroup Visually join related controls — toolbars, segmented controls, split buttons, key-value chips.
Dropdown An action menu — overflow ⋯, split-button alternates, contextual lists.

Forms

Component Reach for this when…
Field 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 Standalone form label outside a Field.
Input / TextArea Single-line / multi-line text entry.
InputGroup Input with left/right addons — search bars, prefixed values.
Select Non-searchable single-select with a short, known option list.
Combobox Searchable single- or multi-select, including grouped/rich rows and inline or dialog creation when its contract fits.
ReactSelect Deprecated compatibility for consumers needing react-select-specific overrides; see the lifecycle ledger.
Toggle / Checkbox Boolean toggle / multi-select boolean.
Calendar Date / multi-date / range picker primitive.
DateRangeFilter Platform date-range filter with presets — for filter bars.
SecretInput Platform secret-value editor with mask toggle and ${var} highlighting.
PasswordGenerator Platform password generator against project secret-validation rules.

Containers & overlays

Component Reach for this when…
Card Default section container — tables, filters, forms, empty states all live in a Card.
Sheet Panel for larger forms or workspaces; right is the usual side. Choose size="form", "wide", or "workspace" by content, using the size examples; other sides serve specific flows.
Dialog Centered modal — use for small create/edit forms (1–2 fields, single confirmation prompt) and short interactive prompts.
AlertDialog Confirm an action (destructive included). Replaces confirm().
Popover Anchored floating panel — filters, pickers, contextual UI.
Tooltip Small floating annotation on hover/focus.
Accordion Collapsible sections.

Data display

Component Reach for this when…
Table Read-mostly list of records with sortable columns. Pair with Empty + Pagination.
DataGrid Editable spreadsheet-style grid — copy/paste, multi-cell selection, keyboard nav. Use only when Table isn't enough.
Pagination Page controls under a Table or list.
Item Vertically-stacked list rows with shared spacing — when a Table is too heavy.
Detail Read-only label/value pairs in a detail view.
Badge Small label or chip — status, scope tag, key/value pair.
SelectedActionBar Floating batch actions for a selected set of records.

Navigation & search

Component Reach for this when…
Sidebar Scope-aware product navigation panel.
Breadcrumb Hierarchical location trail at the top of a page.
Command Search-driven command palette / typeahead list.
Tabs Switch between related views in the same context.
Stepper Show progress through a staged workflow.

Feedback & loading

Component Reach for this when…
Alert Inline message banner inside a page or Card.
Toast Transient post-action feedback. Replaces alert().
Empty Zero-state placeholder — pair with Table, list, or empty filter.
Skeleton Shimmer placeholder while data is loading.
Spinner Project-accented spinner by default; choose xs/sm for compact waits.
Loader Branded loading animation — pending controls and page waits.
PageLoader 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 Horizontal/vertical divider.
AccessRestrictedNotice Inline restriction for a section.
AccessRestrictedDialog Page/tab access gate rendered in document flow, despite its name.
ScopeIcons OrgIcon / SubOrgIcon / ProjectIcon / InstanceIcon — use when intent is scope.
SecretManagerResources Canonical Secret Manager resource catalog (icon, color classes, name, slug, permission subject).
ProjectPermissionSubjects Icon and tile color for every project policy subject (ProjectPermissionSub).
PageHeader Canonical full-width page heading with scope semantics, description, and page actions.
DocumentationLinkBadge Inline "Documentation" link badge in CardTitle.

Icons — lucide-react. 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.
  • 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.

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 replace existing blocked v2 consumers based on matching component names alone; check the lifecycle ledger and interaction parity first.
  • 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 <resource-name>". 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 — canonical semantic reference for variant choice.
  • OverviewPage — full-page composition reference (v3 PageHeader, Card-with-table, Create Secret Sheet, filters, DropdownMenu + ButtonGroup).
  • 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. Prefer v3 for new code — use v2 only where needed for behavior not covered by v3. Existing blocked v2 consumers migrate after lifecycle parity review.
  6. Before merging — make reviewable-ui (lint + type-check).
  7. When in doubt — mirror OverviewPage.