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.
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.
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.
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.
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 (viachildren), 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.
Related
- 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.