Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Settings Row

A borders-only settings layout — titled sections, bordered cards, and label-plus-control rows for building account, workspace, and preference screens.

Status
stable
Since
0.1.0
Accessibility pattern
headings + labelled rows

Last updated

Email notificationsProduct updates, tips, and offers.
ThemeHow the interface looks on this device.
Two-factor authenticationRequire a code at sign-in.

Install

Add Settings Row from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/settings-row

Usage

import {
  SettingsCard,
  SettingsRow,
  SettingsSection,
} from "@/components/ui/settings-row";
import { Switch } from "@/components/ui/switch";

<SettingsSection
  title="Notifications"
  description="Choose what you want to hear about."
>
  <SettingsCard>
    <SettingsRow label="Email" description="Product updates and tips.">
      <Switch defaultChecked aria-label="Email notifications" />
    </SettingsRow>
    <SettingsRow label="SMS" description="Critical alerts only.">
      <Switch aria-label="SMS alerts" />
    </SettingsRow>
  </SettingsCard>
</SettingsSection>;

Anatomy

Settings Row is a compound component. Every exported part, with the data-slot it renders (generated from the canonical source):

SettingsCard — data-slot="settings-card"
SettingsRow — data-slot="settings-row" | "settings-row-control" | "settings-row-description" | "settings-row-label"
SettingsSection — data-slot="settings-section" | "settings-section-description" | "settings-section-header" | "settings-section-title"

Examples

Anatomy

The settings layout is three flat parts that nest from the outside in. Each is a plain, server-safe element with a forwarded ref and a data-slot for styling and targeting:

<SettingsSection title="…" description="…">
  <SettingsCard>
    <SettingsRow label="…" description="…">
      {/* control: Switch, Input, Button, badge, or read-only value */}
    </SettingsRow>
    <SettingsRow label="…">{/* control */}</SettingsRow>
  </SettingsCard>
</SettingsSection>
  • SettingsSection — a titled group (data-slot="settings-section"). Renders an optional title and description above its children. Use one section per logical group (Account, Notifications, Danger zone).
  • SettingsCard — a borders-only container (data-slot="settings-card") that groups rows into a single rounded-lg surface. It collapses the last row's bottom border so the rows read as a divided list.
  • SettingsRow — one setting (data-slot="settings-row"): a label and optional description on the left, the control children on the right, with a border-b divider. The row is its own named @container — it stacks or goes horizontal based on its own measured width, not the viewport, so a row placed in a narrow card (a settings panel in a split view, a dialog) stacks even on a wide screen, while the same row in a full-width page goes horizontal. Works standalone; no SettingsCard required.

All three are flat named exports — there is no dotted namespace.

Settings layouts

A bordered card grouping a mix of rows — a Switch, a Select, and another Switch:

Email notificationsProduct updates, tips, and offers.
ThemeHow the interface looks on this device.
Two-factor authenticationRequire a code at sign-in.

A full section with a titled header above a card of switch rows:

Notifications

Choose what you want to hear about.

EmailProduct updates and tips.
SMSCritical alerts only.
Desktop pushReal-time alerts on this device.

Native labels with controlId

Pass controlId when the row wraps a native form control with a matching id. The visual label renders as a real <label htmlFor={controlId}>, so clicking the label focuses the input and screen readers announce the pairing. Use labelProps to merge extra attributes (e.g. analytics hooks) onto the generated label.

Shown across the product.
Where customer replies are sent.

Control variety

The control slot accepts any node — Input, Switch, a read-only value, a Badge, or a Button. A row with no children renders label-only (no control slot), which suits headings such as a danger-zone intro.

Email notificationsProduct updates and tips.
Workspace IDUsed in API requests.
ws_8f3a1c
PlanYour current subscription tier.
Pro
Active sessionsSign out everywhere else.
Danger zoneDeleting the workspace is permanent and cannot be undone.

API Reference

SettingsRow

PropTypeDefaultDescription
label*React.ReactNodeThe row label rendered on the left (heading line of the row).
childrenReact.ReactNodeThe control rendered on the right — a Switch, Input, Button, badge, or read-only value.
controlIdstringID of the form control rendered in children. When provided, the visual row label renders as a real <label htmlFor={controlId}>.
descriptionReact.ReactNodeOptional supporting description rendered under the label (muted).
labelPropsReact.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>Props merged onto the generated label element when controlId is provided.

Data attributes and CSS variables on SettingsRow

AttributeValues
data-slot"settings-row" | "settings-row-control" | "settings-row-description" | "settings-row-label"

SettingsCard

SettingsCard adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

Data attributes and CSS variables on SettingsCard

AttributeValues
data-slot"settings-card"

SettingsSection

PropTypeDefaultDescription
descriptionReact.ReactNodeSupporting description rendered under the title (muted).
titleReact.ReactNodeSection heading rendered above the grouped content.
titleAsSettingsSectionTitleTag'h3'Heading element the title renders as. Pick the level that continues the page's outline — h2 directly under the page h1, h3 inside an h2 group, and so on. The visual size never changes (it is the text-h4 role either way); only the document structure does. as rather than Base UI render on purpose: useRender calls React.useRef internally, which would force 'use client' onto this file and cost the whole settings family its server-safe status for a prop that only picks a tag name.

Data attributes and CSS variables on SettingsSection

AttributeValues
data-slot"settings-section" | "settings-section-description" | "settings-section-header" | "settings-section-title"

Accessibility

  • SettingsSection renders its title as a real heading — <h3> by default, or whatever titleAs names (h2h6). A settings page nests sections at different depths, and the level is a document-structure fact the page owns: pick the one that continues the outline, so heading navigation stays meaningful. The visual size never changes. Keep one section per heading and order them logically.
  • Pass controlId when the row contains a form control with a matching id; the row label becomes a real <label htmlFor={controlId}>. For composite controls that do not expose an input id, keep an explicit aria-label or aria-labelledby on the control itself.
  • Borders-only by design: rows are separated by border-border, not shadows, so the layout stays legible in high-contrast and forced-colors modes.
KeyAction
TabMove focus through the controls in each row, top to bottom.
Enter / SpaceActivate or toggle the focused control.
ContractStates tested
Behaviourdefault, read-only
Accessibilitylabeled, semantic-html
Visualdefault

Do / Don't

Do
Pair each row with a labelled control, using controlId for native inputs and explicit ARIA labels for composite controls.
Don't
Stack bare rows without a card, or rely on visual proximity alone to name an interactive control.

On this page