# Settings

> A complete screen built from AppShell, Avatar, Button, IconButton and 7 more components.

Pattern · Pro kit · [Live page](https://ui.twentyfirst.media/pro/patterns/SettingsScreen/)

## Code

```tsx
import * as React from "react";
import {
  AppShell,
  Avatar,
  Button,
  Input,
  ListRow,
  PageHeader,
  Panel,
  Select,
  Stagger,
  Switch,
} from "@twentyfirstmedia/pro";
import type { IconName, Tint } from "@twentyfirstmedia/pro";

function App() {
  const sec = React.useState(0);
  const nav: [string, IconName, Tint | "plain"][] = [
    ["Profile", "user", "sky"],
    ["Workspace", "grid", "mint"],
    ["Notifications", "bell", "butter"],
    ["Security", "lock", "lilac"],
    ["Integrations", "link", "plain"],
    ["Billing", "card", "blush"],
  ];
  return (
    <AppShell
      brand="Halo"
      tag="Admin"
      user={{ name: "Mara Ellis", email: "mara@halo.app" }}
      active="Settings"
      dock={false}
    >
      <div className="tf-in">
        <PageHeader
          title="Settings"
          subtitle="Manage your profile and how Halo works for your team"
          actions={[
            <Button key="c" variant="ghost">
              Discard
            </Button>,
            <Button key="s" icon="check">
              Save changes
            </Button>,
          ]}
        />
      </div>
      <div
        className="tf-grid"
        style={
          {
            "--cols": "240px 1fr",
            gap: 24,
            alignItems: "start",
          } as React.CSSProperties
        }
      >
        <div className="tf-panel tf-panel-flush tf-panel-list">
          <Stagger className="tf-list" delay={100}>
            {nav.map((n, i) => (
              <ListRow
                key={n[0]}
                icon={n[1]}
                tint={n[2]}
                title={n[0]}
                active={sec[0] === i}
                onClick={() => {
                  sec[1](i);
                }}
              />
            ))}
          </Stagger>
        </div>
        <Stagger
          className="tf-col"
          style={{
            gap: 16,
          }}
          delay={160}
        >
          <Panel title="Profile" subtitle="Shown to everyone in the workspace">
            <div
              className="tf-row"
              style={{
                gap: 16,
              }}
            >
              <Avatar name="Mara Ellis" size={64} />
              <Button variant="secondary" size="sm">
                Upload photo
              </Button>
              <Button variant="ghost" size="sm">
                Remove
              </Button>
            </div>
            <div
              className="tf-grid"
              style={
                {
                  "--cols": "1fr 1fr",
                  gap: 16,
                } as React.CSSProperties
              }
            >
              <Input label="Full name" defaultValue="Mara Ellis" />
              <Input
                label="Email"
                defaultValue="mara@halo.app"
                hint="Used for sign-in and receipts."
              />
              <Select label="Role" options={["Owner", "Admin", "Editor", "Viewer"]} />
              <Select
                label="Time zone"
                options={[
                  "Europe/Berlin (UTC+2)",
                  "Asia/Dubai (UTC+4)",
                  "America/New_York (UTC−4)",
                ]}
              />
            </div>
          </Panel>
          <Panel title="Notifications" subtitle="Choose what reaches your inbox">
            <div
              className="tf-col"
              style={{
                gap: 16,
              }}
            >
              <Switch spread defaultChecked>
                Approvals waiting on me
              </Switch>
              <Switch spread defaultChecked>
                Invoices paid or overdue
              </Switch>
              <Switch spread>Weekly analytics digest</Switch>
              <Switch spread>Product updates from Halo</Switch>
            </div>
          </Panel>
          <div
            className="tf-card tf-bg-blush"
            style={{
              minHeight: 0,
              flexDirection: "row",
              alignItems: "center",
              justifyContent: "space-between",
            }}
          >
            <div>
              <div
                className="tf-card-title"
                style={{
                  padding: 0,
                }}
              >
                Delete workspace
              </div>
              <div className="tf-card-desc">
                Removes every project, file and invoice. This cannot be undone.
              </div>
            </div>
            <Button variant="secondary" icon="trash">
              Delete…
            </Button>
          </div>
        </Stagger>
      </div>
    </AppShell>
  );
}
export default function Preview() {
  return <App />;
}
```

## Props

### AppShell

| Prop | Type | Notes |
| --- | --- | --- |
| `active?` | `string` |  |
| `brand?` | `ReactNode` | The product name, or a node (a logo mark with the name; collapsed sidebars show it whole). |
| `tag?` | `string` |  |
| `nav?` | `NavItem[]` |  |
| `user?` | `{ name: string; email?: string }` |  |
| `onNavigate?` | `(label: string) => void` |  |
| `topExtra?` | `ReactNode` |  |
| `sheetStyle?` | `CSSProperties` |  |
| `contentKey?` | `string \| number` |  |
| `side?` | `ReactNode` |  |
| `dock?` | `boolean` |  |
| `dockItems?` | `DockProps["items"]` |  |
| `railTop?` | `RailItem[]` |  |
| `railBottom?` | `RailItem[]` |  |
| `navigation?` | `"top" \| "sidebar"` | `top` (default): navigation in the top bar, tools in the side rail. `sidebar`: a labelled sidebar on the frame, with groups, nesting and a drawer on phones. |
| `sidebarGroups?` | `SidebarGroup[]` | With `navigation="sidebar"`: groups instead of the flat `nav` list. |
| `sidebar?` | `Partial<Omit<SidebarProps, "variant" \| "groups">>` | With `navigation="sidebar"`: more sidebar props (collapsed, storageKey, shortcut, linkAs …). |
| `workspaces?` | `WorkspaceSwitcherProps` | With `navigation="sidebar"`: the workspace switcher at the top instead of the brand. |
| `userMenu?` | `(MenuItem \| "-")[]` | The account menu's entries (with `navigation="sidebar"` at the foot of the sidebar). |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Avatar

| Prop | Type | Notes |
| --- | --- | --- |
| `name` | `string` | Required |
| `src?` | `string` |  |
| `size?` | `number` |  |
| `tint?` | `Tint \| "surface-sunken"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Button

| Prop | Type | Notes |
| --- | --- | --- |
| `variant?` | `"primary" \| "secondary" \| "soft" \| "ghost" \| "danger" \| "danger-solid"` |  |
| `size?` | `"md" \| "sm"` |  |
| `icon?` | `IconName` |  |
| `iconRight?` | `IconName` |  |
| `disabled?` | `boolean` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `children?` | `ReactNode` |  |
| `asChild?` | `boolean` | Render the single child element (a link, a router Link) with the button's look instead of a <button>. |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### IconButton

| Prop | Type | Notes |
| --- | --- | --- |
| `icon` | `IconName` | Required |
| `label` | `string` | Accessible name and tooltip (`aria-label` and `title`); `aria-label` or `title` override them. |
| `variant?` | `"soft" \| "solid" \| "outline" \| "ghost"` |  |
| `size?` | `"sm" \| "md" \| "lg"` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `disabled?` | `boolean` |  |
| `asChild?` | `boolean` | Render the single child element (a link) with the button's look; the icon is placed inside it. |
| `children?` | `ReactNode` |  |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Input

| Prop | Type | Notes |
| --- | --- | --- |
| `type?` | `string` |  |
| `placeholder?` | `string` |  |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `disabled?` | `boolean` |  |
| `onChange?` | `(e: ChangeEvent<HTMLInputElement>) => void` |  |
| `label?` | `string` |  |
| `hint?` | `string` |  |
| `error?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### ListRow

| Prop | Type | Notes |
| --- | --- | --- |
| `avatar?` | `string` |  |
| `icon?` | `IconName` |  |
| `tint?` | `Tint \| "plain"` |  |
| `leading?` | `ReactNode` |  |
| `title` | `ReactNode` | Required |
| `subtitle?` | `ReactNode` |  |
| `meta?` | `ReactNode` |  |
| `trailing?` | `ReactNode` |  |
| `unread?` | `boolean` |  |
| `active?` | `boolean` |  |
| `onClick?` | `() => void` |  |
| `href?` | `string` | Makes the row a link (with `linkAs` for a router's link). |
| `linkAs?` | `ElementType` |  |
| `actions?` | `ReactNode` | Buttons of the row's own (rename, remove …), outside the clickable area so nothing interactive is nested. Without `onClick` and `href` the row is not interactive at all. |
| `titleLines?` | `number` | Lines the title may take before it ends in an ellipsis (default: it wraps freely). |
| `subtitleLines?` | `number` | Lines the subtitle may take (default 1); badges and facts wrap within them. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### PageHeader

| Prop | Type | Notes |
| --- | --- | --- |
| `crumbs?` | `(string \| Crumb)[]` | A breadcrumb above the title (see `Breadcrumb`): labels, or items with `href`/`onClick`. |
| `crumbLinkAs?` | `ElementType` | The element crumb links render as, e.g. Next.js `Link`. |
| `title` | `string` | Required |
| `subtitle?` | `string` |  |
| `actions?` | `ReactNode[]` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Panel

| Prop | Type | Notes |
| --- | --- | --- |
| `side?` | `boolean` |  |
| `title?` | `string` |  |
| `subtitle?` | `string` |  |
| `action?` | `ReactNode` |  |
| `children?` | `ReactNode` |  |
| `surface?` | `Surface` | `"glass"`: translucent glass over what is behind (maps, 3D, video, photos); `"solid"` opts out inside a glass `UIProvider`. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Select

| Prop | Type | Notes |
| --- | --- | --- |
| `options` | `string[]` | Required |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `onChange?` | `(e: ChangeEvent<HTMLSelectElement>) => void` |  |
| `label?` | `string` |  |
| `hint?` | `string` |  |
| `error?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Stagger

| Prop | Type | Notes |
| --- | --- | --- |
| `as?` | `string` |  |
| `className?` | `string` |  |
| `style?` | `CSSProperties` |  |
| `delay?` | `number` | Extra delay before the first child, in ms. |
| `reveal?` | `boolean` | Below the fold, wait until the group scrolls into view (default true). |
| `children?` | `ReactNode` |  |

### Switch

| Prop | Type | Notes |
| --- | --- | --- |
| `name?` | `string` |  |
| `checked?` | `boolean` |  |
| `defaultChecked?` | `boolean` |  |
| `onChange?` | `(e: ChangeEvent<HTMLInputElement>) => void` |  |
| `children?` | `ReactNode` |  |
| `spread?` | `boolean` |  |
| `disabled?` | `boolean` |  |
| `label?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
