SettingsCard

A row for a settings page — label, supporting copy, and a right-aligned control that edits a single preference.

SettingsCard is a row-shaped container for a single preference. It pairs a short label and one or two lines of supporting copy with a control pinned to the right edge: an <Input> for free-form values, a <Button> for actions, a switch or select for discrete choices. It is the building block of the settings pages in the dashboard.

Reach for plain <Card> when you’re grouping a cluster of related content (a title, a body, maybe a footer row of actions) into a bordered surface. Reach for SettingsCard when each row is a preference: one label, one description, one control, stacked vertically with other rows into a settings list. Rows inside a <SettingCardGroup> share a single border and are divided by hairlines, which is the visual shape product settings pages want.

Import

import {
  SettingCard,
  SettingCardGroup,
  SettingsDangerZone,
  SettingsZone,
  SettingsZoneRow,
  SettingsShell,
} from "@unkey/ui";

Note that the primary row component is exported as SettingCard (singular), while the helpers for danger zones and the page shell use the Settings prefix.

Anatomy

A single row: title on the left, description underneath it, and the control (here, an <Input>) filling the right side.

Workspace name
The display name ACME sees across the dashboard.

The control slot is children. Its width is capped by contentWidth (default w-[420px]), which keeps rows visually aligned when several SettingsCards stack.

With an icon

Pass an icon to place a small tinted square to the left of the title. It helps the reader scan a long settings page and pairs well with icons from @unkey/icons. The container is fixed at 32px square with rounded corners; style the icon itself via iconClassName or by passing a pre-styled node.

API rate limit
Maximum verifications per second before requests are dropped.

Grouped

Wrap multiple <SettingCard>s in a <SettingCardGroup> to render them as a single bordered panel with hairline dividers between rows. Inside a group, each card drops its own border and border-radius so the group owns the chrome.

Workspace name
The display name ACME sees across the dashboard.
API rate limit
Verifications per second before requests are dropped.
Root key
Used by CI to provision keys. Rotate it if it leaks.

This is the shape most dashboard settings pages take. Keep related preferences in the same group (“Workspace”, “Billing”, “Security”) and use separate groups (or a heading between them) to break the page into sections.

Expandable row

Pass an expandable node to reveal a secondary region below the row. The chevron on the right becomes interactive, clicking the row (or pressing Enter when focused) toggles the region, and the height animates via a ResizeObserver so it fits whatever content you render. Useful when the control itself is more than one field: a nested form, a list of sub-preferences, or a long explanation that shouldn’t crowd the row.

Email notifications
Configure which workspace events send email to admins.

Send email when:

  • A root key is created or rotated
  • Monthly verification quota exceeds 80%
  • A deployment fails to roll out

Set defaultExpanded to open on mount. Override chevronState to "disabled" (show a dimmed chevron, ignore clicks) or "hidden" (no chevron at all) when you want the visual shape but not the interaction.

Danger zone

<SettingsDangerZone> wraps <SettingsZoneRow>s in a red-bordered panel with a heading. Each row renders a title, a description, and a single destructive button on the right. Use it for actions that are irreversible or high-impact: transferring ownership, deleting a workspace, revoking all keys.

Danger Zone

Transfer workspace

Move ACME to a different billing owner. Existing keys keep working.

Delete workspace

Permanently deletes ACME and every key it owns. Cannot be undone.

For warning-level actions (strong, but not destructive), use <SettingsZone variant="warning" title="..."> directly. The row renders a primary button tinted with the warning color instead of the red destructive style.

Composition

The settings-card module ships six exports, each with a narrow job:

  • <SettingCard> — the row itself. Title, description, optional icon, control slot (via children), and an optional expandable region.
  • <SettingCardGroup> — groups rows into a single bordered panel with hairline dividers. Uses context internally so children know to drop their own border.
  • <SettingsZone> — the base for danger/warning panels. Renders a colored heading and a bordered container in the matching tint.
  • <SettingsDangerZone><SettingsZone variant="danger" title="Danger Zone"> preconfigured. The common case.
  • <SettingsZoneRow> — a row inside a zone. Renders its own destructive-or-warning button based on the surrounding zone’s variant (via context).
  • <SettingsShell> — a centered, width-capped <main> container for settings pages. Use it as the outermost wrapper so every settings page has the same horizontal rhythm.

Accessibility

When expandable is set and chevronState is "interactive" (the default in that case), the row becomes a clickable surface. It handles Enter on onKeyDown but does not itself carry role="button" or tabIndex. If you rely on keyboard users reaching the row, set tabIndex={0} and an appropriate role at the call site, or make the control inside the row the primary interactive target and let the row’s click-to-expand be a visual convenience.

The description can be set to truncate with truncateDescription. When truncated, the full text is available through an <InfoTooltip> on hover and focus, so the content stays reachable for assistive tech and keyboard users.

<SettingsZone> renders its title as an <h2>. Pick a heading level that fits your page hierarchy. If your settings page already has an <h1> for the page title and an <h2> above each group, the zone’s own <h2> may need restyling to read as a sibling rather than a new section.

Props

SettingCard

The row. Renders a title, a description, an optional left icon, a control slot on the right, and optionally an expandable region below.

title string | ReactNode Required

The label for the row. Keep it short, a single phrase (“Workspace name”, “API rate limit”).

description string | ReactNode Required

Supporting copy beneath the title. One or two sentences that explain what the control does.

children ReactNode Optional

The control rendered on the right. Typically an <Input>, a <Button>, a <Select>, or a switch.

icon ReactNode Optional

A small node rendered in a tinted 32px square to the left of the title. Icons from @unkey/icons are the intended fit.

iconClassName string Optional

Additional classes on the icon’s 32px square wrapper. Override the tint or shadow when a row needs emphasis.

border "top" | "middle" | "bottom" | "both" | "none" | "default" Optional Default "default"

Controls which borders are drawn when the card stands alone (outside a <SettingCardGroup>). Inside a group this is ignored; the group owns the border.

contentWidth string Optional Default "w-[420px]"

Tailwind width class for the control slot. Override to widen or narrow the right side. Keep it consistent across rows in a group so controls align.

expandable ReactNode Optional

Content revealed below the row when expanded. Passing this prop enables the expand/collapse interaction and shows an interactive chevron on the right.

defaultExpanded boolean Optional Default false

When true, the expandable region is open on mount.

chevronState "hidden" | "interactive" | "disabled" Optional

Override the chevron affordance. Defaults to "interactive" when expandable is set and "hidden" otherwise. Use "disabled" to show a dimmed chevron on a non-toggleable row.

truncateDescription boolean Optional Default false

When true, the description is truncated to one line and the full text is available via an <InfoTooltip> on hover and focus.

className string Optional

Additional Tailwind classes on the inner flex row. Merged via cn.

SettingCardGroup

Groups multiple <SettingCard>s into a single bordered panel divided by hairlines. Children drop their own border when rendered inside a group.

children ReactNode Required

One or more <SettingCard> elements.

SettingsZone

A bordered panel with a colored heading. The base for danger and warning zones.

variant "danger" | "warning" Required

Sets the heading color and border tint. danger is red, warning is amber.

title string Required

Rendered as an <h2> above the panel.

children ReactNode Required

Usually one or more <SettingsZoneRow> elements.

className string Optional

Additional Tailwind classes on the outer wrapper.

SettingsDangerZone

<SettingsZone variant="danger" title="Danger Zone"> preconfigured. Use it when the zone holds irreversible actions.

children ReactNode Required

One or more <SettingsZoneRow> elements.

className string Optional

Additional Tailwind classes on the outer wrapper.

SettingsZoneRow

A row inside a <SettingsZone> or <SettingsDangerZone>. The action button picks its variant (destructive or warning-tinted primary) from the surrounding zone’s variant via context.

title ReactNode Required

The label for the row.

description ReactNode Required

Supporting copy beneath the title.

action { label: string; onClick: () => void; loading?: boolean; disabled?: boolean; className?: string } Required

The right-aligned action. label is the button text, onClick is invoked on click, loading and disabled forward to the button, and className merges with the button’s own classes.

SettingsShell

A centered, width-capped <main> container for settings pages. Use it as the outermost wrapper so every settings page has the same horizontal rhythm and vertical breathing room.

children ReactNode Required

The page content — typically a stack of <SettingCardGroup>s and zones.

className string Optional

Additional Tailwind classes on the <main> element.

  • Card — the general-purpose bordered surface. Reach for it when the content isn’t a single preference row.
  • Button — the most common control for actions on a settings row (rotate, regenerate, transfer).
  • Alert — when a settings page needs a top-of-page notice (e.g. “This workspace is in read-only mode”), rather than a per-row indicator.