Radio
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. If there are many options, Select takes less room on the screen.
How to use it
Section titled “How to use it”<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 --><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>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:
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:
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.
// 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:
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. Without it only the option is read out, not the question it answers.
States
Section titled “States”<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
Section titled “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.
Accessibility
Section titled “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.
- Focus
#lease-1: focus on theradio"One month", checked; inside thegroup"Lease length *". - Press ArrowDown: focus on the
radio"Six months", checked; inside thegroup"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 or Switch instead.