Skip to main content

Theme config

Theme mode

Color palette

Grays

Border radii/radiuses/radiopedes/you know
Border radius
Field border radius
Button border radius
All
Components
Guides
API
Recent

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.

Legend
<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

Legend
<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.

Legend

Field description above fields

Legend

Field description below 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.

Legend
<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>.

These are required!
<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.

Legend
Something went wrong!
<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);
}
}
}
}