# Theme setting

The person's control for the theme: "Match my system", "Light", "Dark" or "High contrast", on its own, for an app with no profile page (`ProfileSettings` draws the same control). It stores and applies nothing: the choice goes back through `onValueChange`, and the app keeps it and puts it on the page, as `data-theme="light"`, `"dark"` or `"hc"` on `<html>` (no attribute for "Match my system", which follows the system's setting). Nothing is chosen for the person: without a `value` it shows "Choose a theme".

```
<ThemeSetting value={settings.theme} onValueChange={(theme) => setSettings({ ...settings, theme })} />
```

- Registry name: `theme-setting` (`@candor/theme-setting`)
- 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/theme-setting

## Install

```bash
npx candor-ui add theme-setting
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/theme-setting`.
It brings in: locale, locked, select, tokens, utils.

## Example

```tsx
"use client"

import * as React from "react"
import { ThemeSetting, type ThemeChoice } from "@/components/candor/theme-setting"

/*
 * The app keeps the choice (here in state; in an app, with the person's settings) and puts it on the page: data-theme
 * on <html>, or no attribute for "Match my system". This example keeps it in a sentence, so the docs page's own theme
 * stays as it is.
 */
export default function Example() {
  const [theme, setTheme] = React.useState<ThemeChoice | undefined>(undefined)
  return (
    <div className="flex max-w-xs flex-col gap-3 text-sm">
      <ThemeSetting value={theme} onValueChange={setTheme} />
      <p className="text-xs text-muted-foreground">
        {theme === undefined ? "Nothing chosen yet." : theme === "system" ? "No data-theme attribute: the page follows the system." : `data-theme="${theme}" on <html>.`}
      </p>
    </div>
  )
}
```

## <ThemeSetting>

Props:

- `value?: ThemeChoice | undefined`: The person's choice as the app last stored it. Left out, nothing is chosen and the control says "Choose a theme".
- `onValueChange: (next: ThemeChoice) => void`: Called with the choice when the person picks one.
- `label?: string | undefined`: The words above the control. Defaults to "Theme".
- `optionLabels?: Partial<Record<ThemeChoice, string>> | undefined`: The options' words, where the app keeps them somewhere other than `themeSetting` (ProfileSettings passes its own).
- `locked?: string | null | undefined`: Why the theme can't be changed now (the organisation sets it). The control stays focusable and says why.
- `className?: string | undefined`: Classes for the wrapper, merged with Candor's.

## Strings (`themeSetting`)

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

- `label`: Theme
- `placeholder`: Choose a theme
- `system`: Match my system
- `light`: Light
- `dark`: Dark
- `hc`: High contrast

## Types

### ThemeChoice

The person's theme: follow the system's light or dark, or always light, dark or high contrast.

```ts
"system" | "light" | "dark" | "hc"
```

## 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`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
