# Settings layout

Settings: grouped sections down the side and the open one beside them. Narrow (it measures its own width, so it works inside a panel too), the list becomes a select above the section. Sections the viewer can't open stay in the list with a lock and the reason, rather than disappearing.

- Registry name: `settings-layout` (`@candor/settings-layout`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/settings-layout

## Install

```bash
npx candor-ui add settings-layout
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/settings-layout`.
It brings in: locale, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { Bell, CreditCard, KeyRound, Plug, UserRound, Users } from "lucide-react"
import { SettingsLayout, SettingsSection, type SettingsGroup } from "@/components/settings-layout"

const groups: SettingsGroup[] = [
  {
    label: "You",
    items: [
      { id: "profile", label: "Profile", Icon: UserRound },
      { id: "notifications", label: "Notifications", Icon: Bell },
    ],
  },
  {
    label: "Workspace",
    items: [
      { id: "members", label: "Members", Icon: Users },
      { id: "connections", label: "Connections", Icon: Plug },
      { id: "keys", label: "API keys", Icon: KeyRound },
      { id: "billing", label: "Billing", Icon: CreditCard, locked: "Only owners can see billing" },
    ],
  },
]

const sections: Record<string, { title: string; description: string; body: string }> = {
  profile: { title: "Profile", description: "How you appear to your team.", body: "Name, avatar and time zone." },
  notifications: { title: "Notifications", description: "What Keel tells you about, and where.", body: "Approvals and failed runs always notify you." },
  members: { title: "Members", description: "Who can use Keel in Acme.", body: "8 members, 2 pending invites." },
  connections: { title: "Connections", description: "Repositories and databases the agents can reach.", body: "GitHub, Production and Staging databases." },
  keys: { title: "API keys", description: "Keys for calling Keel from CI.", body: "No keys yet." },
}

export default function Example() {
  const [active, setActive] = React.useState("connections")
  const s = sections[active]!
  return (
    <div className="flex h-[420px] overflow-hidden rounded-lg border">
      <SettingsLayout groups={groups} active={active} onSelect={setActive}>
        <SettingsSection title={s.title} description={s.description}>
          <p className="text-sm">{s.body}</p>
        </SettingsSection>
      </SettingsLayout>
    </div>
  )
}
```

## <SettingsLayout>

Props:

- `groups: SettingsGroup[]`: The sections, under their group labels; locked ones stay listed with the reason.
- `active: string`: The id of the open section.
- `onSelect: (id: string) => void`: Called with a section's id when it's chosen; never for a locked one.
- `title?: string | undefined`: The accessible name of the section list. Defaults to "Settings".
- `children: React.ReactNode`
- `className?: string | undefined`

## <SettingsSection>

The heading and body of one settings section.

Props:

- `title: string`: The section's heading.
- `description?: React.ReactNode | undefined`: A line under the heading saying what the section controls.
- `children: React.ReactNode`
- `className?: string | undefined`

## Strings (`settingsLayout`)

The fixed words, English by default. Override any of them under `settingsLayout` in `<LocaleProvider strings={{ settingsLayout: { … } }}>`; the table is exported as `settingsLayoutStrings`.

- `settings`: Settings
- `section`: {title} section
- `locked`: {label} (locked: {reason})

## Types

### SettingsItem

```ts
{ id: string label: string Icon?: LucideIcon | undefined /** Why this section can't be opened by the viewer; it stays listed. */ locked?: string | undefined }
```

### SettingsGroup

```ts
{ label: string; items: SettingsItem[] }
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
