Components
Radio
Use field groups to group related radio buttons. See also: Form documentation.
Full support Supported since v105. Full support Supported since v121. Full support Supported since v15.4.
The name prop will get passed down to each radio button in the
group.
---import { Radio } from "@opui/astro"import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { Form } from "@opui/astro"---
<Form> <FieldSet> <FieldLegend>Legend</FieldLegend> <FieldGroup name="fieldset-1-astro"> <Radio checked>Radio 1</Radio> <Radio>Radio 2</Radio> <Radio>Radio 3</Radio> </FieldGroup> </FieldSet></Form><form class="form"> <fieldset class="fieldset"> <legend>Legend</legend> <div class="field-group" role="group"> <label class="radio"> <input type="radio" checked name="fieldset-1-astro" /><span class="label" >Radio 1</span > </label> <label class="radio"> <input type="radio" name="fieldset-1-astro" /><span class="label" >Radio 2</span > </label> <label class="radio"> <input type="radio" name="fieldset-1-astro" /><span class="label" >Radio 3</span > </label> </div> </fieldset></form>Direction
---import { Radio } from "@opui/astro"import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { Form } from "@opui/astro"---
<Form> <FieldSet> <FieldLegend>Legend</FieldLegend> <FieldGroup direction="row" name="fieldset-direction-astro"> <Radio checked>Radio 1</Radio> <Radio>Radio 2</Radio> <Radio>Radio 3</Radio> </FieldGroup> </FieldSet></Form><form class="form"> <fieldset class="fieldset"> <legend>Legend</legend> <div class="field-group row" role="group"> <label class="radio"> <input type="radio" checked name="fieldset-direction-astro" /><span class="label" >Radio 1</span > </label> <label class="radio"> <input type="radio" name="fieldset-direction-astro" /><span class="label" >Radio 2</span > </label> <label class="radio"> <input type="radio" name="fieldset-direction-astro" /><span class="label" >Radio 3</span > </label> </div> </fieldset></form>Field description
Can be placed above and below the fields.
---import { Radio } from "@opui/astro"import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldDescription } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { Form } from "@opui/astro"---
<Form> <FieldSet> <FieldLegend>Legend</FieldLegend> <FieldDescription>Field description above fields</FieldDescription> <FieldGroup direction="row" name="fieldset-field-description-1-astro"> <Radio checked>Radio 1</Radio> <Radio>Radio 2</Radio> <Radio>Radio 3</Radio> </FieldGroup> </FieldSet>
<FieldSet> <FieldLegend>Legend</FieldLegend> <FieldGroup direction="row" name="fieldset-field-description-2-astro"> <Radio checked>Radio 1</Radio> <Radio>Radio 2</Radio> <Radio>Radio 3</Radio> </FieldGroup> <FieldDescription>Field description below fields</FieldDescription> </FieldSet></Form><form class="form"> <fieldset class="fieldset"> <legend>Legend</legend> <p class="field-description">Field description above fields</p> <div class="field-group row" role="group"> <label class="radio"> <input type="radio" checked name="fieldset-field-description-1-astro" /><span class="label">Radio 1</span> </label> <label class="radio"> <input type="radio" name="fieldset-field-description-1-astro" /><span class="label" >Radio 2</span > </label> <label class="radio"> <input type="radio" name="fieldset-field-description-1-astro" /><span class="label" >Radio 3</span > </label> </div> </fieldset> <fieldset class="fieldset"> <legend>Legend</legend> <div class="field-group row" role="group"> <label class="radio"> <input type="radio" checked name="fieldset-field-description-2-astro" /><span class="label">Radio 1</span> </label> <label class="radio"> <input type="radio" name="fieldset-field-description-2-astro" /><span class="label" >Radio 2</span > </label> <label class="radio"> <input type="radio" name="fieldset-field-description-2-astro" /><span class="label" >Radio 3</span > </label> </div> <p class="field-description">Field description below fields</p> </fieldset></form>Disabled
Attach the disabled attribute to the <fieldset> element.
---import { Radio } from "@opui/astro"import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { Form } from "@opui/astro"---
<Form> <FieldSet disabled> <FieldLegend>Legend</FieldLegend> <FieldGroup direction="row" name="fieldset-disabled-1-astro"> <Radio checked>Radio 1</Radio> <Radio>Radio 2</Radio> <Radio>Radio 3</Radio> </FieldGroup> </FieldSet></Form><form class="form"> <fieldset disabled class="fieldset"> <legend>Legend</legend> <div class="field-group row" role="group"> <label class="radio"> <input type="radio" checked name="fieldset-disabled-1-astro" /><span class="label" >Radio 1</span > </label> <label class="radio"> <input type="radio" name="fieldset-disabled-1-astro" /><span class="label" >Radio 2</span > </label> <label class="radio"> <input type="radio" name="fieldset-disabled-1-astro" /><span class="label" >Radio 3</span > </label> </div> </fieldset></form>Required
Attach the required attribute to at least one of your <input> elements.
---import { Radio } from "@opui/astro"import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { Form } from "@opui/astro"---
<Form> <FieldSet> <FieldLegend>These are required!</FieldLegend> <FieldGroup direction="row" name="fieldset-required-1-astro"> <Radio required>Radio 1</Radio> <Radio required>Radio 2</Radio> <Radio required>Radio 3</Radio> </FieldGroup> </FieldSet></Form><form class="form"> <fieldset class="fieldset"> <legend>These are required!</legend> <div class="field-group row" role="group"> <label class="radio"> <input type="radio" name="fieldset-required-1-astro" required /><span class="label" >Radio 1</span > </label> <label class="radio"> <input type="radio" name="fieldset-required-1-astro" required /><span class="label" >Radio 2</span > </label> <label class="radio"> <input type="radio" name="fieldset-required-1-astro" required /><span class="label" >Radio 3</span > </label> </div> </fieldset></form>Validation
Attach the data-invalid attribute to your Fieldset component.
---import { Radio } from "@opui/astro"import { FieldSet } from "@opui/astro"import { FieldLegend } from "@opui/astro"import { FieldGroup } from "@opui/astro"import { Form } from "@opui/astro"---
<Form> <FieldSet data-invalid> <FieldLegend>Legend</FieldLegend> <FieldGroup direction="row" name="field-group-validation-1-astro"> <Radio checked>Radio 1</Radio> <Radio>Radio 2</Radio> <Radio>Radio 3</Radio> </FieldGroup> <span class="end-text">Something went wrong!</span> </FieldSet></Form><form class="form"> <fieldset data-invalid="true" class="fieldset"> <legend>Legend</legend> <div class="field-group row" role="group"> <label class="radio"> <input type="radio" checked name="field-group-validation-1-astro" /><span class="label">Radio 1</span> </label> <label class="radio"> <input type="radio" name="field-group-validation-1-astro" /><span class="label" >Radio 2</span > </label> <label class="radio"> <input type="radio" name="field-group-validation-1-astro" /><span class="label" >Radio 3</span > </label> </div> <span class="end-text">Something went wrong!</span> </fieldset></form>API
| Prop | Type | Default | Description |
|---|---|---|---|
critical | boolean | false | Field group error state. |
legend | string | - | The text for the legend element. |
direction | "row" | - | The orientation of the field group. |
Browser support
Full support Supported since v105. Full support Supported since v121. Full support Supported since v15.4.
See also the full browser support guide.
Installation
See also
@layer components.root { label.radio { --_input-size: 1.125rem; --_indicator-size: 50%;
align-items: center; color: var(--text-primary); cursor: pointer; display: inline-grid; gap: 0 var(--size-2); grid-auto-columns: auto; grid-auto-flow: column; inline-size: fit-content; line-height: 1.5; transform: translateZ(0); user-select: none;
/* Disabled */ &:has([disabled]) { cursor: not-allowed; opacity: 0.64; user-select: none;
input { cursor: not-allowed; } }
/* Required dot */ &:has(:invalid) { .label:after { color: var(--red); content: "*"; inset: 0 -0.25ex auto auto; position: absolute; } }
/* Label */ .label { color: var(--text-primary); font-size: var(--font-size-05); grid-column: 2; grid-row: 1; padding-inline: 0 1ex; position: relative; }
/* End text */ :where(.end-text) { color: var(--text-muted); font-size: var(--font-size-0); grid-column: 2; grid-row: 2; line-height: 1.5; z-index: 1; }
/* Stacked layout */ &.stack { grid-auto-columns: unset; justify-items: center;
.label { grid-column: 1/-1; grid-row: 2; margin-block-start: var(--size-1); padding-inline: 1ex;
/* Required dot */ &::after { inset: 0 -0.25ex auto auto; } }
.end-text { grid-column: 1/-1; grid-row: 3; } }
/* Input */ input[type="radio"] { appearance: none; aspect-ratio: 1; background-color: var(--surface-default); block-size: var(--_input-size); border: 1px solid var(--field-border-color); border-radius: var(--radius-round); box-sizing: border-box; cursor: pointer; display: grid; inline-size: var(--_input-size); margin: 0; padding: 0; place-items: center; position: relative;
&:checked { background-color: var(--primary); border-color: var(--primary); }
/* Dot */ &::after { background-color: var(--primary-contrast); block-size: var(--_indicator-size); border-radius: var(--radius-round); content: ""; inline-size: var(--_indicator-size); margin: auto; opacity: 0; }
&:checked::after { opacity: 1; }
&::before { --highlight-size: 175%; } }
/* Sizes */ &.small { input[type="radio"] { --_input-size: var(--size-3);
} }
&.large { input[type="radio"] { --_input-size: var(--size-4);
} }
/* Validation */ &[data-invalid], &:has(:user-invalid) { --palette-source: oklch(0.58 0.21 var(--hue-red));
:where(.end-text) { color: var(--primary); } }
/* Touch devices */ @media (pointer: coarse) { input[type="radio"] { block-size: var(--size-4); inline-size: var(--size-4); } }
@media (forced-colors: active) { input[type="radio"] { border: 1px solid CanvasText;
&:checked { background-color: SelectedItem; border-color: SelectedItem;
&::after { background-color: SelectedItemText; } } } } }}@layer components.extended {
/* Form */ :where(.form) { display: grid; gap: var(--size-8);
hr { margin-block: 0; } }
/* Fieldset */ :where(.fieldset) { all: unset; border: 0; border-radius: 0; display: grid; gap: var(--size-1); margin: 0; min-inline-size: 0; padding: 0;
/* Legend */ :where(legend, .legend) { all: unset; color: var(--text-primary); font-weight: 600; margin-block-end: var(--size-3); padding: 0;
&:has(+ :where(.field-description)) { margin-block-end: 0; } }
/* Field description / supporting text */ :where(.field-description) { color: var(--text-muted); font-size: var(--font-size-05); line-height: var(--font-lineheight-3);
&:has(+ *) { margin-block-end: var(--size-3); } }
/* End text */ :where(.end-text) { color: var(--text-muted); font-size: var(--font-size-0); line-height: var(--font-lineheight-3); }
&:has(.text-field.row) { row-gap: var(--size-7); }
/* Disabled */ &[disabled] { opacity: 0.64; user-select: none;
input, label, .label { cursor: not-allowed; } }
/* Error / validation */ &[data-invalid] { :where(.end-text) { color: var(--primary); }
:where(.checkbox, .radio, .switch) { --palette-source: oklch(0.58 0.21 var(--hue-red));
:where(.end-text) { color: var(--primary); } }
:where(.switch) { input { border-radius: var(--radius-round); outline: 2px solid var(--primary); } } }
/* Required */ &:has(:invalid) { :where(legend, .legend) { padding-inline-end: 1ex; position: relative;
&::after { color: var(--red); content: "*"; inset: 0 -0.25ex auto auto; position: absolute; } } } }
/* Field group */ :where(.field-group) { display: flex; flex-direction: column; gap: var(--size-4);
/* If it only has checkboxes, radios, or switches */ &:has(> :where(.checkbox, .radio, .switch)):not(:has(> :not(.checkbox, .radio, .switch))) { gap: var(--size-2); }
/* If it only has buttons */ &:has(.button):not(:has(*:not(.button))) { align-items: center; flex-direction: row; gap: var(--size-2);
/* If it's not preceeded by an hr */ &:not(hr + &) { margin-block-start: var(--size-4); } }
&+& { margin-block-start: var(--size-5); }
/* Directions */ &.row { flex-direction: row; flex-wrap: wrap;
&:has(> :where(.checkbox, .radio, .switch)) { gap: var(--size-4); } } }}