# Radio

> Radio buttons for one choice among several. A CSS component with two classes and no JavaScript.

Plain CSS on an ordinary `<input type="radio">`. The buttons in a group are tied together by `name`, and that is what lets only one be selected, and lets the arrow keys move between them.

If the user can choose several, use [Checkbox](/components/checkbox.md). If there are many options, [Select](/components/select.md) takes less room on the screen.

## How to use it

**Plain HTML**

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/tokens/tokens.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/components/css/radio/radio.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/components/css/fieldset/fieldset.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/components/css/label/label.css">
<!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/dist/lotek.css -->
```

```html
<fieldset class="lt-fieldset">
<legend class="lt-legend" data-required="symbol">Lease length</legend>


<div class="lt-radio-row">
  <input class="lt-radio" type="radio" id="lease-1" name="lease" value="1" checked />
  <label class="lt-label" for="lease-1">One month</label>
</div>


<div class="lt-radio-row">
  <input class="lt-radio" type="radio" id="lease-6" name="lease" value="6" />
  <label class="lt-label" for="lease-6">Six months</label>
</div>


<div class="lt-radio-row">
  <input class="lt-radio" type="radio" id="lease-12" name="lease" value="12" disabled />
  <label class="lt-label" for="lease-12">Twelve months</label>
</div>
</fieldset>
```

**TypeScript**

```ts
import "@lo-tek/interfaces/tokens.css"
import "@lo-tek/interfaces/radio.css"
import "@lo-tek/interfaces/fieldset.css"
import "@lo-tek/interfaces/label.css"
```

`lt` is used in two ways, and which one depends on whether you create the element or find it.

**If you create the markup,** spread the attributes in. That is the way in React, Astro and anything else that renders:

```tsx
import { lt } from "@lo-tek/interfaces/react"


<div className={lt.radio.row}>
  <input {...lt.radio()} id="lease-1" name="lease" value="1" />
  <label {...lt.label()} htmlFor="lease-1">One month</label>
</div>
```

The import is from `/react` here, since the block is JSX. From there the keys are `className` and `htmlFor`. In Astro and anything else without React, import `@lo-tek/interfaces` and you get `class` and `for`.

**If the element is already there,** set them on it. That is the way when you enhance HTML a server or a template has sent:

```ts
import { lt } from "@lo-tek/interfaces"


const buttons = document.querySelectorAll<HTMLInputElement>("[name='lease']")


function mark(invalid: boolean) {
  for (const button of buttons) {
    lt.setAttributes(button, lt.radio({ state: invalid ? "invalid" : "default" }))
  }
}
```

You set `name` yourself, since only you know what the group is called. `lt.radio.row` gives the class for the row around it. See [Type-safe use](/type-safe-use.md).

**kotlinx.html**

```kotlin
// Once for the whole app, in the page template: every stylesheet, from the WebJar.
head { link(rel = "stylesheet", href = LotekWebJar.CSS) }
```

On a Kotlin or Java server, the same with kotlinx.html and `lo-tek-kotlinx-html`. The builder functions are in `Lt`, with an enum for each list of values:

```kotlin
import kotlinx.html.*
import no.lotek.*
import no.lotek.html.lt


fun FlowContent.lease() {
    fieldSet {
        lt(Lt.fieldset())
        legend { lt(Lt.legend(required = RequiredMarker.SYMBOL)); +"Lease length" }
        div {
            lt(Lt.radio.row)
            input { lt(Lt.radio()); id = "lease-1"; name = "lease"; value = "1"; checked = true }
            label { lt(Lt.label()); htmlFor = "lease-1"; +"One month" }
        }
        div {
            lt(Lt.radio.row)
            input { lt(Lt.radio()); id = "lease-6"; name = "lease"; value = "6" }
            label { lt(Lt.label()); htmlFor = "lease-6"; +"Six months" }
        }
        div {
            lt(Lt.radio.row)
            input { lt(Lt.radio()); id = "lease-12"; name = "lease"; value = "12"; disabled = true }
            label { lt(Lt.label()); htmlFor = "lease-12"; +"Twelve months" }
        }
    }
}
```

The group always belongs in a [Fieldset](/components/fieldset.md). Without it only the option is read out, not the question it answers.

## States

**Plain HTML**

```html
<div class="lt-radio-row">
<input class="lt-radio" type="radio" id="invalid-choice" name="example" data-state="invalid" aria-invalid="true" />
<label class="lt-label" for="invalid-choice">Choose a lease length</label>
</div>
```

| `data-state` | Used for |
| - | - |
| *(omitted)* | Normal state |
| `invalid` | Validation has failed |
| `success` | The choice is confirmed valid. Use sparingly. |

If validation fails, set the state on every button in the group. It is the group that is invalid, not one of the buttons.

## Classes you can style

| Class | What it is |
| - | - |
| `.lt-radio` | The button itself |
| `.lt-radio-row` | The row that holds the button and the text side by side |

| Variable | Default |
| - | - |
| `--lt-radio-size` | `var(--lt-spacing-5)` |

The dot is a gradient drawn in `--lt-color-accent-content`, so it follows the theme without a separate image. See [Customising](/customising.md).

## Accessibility

What a screen reader is given when the recipe above is used, step by step. It is checked through AT-SPI, the interface Orca reads, in Chromium and Firefox, on every change, by `check-accessibility-tree.ts`, and every change the check lists as a mutation must make it fail.

1. Focus `#lease-1`: focus on the `radio` "One month", checked; inside the `group` "Lease length \*".
2. Press `ArrowDown`: focus on the `radio` "Six months", checked; inside the `group` "Lease length \*".

- Every button in the group has to share `name`. Without it each button becomes its own group, and the user can choose several.
- The arrow keys move between the buttons and select at the same time. Tab leaves the whole group. That is the browser’s own behaviour, and it should not be overridden.
- Do not preselect an option when the choice has a consequence. The default then becomes an answer the user never considered.
- If you only have two options that are each other’s opposite, consider [Checkbox](/components/checkbox.md) or [Switch](/components/switch.md) instead.
