# Settings List

Arrange labeled settings and their controls in a shared card.

Project settings

[Complete TSX file](/examples/settings-list/default.tsx)

```tsx
"use client";

import {
  Select,
  SelectPopup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/siteplane/components/select";
import {
  SettingsList,
  SettingsListItem,
} from "@/siteplane/components/settings-list";
import { Switch } from "@/siteplane/components/switch";

const visibilityOptions = [
  { label: "Workspace", value: "workspace" },
  { label: "Private", value: "private" },
];

export default function Example() {
  return (
    <SettingsList
      className="max-w-md"
      description="Manage visibility and notifications for your project."
      title="Project settings"
    >
      <SettingsListItem
        control={(controlProps) => (
          <Select defaultValue="workspace" items={visibilityOptions}>
            <SelectTrigger className="w-40" {...controlProps}>
              <SelectValue />
            </SelectTrigger>
            <SelectPopup>
              {visibilityOptions.map(({ label, value }) => (
                <SelectItem key={value} value={value}>
                  {label}
                </SelectItem>
              ))}
            </SelectPopup>
          </Select>
        )}
        description="Choose who can access this project."
        title="Visibility"
      />
      <SettingsListItem
        control={(controlProps) => <Switch {...controlProps} defaultChecked />}
        description="Receive updates about activity in this project."
        layout="inline"
        title="Email notifications"
      />
      <SettingsListItem
        control={(controlProps) => <Switch {...controlProps} />}
        description="A summary of project activity every Monday."
        layout="inline"
        title="Weekly digest"
      />
    </SettingsList>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/settings-list
```

With npm: `npx shadcn@4.18.0 add @siteplane/settings-list`. [Registry JSON](/registry/settings-list.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/badge @siteplane/input @siteplane/select @siteplane/switch
```

## Usage

```tsx
import {
  SettingsList,
  SettingsListItem,
} from "@/siteplane/components/settings-list";
import { Switch } from "@/siteplane/components/switch";

<SettingsList title="Preferences">
  <SettingsListItem
    title="Email updates"
    control={(props) => <Switch {...props} />}
  />
</SettingsList>;
```

## Composition

```text
SettingsList
└── SettingsListItem
    └── control(props) → Switch, Input or another public control
```

## Controlled values

Keep the values in your application state and spread every prop passed to `control`, so the title and description stay associated with the control. An action button, such as Change or Manage, keeps its own text as its name, so pass it only `aria-describedby`. `tag` adds a badge beside the title, and `layout="inline"` keeps a short control next to its label in a narrow list.

Controlled profile settings

[Complete TSX file](/examples/settings-list/controlled.tsx)

```tsx
"use client";

import { useState } from "react";
import { Badge } from "@/siteplane/components/badge";
import { Input } from "@/siteplane/components/input";
import {
  SettingsList,
  SettingsListItem,
} from "@/siteplane/components/settings-list";
import { Switch } from "@/siteplane/components/switch";

export default function Example() {
  const [name, setName] = useState("Mara");
  const [productUpdates, setProductUpdates] = useState(true);
  return (
    <SettingsList
      description="How other members see you in this workspace."
      title="Profile"
    >
      <SettingsListItem
        control={(props) => (
          <Input {...props} onValueChange={setName} value={name} />
        )}
        title="Display name"
      />
      <SettingsListItem
        control={(props) => (
          <Switch
            {...props}
            checked={productUpdates}
            onCheckedChange={setProductUpdates}
          />
        )}
        description="Occasional news about new features."
        layout="inline"
        title="Product updates"
      />
      <SettingsListItem
        control={(props) => <Switch {...props} defaultChecked disabled />}
        description="Your organization manages sign-in for this workspace."
        layout="inline"
        tag={<Badge variant="outline">Managed</Badge>}
        title="Single sign-on"
      />
    </SettingsList>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| SettingsList.title | ReactNode | required | Section heading, an h2 unless titleLevel sets another level. |
| SettingsList.titleLevel | 1 \| 2 \| 3 \| 4 \| 5 \| 6 | 2 | Heading level of the title, with the same look at every level. A list inside a section of a settings page takes the next level. |
| SettingsList.description | ReactNode | unset | Section help text. |
| SettingsListItem.title | ReactNode | required | Accessible control label. |
| SettingsListItem.description | ReactNode | unset | Associated description. |
| SettingsListItem.control | (props: SettingsListControlProps) => ReactNode | required | Renders the actual control with generated aria-labelledby and optional aria-describedby. An action button takes only aria-describedby, so it keeps its own text as its name. |
| SettingsListItem.layout | inline \| responsive | responsive | Responsive stacks until the list is 36rem (576 px) wide; inline keeps label and control together. |
| SettingsListItem.tag | ReactNode | unset | Supplementary tag beside the title. |
SettingsListProps extends native section props without native title.
SettingsListItemProps extends native div props without title/children.
SettingsListControlProps contains aria-labelledby and optional aria-describedby.
Both wrappers accept className but their types do not expose ref. The card,
separators and associations are provided by the component; values, request
state, validation and saving are application-owned.

[Read the installed source](/source/settings-list).

## Usage notes

Use responsive layout for wider text controls. The rows follow the width of the list, not of the screen: a responsive row puts its control beside its copy once the list is 36rem (576 px) wide, and the card padding, the header inset and the row gaps widen with it. A list beside a sidebar or in a column therefore stays stacked while it is narrow. The list is a size container named `settings-list`, so a control can follow the same switch: `className="w-full @min-[36rem]/settings-list:w-80"` fills a stacked row and takes 320 px beside the copy. Do not replace control props with hard-coded labels that break the generated association. The list itself is not a form and does not submit values.
