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 supportSupported since v105.Full supportSupported since v121.Full supportSupported since v15.4.

    Anatomy

    • <Radio>

      Container element.

    • v-model

      The radio input.

    • v-slot:default

      The label.

    • v-slot:end-text

      Supporting text displayed below the label.

    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="radio-group">
    <Radio value="1" checked>Radio 1</Radio>
    <Radio value="2">Radio 2</Radio>
    <Radio value="3">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" name="radio-group" value="1" checked /><span
    class="ui-label"
    ><!--[-->Radio 1<!--]--></span
    ><!----></label
    ><label class="ui-radio"
    ><input type="radio" name="radio-group" value="2" /><span
    class="ui-label"
    ><!--[-->Radio 2<!--]--></span
    ><!----></label
    ><label class="ui-radio"
    ><input type="radio" name="radio-group" value="3" /><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="radio-group-direction">
    <Radio value="1" checked>Radio 1</Radio>
    <Radio value="2">Radio 2</Radio>
    <Radio value="3">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"
    name="radio-group-direction"
    value="1"
    checked
    /><span class="ui-label"><!--[-->Radio 1<!--]--></span><!----></label
    ><label class="ui-radio"
    ><input type="radio" name="radio-group-direction" value="2" /><span
    class="ui-label"
    ><!--[-->Radio 2<!--]--></span
    ><!----></label
    ><label class="ui-radio"
    ><input type="radio" name="radio-group-direction" value="3" /><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="radio-group-field-description-1">
    <Radio value="1" checked>Radio 1</Radio>
    <Radio value="2">Radio 2</Radio>
    <Radio value="3">Radio 3</Radio>
    </FieldGroup>
    </FieldSet>
    <FieldSet>
    <FieldLegend>Legend</FieldLegend>
    <FieldGroup direction="row" name="radio-group-field-description-2">
    <Radio value="1" checked>Radio 1</Radio>
    <Radio value="2">Radio 2</Radio>
    <Radio value="3">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"
    name="radio-group-field-description-1"
    value="1"
    checked
    /><span class="ui-label"><!--[-->Radio 1<!--]--></span><!----></label
    ><label class="ui-radio"
    ><input
    type="radio"
    name="radio-group-field-description-1"
    value="2"
    /><span class="ui-label"><!--[-->Radio 2<!--]--></span><!----></label
    ><label class="ui-radio"
    ><input
    type="radio"
    name="radio-group-field-description-1"
    value="3"
    /><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"
    name="radio-group-field-description-2"
    value="1"
    checked
    /><span class="ui-label"><!--[-->Radio 1<!--]--></span><!----></label
    ><label class="ui-radio"
    ><input
    type="radio"
    name="radio-group-field-description-2"
    value="2"
    /><span class="ui-label"><!--[-->Radio 2<!--]--></span><!----></label
    ><label class="ui-radio"
    ><input
    type="radio"
    name="radio-group-field-description-2"
    value="3"
    /><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="radio-group-disabled">
    <Radio value="1" checked>Radio 1</Radio>
    <Radio value="2">Radio 2</Radio>
    <Radio value="3">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"
    name="radio-group-disabled"
    value="1"
    checked
    /><span class="ui-label"><!--[-->Radio 1<!--]--></span><!----></label
    ><label class="ui-radio"
    ><input type="radio" name="radio-group-disabled" value="2" /><span
    class="ui-label"
    ><!--[-->Radio 2<!--]--></span
    ><!----></label
    ><label class="ui-radio"
    ><input type="radio" name="radio-group-disabled" value="3" /><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="radio-group-required">
    <Radio value="1" required>Radio 1</Radio>
    <Radio value="2" required>Radio 2</Radio>
    <Radio value="3" 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"
    name="radio-group-required"
    value="1"
    required
    /><span class="ui-label"><!--[-->Radio 1<!--]--></span><!----></label
    ><label class="ui-radio"
    ><input
    type="radio"
    name="radio-group-required"
    value="2"
    required
    /><span class="ui-label"><!--[-->Radio 2<!--]--></span><!----></label
    ><label class="ui-radio"
    ><input
    type="radio"
    name="radio-group-required"
    value="3"
    required
    /><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="radio-group-validation">
    <Radio value="1" checked>Radio 1</Radio>
    <Radio value="2">Radio 2</Radio>
    <Radio value="3">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"
    name="radio-group-validation"
    value="1"
    checked
    /><span class="ui-label"><!--[-->Radio 1<!--]--></span><!----></label
    ><label class="ui-radio"
    ><input type="radio" name="radio-group-validation" value="2" /><span
    class="ui-label"
    ><!--[-->Radio 2<!--]--></span
    ><!----></label
    ><label class="ui-radio"
    ><input type="radio" name="radio-group-validation" value="3" /><span
    class="ui-label"
    ><!--[-->Radio 3<!--]--></span
    ><!----></label
    ><!--]-->
    </div>
    <span class="ui-end-text">Something went wrong!</span><!--]-->
    </fieldset>
    <!--]-->
    </form>

    API

    Radio API

    PropTypeDefaultDescription
    errorbooleanfalseShows error styles.
    hideLabelbooleanfalseVisually hides the label.
    size"small", "large"-The size of the element.
    stackbooleanfalseStacks the label under the input.
    v-modelstring, number, boolean-The selected value of the group.

    Slots

    SlotDescription
    defaultThe label.
    end-textSupporting text displayed below the label.

    Attributes that aren't props, such as disabled, name or value, go to the <input>.

    Field group API

    PropTypeDefaultDescription
    direction"row", "column"-The orientation of the element.
    namestring-Sets name on every input, select and textarea inside.

    Slots

    SlotDescription
    defaultThe fields, such as checkboxes, radios or switches.

    Browser support

    Full supportSupported since v105.Full supportSupported since v121.Full supportSupported 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 preceded 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);
    }
    }
    }
    }