Radio group
Words in a row. The one you choose turns italic, because it's yours now. One dot glides beneath it along a slur, or the italic itself slides through the words to it and sets a full stop after it. A ballot cross can be written after it, or, in a heavy column, its italic swells.
Report an issue with Radio groupExample
Rest
Pointed at
Focus
Chosen
Disabled
In a column
Glissando, pointed at
Glissando, chosen
Ballot, pointed at
Ballot, chosen
Sforzando, pointed at
Sforzando, focus
Install
Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base along.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/radio-group.jsonUsage
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"export default function Example() {return (<div className="grid justify-items-start gap-(--ot-space-8)"><RadioGroup legend="Timeline" defaultValue="month"><RadioGroupItem value="two-weeks">Two weeks</RadioGroupItem><RadioGroupItem value="month">A month</RadioGroupItem><RadioGroupItem value="season">A season</RadioGroupItem></RadioGroup><RadioGroup variant="glissando" legend="Reply by" defaultValue="wednesday"><RadioGroupItem value="monday">Monday</RadioGroupItem><RadioGroupItem value="wednesday">Wednesday</RadioGroupItem><RadioGroupItem value="friday">Friday</RadioGroupItem><RadioGroupItem value="later">Next week</RadioGroupItem></RadioGroup><RadioGroup variant="ballot" legend="Seat" defaultValue="aisle"><RadioGroupItem value="window">Window</RadioGroupItem><RadioGroupItem value="aisle">Aisle</RadioGroupItem><RadioGroupItem value="either">Either</RadioGroupItem></RadioGroup><RadioGroup variant="sforzando" legend="Tone of voice" defaultValue="warm"><RadioGroupItem value="plain">Plain</RadioGroupItem><RadioGroupItem value="warm">Warm</RadioGroupItem><RadioGroupItem value="bold">Bold</RadioGroupItem><RadioGroupItem value="grand">Grand</RadioGroupItem></RadioGroup></div>)}Contract
- Underneath: native radios, plus a hook that places the marks.
- Anatomy:
<fieldset class="ot-choice" data-variant="legato | glissando | ballot | sforzando" data-orientation="horizontal | vertical">, a legend, then<label><input type="radio"><span class="ot-choice-word">Words</span><span class="ot-choice-yours" aria-hidden="true">Words</span></label>for each option; the roman names the radio, and the italic copy shares its grid cell. Legato adds<svg class="ot-choice-slur">and<span class="ot-choice-dot">; ballot adds<svg class="ot-choice-ballot" aria-hidden="true">to each label. - Yours: the chosen word cross-fades into the italic (the roman leaves at allegro, the italic arrives at moderato, so the two are never printed over each other), and the unchosen words step back to pencil. Script adds
data-readyonce the marks are placed, so the first render never moves. - Legato (default). One accent dot sits under the chosen word (
--ot-choice-drop, 0.66em below it, so small words keep it as close); in a column it hangs in the margin before the word. Script sets--ot-choice-xand--ot-choice-y, measured from the slur's own corner so a legend doesn't shift it. Choosing, the dot glides along a slur, a curve that bows away from the words (out into the margin, in a column) and stays shallow however far it goes; it is drawn out along the curve like a drop of ink (longest at mid-flight) and lands round, and a longer glide takes a little longer, never past--ot-andante. Pointing at an unchosen word sketches that slur in pencil from the dot to a hairline ring where the dot would land (label::before); leaving, the line passes through the way it was drawn, and choosing uses it up as the dot runs along it. - Glissando. No dot. Script lays the words end to end on one line of reading (
--ot-choice-sfor each word's start), and the italic shows only inside a lens on that line (--ot-choice-ato-b, with a 0.2em seam). Choosing moves the lens leading edge first, so the italic wipes through every word between, then gathers itself into the chosen word, whichever row the words wrap onto. Then an accent full stop lands with spiccato just after the italic's last letter. Its room is always kept, so nothing moves its neighbours. Pointing sketches the stop as a pencil ring there. - Ballot. The cross a hand sets in the box after a name on a ballot paper. Every word keeps a margin after it (1.2em, inside the label, so the pointer and focus include it, nothing moves, and the words still line up under the legend), and there the chosen word gets a pen cross in the accent (
.ot-choice-ballot, one path withpathLength=1): two strokes as tall as a capital and never quite square, a short one down to the right and then a longer one, set off higher, that bows and runs past it. The dash runs on from the first stroke into the second, so the pen writes one, lifts and writes the other. Choosing writes the new cross (breath) while the one it leaves passes through, its tail following its head off the page. Pointing sketches the cross in pencil; leaving, the pencil passes through the same way, and choosing inks the sketch where it stands. Right to left, the margin and the cross mirror, so the short stroke still comes first. - Sforzando. The poster's scale contrast. The words stand in a column, set heavy (780) and narrow (72%) in the voice and packed close at
--ot-mf; the chosen one's roman steps out and its italic, in the accent, swells to 1.75× from its baseline with spiccato, the sudden accent the mark is named for. It runs up under the word above, which stays on top and reads, the way "has" crosses "It" on the poster. Nothing reflows: the swell is a scale. Focus rings the swell itself. It sets its own column, soorientationdoesn't apply. - States: rest, hover, focus (accent outline around the word), chosen, disabled (pencil at 55%). Right to left, the dot hangs on the inline start, the lens runs right to left, the cross mirrors and the swell grows from the right. Forced colours: the dot, the stop, the chosen cross and focus use
Highlight, the slur, rings and pencil crossesCanvasText. - Keyboard: native radio group (arrow keys move the choice). Under reduced motion the marks move straight to their places, and a cross is simply there.
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
name | string | Shared by every radio so a form submits the choice. Generated if left out. | |
legend | ReactNode | The small label over the words. Without one, give the group an aria-label. | |
variant | "legato" | "glissando" | "ballot" | "sforzando" | "legato" | legato: one accent dot glides under the chosen word along a slur, drawn out like a drop of ink as it goes; pointing sketches that slur in pencil to a ring. glissando: no dot; the italic itself slides through the words between, leading edge first, and an accent full stop lands after the chosen word; pointing sketches the stop as a ring. ballot: a pen cross is written after the chosen word in the accent, in two strokes, as on a ballot paper, and the one it leaves runs out; pointing sketches the cross in pencil. Every word keeps the margin for it. sforzando: the words stand in a column, heavy and narrow, and the chosen one's italic swells in the accent under the word above; it sets its own column, so orientation doesn't apply. |
orientation | "horizontal" | "vertical" | "horizontal" | The words run in a line and wrap, or stand in a column. In a column the legato dot hangs in the margin before the word. |
value / defaultValue | string | The chosen item's value. | |
onValueChange | (value: string) => void | Called with the new value when the choice changes. | |
RadioGroupItem value | string | What this word stands for. | |
RadioGroupItem children | string | The word. Plain text, because the italic copy is drawn from it. | |
RadioGroupItem disabled | boolean | false | The word steps back and can't be chosen. |
RadioGroupItem labelClassName | string | Classes for the word's label wrapper. className goes to the native radio. |
Craft
Where its move comes from
| Component | Reference | Move |
|---|---|---|
| Radio | "It has to be design."; the score's slur | Chosen word turns italic; the rest step back. The dot rides a slur, or the italic slides to the word and sets a full stop |
| Radio, ballot | A ballot paper's cross; the collages' pen marks | A hand cross written after the chosen word |
| Radio, sforzando | "It has to be design." scale contrast; the score's sfz | Heavy narrow words in a column; the chosen italic swells under the word above |
How it moves
| Component | Articulation | What moves |
|---|---|---|
| Radio | Legato | The dot glides along a slur, drawn out like a drop of ink; pointing sketches the slur in pencil to a ring |
| Radio, glissando | Glissando | The italic wipes through the words between, leading edge first; the full stop lands |
| Radio, ballot | Written | The pen writes the cross, one stroke then the other; the old cross passes through; pointing sketches it in pencil |
| Radio, sforzando | Sforzando | The chosen italic swells sharply from its baseline; the one it leaves falls back at once |