Keyboard shortcut: Ctrl + K
Get started

Radio

One choice among several. The group is native: every button sharing a name belongs to it, and the browser handles the exclusivity and the arrow keys.

Usage

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VRadio } from 'vectis-ui'

const plan = ref('standard')
</script>

<template>
  <VRadio v-model="plan" name="plan" value="free">Free</VRadio>
  <VRadio v-model="plan" name="plan" value="standard">Standard</VRadio>
  <VRadio v-model="plan" name="plan" value="pro">Pro</VRadio>
</template>

Examples

Hint

label writes the text beside the dot, and the default slot replaces it. hint adds a caption underneath, tied to the button through aria-describedby, which is the place to say what an option implies.

vue
Three to five working days.Next working day, when ordered before noon.
<script setup lang="ts">
import { ref } from 'vue'
import { VRadio } from 'vectis-ui'

const delivery = ref('standard')
</script>

<template>
  <div class="stack">
    <VRadio
      v-model="delivery"
      name="delivery"
      value="standard"
      label="Standard"
      hint="Three to five working days."
    />
    <VRadio
      v-model="delivery"
      name="delivery"
      value="express"
      label="Express"
      hint="Next working day, when ordered before noon."
    />
  </div>
</template>

<style scoped>
.stack {
  display: grid;
  justify-items: start;
  gap: var(--vectis-space-3);
}
</style>

Label position

labelPosition moves the label before the dot instead of after it.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VRadio } from 'vectis-ui'

const side = ref('end')
</script>

<template>
  <div class="stack">
    <VRadio v-model="side" name="label-side" value="end">Label after the dot, the default</VRadio>
    <VRadio v-model="side" name="label-side" value="start" label-position="start">
      Label before the dot
    </VRadio>
  </div>
</template>

<style scoped>
/* `justify-items: start` keeps each row as wide as its own label: the clickable area is
   the whole <label>, so a stretched row would be clickable well past its text. */
.stack {
  display: grid;
  justify-items: start;
  gap: var(--vectis-space-2);
}
</style>

Spread

spread takes the full width offered and pushes the label and the dot to opposite ends of the row.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VRadio } from 'vectis-ui'

const side = ref('start')
</script>

<template>
  <div class="settings">
    <VRadio v-model="side" name="spread-side" value="start" spread label-position="start">
      Label at the start, dot at the end
    </VRadio>
    <VRadio v-model="side" name="spread-side" value="end" spread>
      Dot at the start, label at the end
    </VRadio>
  </div>
</template>

<style scoped>
/* A spread row takes the width it is given, so the panel is what decides how far apart
   the label and the dot end up. */
.settings {
  display: grid;
  gap: var(--vectis-space-3);
  inline-size: 100%;
  max-inline-size: 24rem;
  padding: var(--vectis-space-4);
  border: 1px solid var(--vectis-color-border);
  border-radius: var(--vectis-radius-surface);
}
</style>

Read-only

readonly, set on every button of the group, keeps the selection where it is. The component cancels the click, and the arrow keys are covered too, since the browser selects the next button through a click: the focus moves, the selection does not. No aria-readonly is written, ARIA allowing it on a radiogroup and not on a radio: put it on the wrapper that names the question, or nothing announces the state. And a read-only button still takes part in constraint validation, so readonly with required and nothing selected leaves a form that cannot be submitted or fixed.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VRadio } from 'vectis-ui'

const plan = ref('standard')
</script>

<template>
  <div class="stack">
    <!-- Set on every button of the group. The arrow keys still move the focus, and the
         selection stays where it is. -->
    <VRadio v-model="plan" name="locked-plan" value="free" readonly label="Free" />
    <VRadio v-model="plan" name="locked-plan" value="standard" readonly label="Standard" />
    <VRadio v-model="plan" name="locked-plan" value="pro" readonly label="Pro" />
  </div>
</template>

<style scoped>
.stack {
  display: grid;
  justify-items: start;
  gap: var(--vectis-space-3);
}
</style>

Disabled

disabled prevents the button from being picked and greys it out through the colour tokens. A button both selected and disabled keeps its dot.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VRadio } from 'vectis-ui'

const plan = ref('legacy')
</script>

<template>
  <div class="stack">
    <VRadio v-model="plan" name="plan-tier" value="free">Free</VRadio>
    <VRadio v-model="plan" name="plan-tier" value="pro" disabled>
      Pro, not available on this account
    </VRadio>
    <VRadio v-model="plan" name="plan-tier" value="legacy" disabled>
      Legacy, no longer offered
    </VRadio>
  </div>
</template>

<style scoped>
.stack {
  display: grid;
  justify-items: start;
  gap: var(--vectis-space-2);
}
</style>

API

Props

PropTypeDefault
valueItemValuenone
What choosing this button means. The group's v-model holds the value of the selected button, so this is what it becomes when this one is picked.
labelstringnone
The text beside the dot, which names it. The default slot replaces it.
hintstringnone
A line of help under the label. It is tied to the radio button for assistive technology, so it is read out after the label rather than as part of it.
labelPositionRadioLabelPosition'start' | 'end''end'
Which side of the dot the label sits on.
spreadbooleanfalse
Pushes the label and the dot to opposite ends of the line, the row taking the full width available.
invalidbooleanfalse
Marks the field as invalid, which colours the dot and tells assistive technology so. It is for a rule the browser cannot check by itself.
disabledbooleanfalse
Makes this choice unusable, greyed out through the colour tokens.
readonlybooleanfalse
Shows the selection without allowing it to be changed. The button can still be focused and is still submitted with its form; a click or an arrow key simply selects nothing. Set it on every button of the group.
v-modelItemValue''
The value selected in the group, shared by every radio carrying the same name. It is empty until one is chosen, and a radio is selected when it matches its own value.

Slots

SlotType
default{}
The label, when it needs more than the label prop's text. It is clickable.

Types

The types the tables above name, written as the library declares them. The ones carrying export can be imported from vectis-ui to type your own code; the others are the shape of what a slot hands out.

export type ItemValue = string | number

CSS variables

TokenValue
--vectis-control-size-check1.25rem
--vectis-control-size-check-dot0.5rem