---
title: "Settings page"
description: "A settings page, finished: pass its sections as items (each with its rows), its draft’s save and the router’s unsaved-changes guard. The sections are one form that the save bar at its foot saves together; settings that apply at once need no save."
---

> Documentation Index
> Fetch the complete documentation index at: https://epidermis.getarbol.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Settings page

## A draft saved together

Change the greeting: the save bar appears. Save checks the fields first (clear the greeting to see it refuse).

```tsx
import { useState } from 'react';
import { SettingRow, SettingToggle, SettingsPage, TextField } from '@arbol-ai/ui';

const SAVED_GREETING = 'Hi, this is Clínica Norte. How can I help?';

const AgentSettings = () => {
  const [saved, setSaved] = useState({ greeting: SAVED_GREETING, endSilentCalls: true });
  const [draft, setDraft] = useState(saved);
  const [isSaved, setIsSaved] = useState(false);
  return (
    <SettingsPage
      className="px-0 md:px-0"
      description="How the front desk agent talks to callers."
      label="Agent settings"
      save={{
        idle: isSaved ? 'All changes saved.' : undefined,
        isDirty: draft.greeting !== saved.greeting || draft.endSilentCalls !== saved.endSilentCalls,
        onDiscard: () => {
          setDraft(saved);
        },
        onSave: () => {
          setSaved(draft);
          setIsSaved(true);
        },
      }}
      sections={[
        {
          id: 'greeting',
          title: 'Greeting',
          description: 'What callers hear first.',
          content: (
            <SettingRow description="Said when the agent answers." label="Greeting" layout="stacked">
              <TextField
                aria-label="Greeting"
                isRequired
                multiline
                onChange={(greeting) => {
                  setDraft({ ...draft, greeting });
                  setIsSaved(false);
                }}
                value={draft.greeting}
              />
            </SettingRow>
          ),
        },
        {
          id: 'calls',
          title: 'Calls',
          content: (
            <SettingToggle
              description="The agent hangs up after 30 seconds of silence."
              isSelected={draft.endSilentCalls}
              label="End silent calls"
              onChange={(endSilentCalls) => {
                setDraft({ ...draft, endSilentCalls });
                setIsSaved(false);
              }}
            />
          ),
        },
      ]}
      title="Agent settings"
    />
  );
};

<AgentSettings />
```

## Settings that apply at once

No save: each control applies as it changes.

```tsx
import { useState } from 'react';
import { SegmentedControl, SettingRow, SettingsPage } from '@arbol-ai/ui';

const Preferences = () => {
  const [language, setLanguage] = useState('es');
  return (
    <SettingsPage
      className="px-0 md:px-0"
      sections={[
        {
          id: 'preferences',
          title: 'Preferences',
          description: 'How this browser shows Account.',
          content: (
            <SettingRow label="Language" layout="responsive">
              <SegmentedControl
                label="Language"
                onChange={setLanguage}
                options={[
                  { label: 'Español', value: 'es' },
                  { label: 'English', value: 'en' },
                ]}
                size="sm"
                value={language}
              />
            </SettingRow>
          ),
        },
      ]}
      title="Profile"
    />
  );
};

<Preferences />
```

## An area with tabs and a preview

The area’s pages as tabs, a notice first, and what the settings change beside them.

```tsx
import { UserPlusIcon } from '@phosphor-icons/react';
import { Notice, SettingRow, SettingsPage, TextField } from '@arbol-ai/ui';

<SettingsPage
  className="px-0 md:px-0"
  preview={
    <div className="rounded-card bg-card text-muted-foreground grid place-items-center border p-8 text-sm">
      Welcome to Clínica Norte
    </div>
  }
  primaryAction={{ label: 'Invite people', icon: <UserPlusIcon />, href: '#/settings-page' }}
  sections={[
    {
      id: 'identity',
      title: 'Identity',
      description: 'How the organization appears in every app.',
      content: (
        <SettingRow label="Name" layout="stacked">
          <TextField aria-label="Name" defaultValue="Clínica Norte" />
        </SettingRow>
      ),
    },
  ]}
  summary={<Notice tone="info">Only administrators change these settings.</Notice>}
  tabs={{
    items: [
      { id: 'organization', label: 'Organization', href: '#/settings-page' },
      { id: 'team', label: 'Team', href: '#/settings-page' },
      { id: 'billing', label: 'Billing', href: '#/settings-page' },
    ],
    label: 'Settings pages',
    value: 'organization',
  }}
  title="Organization"
/>
```

## Source

The examples above are [`src/blocks/settings-page/settings-page.examples.tsx`](https://github.com/getarbol/project-arbol-ui/blob/main/src/blocks/settings-page/settings-page.examples.tsx), which the library's tests render too.

Source: https://epidermis.getarbol.com/components/blocks/settings-page/index.mdx
