Deck
Browse the catalog

Build / Molecules

SettingRow

  • molecule
  • play-fn

One preference: what it is called, what it does, and the control that changes it.

Settings screens are mostly this row repeated, so it is a component rather than a layout each page reinvents. Consecutive rows draw their own divider, which means a group is just rows inside a Card — no wrapper element and no separator to remember.

Passing checked renders the switch and the label together, so the title is the switch's accessible name and its <label>: the words are part of the hit target, and assistive tech hears the setting once rather than twice. Reach for control for everything that is not on/off.

import { SettingRow } from '@meetcard/deck'

Example

<Card> <SettingRow title="New connection" description="When someone adds you." checked={alerts} onCheckedChange={setAlerts} /> <SettingRow title="Export my data" description="Cards and notes as JSON." control={<Button variant="secondary">Export</Button>} /> </Card>

Stories

Every preview below is the real component, rendered in this page from the same story Storybook runs. The width control resizes each preview's container; CSS media queries still follow the browser window, so breakpoint behaviour is worth checking in Storybook.

The default shape of a preference: a switch whose label is the row's own title, so the words are part of the hit target.

Toggle
Source
const Toggle = () => {
    const [on, setOn] = useState(true)

    return (
        <Card style={{ maxWidth: 560 }}>
            <SettingRow
                title="New connection"
                description="When someone adds you to their connections."
                checked={on}
                onCheckedChange={setOn} />
        </Card>
    );
};

Off.

Off
Source
const Off = () => <Card style={{ maxWidth: 560 }}>
    <SettingRow
        title="New connection"
        description="When someone adds you to their connections."
        checked={false} />
</Card>;

Not every setting is on or off. control takes whatever changes it.

With Action
Source
const WithAction = () => <Card style={{ maxWidth: 560 }}>
    <SettingRow
        title="Export my data"
        description="Download a copy of your cards, connections, and notes as JSON."
        control={(<Button variant="secondary" size="sm">Export
                </Button>)} />
</Card>;

A read-only row: the state is reported, and changing it happens elsewhere.

With Status
Source
const WithStatus = () => <Card style={{ maxWidth: 560 }}>
    <SettingRow
        title="Browser push"
        description="Push is blocked for this site and can't be reset from here."
        control={<Badge tone="error">Blocked</Badge>} />
</Card>;

A setting that reveals a follow-on choice. The detail is indented under the row it belongs to, so it reads as part of that setting rather than the next.

With Detail
Source
const WithDetail = () => {
    const [on, setOn] = useState(true)
    const [cadence, setCadence] = useState('weekly')

    return (
        <Card style={{ maxWidth: 560 }}>
            <SettingRow
                title="Follow-up reminders"
                description="Get nudged to follow up with people you've exchanged cards with."
                checked={on}
                onCheckedChange={setOn} />
            {on ? (
              <SettingRow
                title="Default cadence"
                description="How often to surface follow-up reminders."
              >
                <ChoiceGroup
                  label="Default cadence"
                  hideLabel
                  options={[
                    { value: 'daily', label: 'Daily' },
                    { value: 'weekly', label: 'Weekly' },
                    { value: 'monthly', label: 'Monthly' },
                  ]}
                  value={cadence}
                  onChange={setCadence}
                />
              </SettingRow>
            ) : null}
        </Card>
    );
};

Consecutive rows rule themselves off, so a group needs no wrapper.

Group
Source
const Group = function Render() {
  const [state, setState] = useState({ connection: true, lead: false, sync: true })
  const set = (key: keyof typeof state) => (next: boolean) =>
    setState((all) => ({ ...all, [key]: next }))

  return (
    <Card style={{ maxWidth: 560 }}>
      <SettingRow
        title="New connection"
        description="When someone adds you to their connections."
        checked={state.connection}
        onCheckedChange={set('connection')}
      />
      <SettingRow
        title="New lead"
        description="When a connection is flagged as a potential lead."
        checked={state.lead}
        onCheckedChange={set('lead')}
      />
      <SettingRow
        title="Sync failure"
        description="When a CRM sync fails and needs attention."
        checked={state.sync}
        onCheckedChange={set('sync')}
      />
    </Card>
  )
};

Disabled — a setting that exists but cannot be changed from here.

Disabled
Source
const Disabled = () => <Card style={{ maxWidth: 560 }}>
    <SettingRow
        title="New connection"
        description="When someone adds you to their connections."
        checked={false}
        disabled />
</Card>;

Props

PropTypeDefaultDescription
titlerequiredReactNode—What the setting is called. Becomes the switch's label in toggle form.
checkedboolean—Present for a toggle row: renders a Switch at the row's end whose label is this row's own title, so tapping the words works too.
childrenReactNode—Content below the row, indented under it — the follow-on choice a setting reveals, or the detail it needs to explain itself.
controlReactNode—The row's end slot when it is not a toggle — a Button, a Badge, a Select. Ignored when checked is given; a row has one control.
descriptionReactNode—What turning it on actually does.
disabledboolean——
onCheckedChange(checked: boolean) => void——

Also accepts everything from Omit, so id, data-* and event handlers pass straight through.

Open in Storybookfor controls, the a11y panel and the interaction runner.