# Color mode

Lets readers choose Auto, light, dark or high contrast: radios that work without JavaScript, or one round button that steps through them.

### Known issues
What to expect:

- Without [the script for the head](https://nandovejer.github.io/luz/docs/components/theme-mode/#boot), a dark visitor sees the page light for a moment first: the element runs after the first paint.
- Auto follows the system setting only. To let a reader pick the time of day too, add [`<luz-theme-time>`](https://nandovejer.github.io/luz/docs/catalog/theme-time/) from the catalog.
- The text is yours: LUZ writes no label, so translate the legend and the modes with the page.
- It has not been tested with a screen reader.

### Copy the markup
Write one radio for each choice inside `<luz-theme-mode>`, in the order the button steps through them. Put an SVG icon (`currentColor`, `aria-hidden`) in each label: the button shows the checked one. The legend and the labels are yours.

Markup
```html
<script type="module" src="https://cdn.jsdelivr.net/npm/@luz-ds/core@0.3.0/dist/luz.min.js"></script>

<luz-theme-mode>
  <fieldset class="luz-theme-mode__group">
    <legend class="luz-theme-mode__legend">Color mode</legend>
    <label class="luz-theme-mode__option"><input class="luz-theme-mode__input" type="radio" name="luz-theme-mode" value="auto" checked><svg class="luz-theme-mode__icon" aria-hidden="true">…</svg>Auto</label>
    <label class="luz-theme-mode__option"><input class="luz-theme-mode__input" type="radio" name="luz-theme-mode" value="light"><svg class="luz-theme-mode__icon" aria-hidden="true">…</svg>Light</label>
    <label class="luz-theme-mode__option"><input class="luz-theme-mode__input" type="radio" name="luz-theme-mode" value="dark"><svg class="luz-theme-mode__icon" aria-hidden="true">…</svg>Dark</label>
    <label class="luz-theme-mode__option"><input class="luz-theme-mode__input" type="radio" name="luz-theme-mode" value="high-contrast"><svg class="luz-theme-mode__icon" aria-hidden="true">…</svg>High contrast</label>
  </fieldset>
</luz-theme-mode>
```

`luz.min.js` loads `theme-mode.min.js` when the page has the element. To load it yourself, or for an element added after the page has loaded, `import "@luz-ds/core/theme-mode";`.

### Variants and options
- `<luz-theme-mode>`: The element. It holds the radios; with scripting on it adds one round button, fixed at the end corner.
- `inline`: A boolean. The button stays where the markup puts it: a footer, a navbar, a sidebar.
- `no-remember`: A boolean. The choice is not read or stored under `luz-theme`: the page starts in Auto.
- `name="luz-theme-mode"`: Keep it: without JavaScript the tokens read it to change the mode.
- `value`: `auto`, `light`, `dark` or `high-contrast`. The button steps through the radios in markup order and skips a disabled one.
- `.luz-theme-mode__group`: The fieldset of radios; without the button, a row of options.
- `.luz-theme-mode__legend`, `.luz-theme-mode__option`, `.luz-theme-mode__input`, `.luz-theme-mode__icon`: The legend (it names the group and, with the checked label, the button); a label with its radio, icon and name; the radio, visually hidden and focusable; the icon, which the button copies from the checked option.
- `__button`, `__button--fixed`, `__status`: Written by the script, not by you: the round button (fixed unless `inline`) and a hidden live region that says the new mode.

#### Properties, methods and event

- `el.choice`: The choice (`auto`, `light`, `dark`, `high-contrast`). Setting it chooses that radio; other values are ignored.
- `el.mode`: The mode in use. In Auto it is the system's.
- `el.refresh()`: Applies the mode again. Call it after you change what Auto means.
- `el.autoMode`: A function that returns `light`, `dark` or nothing (the system's). Another script sets it to own what Auto means, as `<luz-theme-time>` does; the system's high contrast still wins.
- `luz-theme-mode:change`: Bubbles after the upgrade and on every change. `detail`: `mode`, `auto` (the choice is Auto) and `user` (a person chose it).

### How it works
A native radio group, styled by `components/theme-mode.css`. The radios are the state; with scripting on, the element turns them into one button.

- **The button:** each press checks the next radio and applies it; the group is hidden while it is there. The script adds `luz-theme-mode__button--fixed` to the fixed button and leaves it off with `inline`. A focused radio passes the focus to the button.
- **Auto follows the system,** also while the page is open: `prefers-contrast: more` gives `high-contrast`, else `prefers-color-scheme: dark` gives `dark`, else `light`.
- **What it writes on `<html>`:** the mode in `data-mode`, and with no brand the same value in `data-theme`, as the tokens read it. A page that names a brand (`data-theme="my-brand"`, a [theme of your own](https://nandovejer.github.io/luz/docs/tokens/themes/) made from `_template.css`) keeps it, and the blocks of the brand answer to `data-mode`.
- **The choice is remembered** under `luz-theme` in `localStorage`; Auto removes it and `no-remember` turns it off. If the script for the head has written `data-mode`, the first Auto adopts it. A second `<luz-theme-mode>` follows the first.
- **Where the button sits:** unless `inline`, at the end corner of the window (bottom left in a right-to-left page), at `--luz-z-raised`: above the content, below the sidebar, the dialogs and the toasts.
- **Keep the corner free.** Give the footer an end padding of `calc(var(--luz-control-lg) + var(--luz-space-md) * 2)` under `@media (scripting: enabled)`. The stylesheet adds `scroll-padding-block-end` to `<html>`, so Tab never leaves a focused element under the button; over a showing [`<luz-cookie-consent>`](https://nandovejer.github.io/luz/docs/catalog/cookie-consent/) the button rides above the banner.
- **In a sidebar** it takes the colours of catalog's [sidebar](https://nandovejer.github.io/luz/docs/catalog/sidebar/); elsewhere on a dark surface, set the `--luz-theme-mode-*` properties.

### Without JavaScript
The four radios show where the markup puts them; there is no button. Choosing one changes the mode, because the tokens read the checked radio (`:root:has()`), until `<html>` has a `data-mode`. It works for a [brand theme](https://nandovejer.github.io/luz/docs/tokens/themes/) too, if its `_template.css` is current. The choice is not remembered, and Auto follows the system: dark under `prefers-color-scheme: dark`, high contrast under `prefers-contrast: more`, light otherwise.

### Keyboard and ARIA
- **Button:** Tab reaches it; Enter or Space moves to the next mode. Focus follows the markup, not the corner.
- **Name and status:** the button's name is the text of the legend, a colon and the label of the checked option, such as “Color mode: Dark”. After a person chooses, a hidden `role="status"` region says the same; it says nothing when the page loads.
- **Radios:** without the button, Tab reaches the group and the arrow keys choose a mode.

### When to use
Offer a color mode control on every page of a site, in the same place.

- The round button at the end corner, once per page.
- The round button with `inline`, where it belongs to a panel: a footer, a navbar, a sidebar.

### When not to use
Do not put a second fixed button on a page: the others are inline.

- Do not fix it at a corner another control holds, such as a chat button.
- Do not leave the radios out of the markup: without JavaScript they are the only control.
- Do not use it to choose a brand: a brand is named with `data-theme`; this element chooses its mode.
