Components
Radio
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.
<script setup lang="ts">import { FieldGroup, FieldLegend, FieldSet, Form, Radio } from "opui-css/vue"</script>
<template> <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></template><form class="ui-form"> <!--[--> <fieldset class="ui-fieldset"> <!--[--> <legend class=""><!--[-->Legend<!--]--></legend> <div class="ui-field-group" role="group"> <!--[--><label class="ui-radio" ><input type="radio" aria-describedby="s0-0" /><span class="ui-label" ><!--[-->Radio 1<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s0-1" /><span class="ui-label" ><!--[-->Radio 2<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s0-2" /><span class="ui-label" ><!--[-->Radio 3<!--]--></span ><!----></label ><!--]--> </div> <!--]--> </fieldset> <!--]--></form>Direction
<script setup lang="ts">import { FieldGroup, FieldLegend, FieldSet, Form, Radio } from "opui-css/vue"</script>
<template> <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></template><form class="ui-form"> <!--[--> <fieldset class="ui-fieldset"> <!--[--> <legend class=""><!--[-->Legend<!--]--></legend> <div class="ui-field-group ui-row" role="group"> <!--[--><label class="ui-radio" ><input type="radio" aria-describedby="s1-0" /><span class="ui-label" ><!--[-->Radio 1<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s1-1" /><span class="ui-label" ><!--[-->Radio 2<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s1-2" /><span class="ui-label" ><!--[-->Radio 3<!--]--></span ><!----></label ><!--]--> </div> <!--]--> </fieldset> <!--]--></form>Field description
Can be placed above and below the fields.
<script setup lang="ts">import { FieldDescription, FieldGroup, FieldLegend, FieldSet, Form, Radio,} from "opui-css/vue"</script>
<template> <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></template><form class="ui-form"> <!--[--> <fieldset class="ui-fieldset"> <!--[--> <legend class=""><!--[-->Legend<!--]--></legend> <p class="ui-field-description"> <!--[-->Field description above fields<!--]--> </p> <div class="ui-field-group ui-row" role="group"> <!--[--><label class="ui-radio" ><input type="radio" aria-describedby="s2-0" /><span class="ui-label" ><!--[-->Radio 1<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s2-1" /><span class="ui-label" ><!--[-->Radio 2<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s2-2" /><span class="ui-label" ><!--[-->Radio 3<!--]--></span ><!----></label ><!--]--> </div> <!--]--> </fieldset> <fieldset class="ui-fieldset"> <!--[--> <legend class=""><!--[-->Legend<!--]--></legend> <div class="ui-field-group ui-row" role="group"> <!--[--><label class="ui-radio" ><input type="radio" aria-describedby="s2-3" /><span class="ui-label" ><!--[-->Radio 1<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s2-4" /><span class="ui-label" ><!--[-->Radio 2<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s2-5" /><span class="ui-label" ><!--[-->Radio 3<!--]--></span ><!----></label ><!--]--> </div> <p class="ui-field-description"> <!--[-->Field description below fields<!--]--> </p> <!--]--> </fieldset> <!--]--></form>Disabled
Attach the disabled attribute to the <fieldset> element.
<script setup lang="ts">import { FieldGroup, FieldLegend, FieldSet, Form, Radio } from "opui-css/vue"</script>
<template> <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></template><form class="ui-form"> <!--[--> <fieldset class="ui-fieldset" disabled> <!--[--> <legend class=""><!--[-->Legend<!--]--></legend> <div class="ui-field-group ui-row" role="group"> <!--[--><label class="ui-radio" ><input type="radio" aria-describedby="s3-0" /><span class="ui-label" ><!--[-->Radio 1<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s3-1" /><span class="ui-label" ><!--[-->Radio 2<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s3-2" /><span class="ui-label" ><!--[-->Radio 3<!--]--></span ><!----></label ><!--]--> </div> <!--]--> </fieldset> <!--]--></form>Required
Add the required attribute on at least one Radio. It is forwarded to the underlying <input>.
<script setup lang="ts">import { FieldGroup, FieldLegend, FieldSet, Form, Radio } from "opui-css/vue"</script>
<template> <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></template><form class="ui-form"> <!--[--> <fieldset class="ui-fieldset"> <!--[--> <legend class=""><!--[-->These are required!<!--]--></legend> <div class="ui-field-group ui-row" role="group"> <!--[--><label class="ui-radio" ><input type="radio" required aria-describedby="s4-0" /><span class="ui-label" ><!--[-->Radio 1<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" required aria-describedby="s4-1" /><span class="ui-label" ><!--[-->Radio 2<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" required aria-describedby="s4-2" /><span class="ui-label" ><!--[-->Radio 3<!--]--></span ><!----></label ><!--]--> </div> <!--]--> </fieldset> <!--]--></form>Validation
Attach data-invalid to the FieldSet wrapper, or
use the error prop on individual Radio components.
<script setup lang="ts">import { FieldGroup, FieldLegend, FieldSet, Form, Radio } from "opui-css/vue"</script>
<template> <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="ui-end-text">Something went wrong!</span> </FieldSet> </Form></template><form class="ui-form"> <!--[--> <fieldset class="ui-fieldset" data-invalid> <!--[--> <legend class=""><!--[-->Legend<!--]--></legend> <div class="ui-field-group ui-row" role="group"> <!--[--><label class="ui-radio" ><input type="radio" aria-describedby="s5-0" /><span class="ui-label" ><!--[-->Radio 1<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s5-1" /><span class="ui-label" ><!--[-->Radio 2<!--]--></span ><!----></label ><label class="ui-radio" ><input type="radio" aria-describedby="s5-2" /><span class="ui-label" ><!--[-->Radio 3<!--]--></span ><!----></label ><!--]--> </div> <span class="ui-end-text">Something went wrong!</span ><!--]--> </fieldset> <!--]--></form>API
| Prop | Type | Default | Description |
|---|---|---|---|
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.ui-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) { .ui-label:after { color: var(--red); content: "*"; inset: 0 -0.25ex auto auto; position: absolute; } }
/* Label */ .ui-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(.ui-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 */ &.ui-stack { grid-auto-columns: unset; justify-items: center;
.ui-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; } }
.ui-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 */ &.ui-small { input[type="radio"] { --_input-size: var(--size-3); } }
&.ui-large { input[type="radio"] { --_input-size: var(--size-4); } }
/* Validation */ &[data-invalid], &:has(:user-invalid) { --primary: var(--critical);
:where(.ui-end-text) { color: var(--critical); } }
/* 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(.ui-form) { display: grid; gap: var(--size-8);
hr { margin-block: 0; } }
/* Fieldset */ :where(.ui-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, .ui-legend) { all: unset; color: var(--text-primary); font-weight: 600; margin-block-end: var(--size-3); padding: 0;
&:has(+ :where(.ui-field-description)) { margin-block-end: 0; } }
/* Field description / supporting text */ :where(.ui-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(.ui-end-text) { color: var(--text-muted); font-size: var(--font-size-0); line-height: var(--font-lineheight-3); }
&:has(.ui-text-field.ui-row) { row-gap: var(--size-7); }
/* Disabled */ &[disabled] { opacity: 0.64; user-select: none;
input, label, .ui-label { cursor: not-allowed; } }
/* Error / validation */ &[data-invalid] { :where(.ui-end-text) { color: var(--critical); }
:where(.ui-checkbox, .ui-radio, .ui-switch) { --primary: var(--critical);
:where(.ui-end-text) { color: var(--critical); } }
:where(.ui-switch) { input { border-radius: var(--radius-round); outline: 2px solid var(--critical); } } }
/* Required */ &:has(:invalid) { :where(legend, .ui-legend) { padding-inline-end: 1ex; position: relative;
&::after { color: var(--red); content: "*"; inset: 0 -0.25ex auto auto; position: absolute; } } } }
/* Field group */ :where(.ui-field-group) { display: flex; flex-direction: column; gap: var(--size-4);
/* If it only has checkboxes, radios, or switches */ &:has(> :where(.ui-checkbox, .ui-radio, .ui-switch)):not( :has(> :not(.ui-checkbox, .ui-radio, .ui-switch)) ) { gap: var(--size-2); }
/* If it only has buttons */ &:has(.ui-button):not(:has(*:not(.ui-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 */ &.ui-row { flex-direction: row; flex-wrap: wrap;
&:has(> :where(.ui-checkbox, .ui-radio, .ui-switch)) { gap: var(--size-4); } } }}