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.
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-pagepage 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'sshadow-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
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.
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.
| 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 |
| 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.
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.
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>, hoverbg-<c>/35(seeBadge.tsx) - Button —
bg-<c>/10 border-<c>/25 text-foreground, hoverbg-<c>/15 border-<c>/30(seeButton.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.
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. UseCardTitlefor 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.
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.
Where present, stories may use these conventions:
Variant: Xstories — one per prop-driven variant (e.g.Variant: Outline).Example: Xstories — 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.
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).
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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.
| 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.
- Page container —
max-w-8xl(88rem) centered,bg-page. - Page header — v3
PageHeaderwith scope/product icon + underlinedh1+ description. Import it from@app/components/v3and setscopetoorg,namespace,instance, or the applicableProjectType. SeePageHeader.tsx. - Header boundary —
PageHeaderowns 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. UseclassNamefor genuine compact/responsive cases, such asmb-6 md:mb-10for Overview; see the header's spacing stories. - Section — one
Cardper logical section. Title + optionalDocumentationLinkBadgeinCardHeader; primary action inCardAction(top-right). - Tables inside Cards — filters and search sit in the
CardHeaderabove the table; pagination sits in theCardFooteror bottom ofCardContent. Empty state — when the table has no rows (and isn't loading), hide theTableentirely and renderEmptyin its place; never leave a column header floating above a blank body. AddclassName="border"toEmptywhenever it's nested in aCard,Sheet, orDialogso 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-sideSheet. 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 nestedflex flex-col gap-2only for compact sets of equivalent actions such as social or SSO provider buttons. UseFieldSeparatorbetween alternate authentication paths. Do not recreate sibling spacing withmt-*,mb-*, orpy-*; 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.
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.
- 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
Cardalready hasshadow-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
projectyellow,orgblue, orsub-orggreen as generic accents. They are scope signals; repurposing them creates false hierarchy. The documented brand-forwardtext-highlighttreatment and deliberately brandedButtonBadgeflags 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.
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.
- 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.
- 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:writepermission." - Destructive confirmation — name the resource and the consequence "Delete "API_KEY" — this cannot be undone."
- Success toasts — past tense, specific: "Secret "API_KEY" created".
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.
Use DocumentationLinkBadge (info variant, external-link icon). Label it
"Documentation" — not "Learn more", "Read docs", "See more".
Pasteable prompt fragments for AI coding agents producing new UI.
Before generating UI for any component:
- Find the export in the v3 generic or platform barrel and read its source.
- If a sibling story exists, consult its examples and use-when guidance.
- 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
Cardfrom@app/components/v3. UseCardHeaderwithCardTitle+ optionalCardDescription+CardActionfor the top-right primary button (the variant for the current scope or product, when supported). Put the table or content inCardContent.
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,DialogHeaderwithDialogTitle+DialogDescription,DialogFooterwith the action buttons). Large or multi-step forms (many fields, scrollable detail, wizards): right-sideSheet(Sheet,SheetContent,SheetHeaderwithSheetTitle+SheetDescription,SheetFooterwith the action buttons). Usereact-hook-formwith a Zod resolver in both cases. Compose labeled/validated inputs inFieldwithFieldLabeland only the description, content layout, or feedback slots needed. Primary button variant follows the current scope/product (project,org,sub-org,pam, orav, when supported); cancel isghost.
A status indicator:
Use
Badgefrom@app/components/v3. Pick the variant by intent:dangerfor errors or expired access,warningfor stale or attention-warranting,successfor healthy / completed,infofor informational,neutralfor disabled / empty,project/org/sub-orgfor 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 variantdanger. Cancel button is variantoutline.
Unsaved changes:
Use
DiscardChangesAlertDialogwithuseDiscardChangesGuard. Title: "Discard Changes?". Description names what will be lost. Confirm is Discard (danger); cancel is Keep Editing. Overlay editors intercept close withrequestDiscard. Do not usewindow.confirm.
A documentation link in a section:
Use
DocumentationLinkBadgefrom@app/components/v3/platform. Place it in theCardTitlenext 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 withClockAlertIconfor expired access).- §8 above for any user-visible copy.
- Run Storybook —
cd frontend && npm run storybook(port 6006). Open Badge, Button, Card, Table, Sheet first. - Read the two reference pages —
OverviewPageandAccessControlPagerender the full v3 vocabulary in production. - Tokens live in
index.css—@themeblocks and theme overrides. Never introduce a hex that is not here. - 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. - 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.
- Before merging —
make reviewable-ui(lint + type-check). - When in doubt — mirror
OverviewPage.