Radio
TxRadioGroup + TxRadio****+********
Standard single-select with a dot + label, good for many options.
Demo will load when visible.
<script setup lang="ts">
import { ref } from 'vue'
const value = ref<'a' | 'b' | 'c'>('a')
</script>
<template>
<div class="tx-demo tx-demo__col tx-demo--max-400">
<TxRadioGroup v-model="value" type="standard" direction="row">
<TxRadio value="a" label="Option A" />
<TxRadio value="b" label="Option B" />
<TxRadio value="c" label="Option C" />
</TxRadioGroup>
<TxCard variant="plain" background="mask" :padding="10" :radius="14">
<div class="tx-demo__meta">
selected: {{ value }}
</div>
</TxCard>
</div>
</template>
Single-select emphasizing hierarchy, good for explanatory text.
Demo will load when visible.
<script setup lang="ts">
import { ref } from 'vue'
const value = ref<'a' | 'b' | 'c'>('a')
</script>
<template>
<div class="tx-demo tx-demo__col tx-demo--max-520">
<TxRadioGroup v-model="value" type="card">
<TxRadio value="a">
<div class="tx-demo__title">
Option A
</div>
<div class="tx-demo__desc">
Description text for option A
</div>
</TxRadio>
<TxRadio value="b">
<div class="tx-demo__title">
Option B
</div>
<div class="tx-demo__desc">
A longer description line to test wrapping and spacing
</div>
</TxRadio>
<TxRadio value="c" disabled>
<div class="tx-demo__title">
Option C (disabled)
</div>
<div class="tx-demo__desc">
Disabled option
</div>
</TxRadio>
</TxRadioGroup>
<TxCard variant="plain" background="mask" :padding="10" :radius="14">
<div class="tx-demo__meta">
selected: {{ value }}
</div>
</TxCard>
</div>
</template>
Compact button group, good for few options.
Demo will load when visible.
<script setup lang="ts">
import { ref } from 'vue'
const value = ref<'a' | 'b' | 'c'>('a')
</script>
<template>
<div class="tx-demo tx-demo__col">
<TxRadioGroup v-model="value">
<TxRadio value="a">
Option A
</TxRadio>
<TxRadio value="b">
Option B
</TxRadio>
<TxRadio value="c">
Option C
</TxRadio>
</TxRadioGroup>
<TxCard variant="plain" background="mask" :padding="10" :radius="14">
<div class="tx-demo__meta">
selected: {{ value }}
</div>
</TxCard>
</div>
</template>
Button group animation with indicator: default → normal → blur → glass.
Demo will load when visible.
<script setup lang="ts">
import { ref } from 'vue'
const valueDefault = ref<'a' | 'b' | 'c'>('a')
const valuePlain = ref<'a' | 'b' | 'c'>('a')
const valueBlur = ref<'a' | 'b' | 'c'>('a')
const valueGlass = ref<'a' | 'b' | 'c'>('a')
</script>
<template>
<div class="tx-demo tx-demo__col tx-demo__col--18">
<div class="tx-demo__col tx-demo__col--10">
<div class="tx-demo__label">
Default
</div>
<TxRadioGroup v-model="valueDefault">
<TxRadio value="a">
Option A
</TxRadio>
<TxRadio value="b">
Option B
</TxRadio>
<TxRadio value="c">
Option C
</TxRadio>
</TxRadioGroup>
</div>
<div class="tx-demo__col tx-demo__col--10">
<div class="tx-demo__label">
Plain
</div>
<TxRadioGroup v-model="valuePlain" :elastic="false">
<TxRadio value="a">
Option A
</TxRadio>
<TxRadio value="b">
Option B
</TxRadio>
<TxRadio value="c">
Option C
</TxRadio>
</TxRadioGroup>
</div>
<div class="tx-demo__col tx-demo__col--10">
<div class="tx-demo__label">
Blur
</div>
<TxRadioGroup v-model="valueBlur" blur>
<TxRadio value="a">
Option A
</TxRadio>
<TxRadio value="b">
Option B
</TxRadio>
<TxRadio value="c">
Option C
</TxRadio>
</TxRadioGroup>
</div>
<div class="tx-demo__col tx-demo__col--10">
<div class="tx-demo__label">
Glass
</div>
<TxRadioGroup v-model="valueGlass" glass>
<TxRadio value="a">
Option A
</TxRadio>
<TxRadio value="b">
Option B
</TxRadio>
<TxRadio value="c">
Option C
</TxRadio>
</TxRadioGroup>
</div>
</div>
</template>
Demo will load when visible.
<script setup lang="ts">
import { ref } from 'vue'
const value = ref<'a' | 'b'>('a')
</script>
<template>
<div class="tx-demo tx-demo__row">
<TxRadioGroup v-model="value" disabled>
<TxRadio value="a">
Disabled A
</TxRadio>
<TxRadio value="b">
Disabled B
</TxRadio>
</TxRadioGroup>
<TxRadioGroup v-model="value">
<TxRadio value="a" disabled>
Option A
</TxRadio>
<TxRadio value="b">
Option B
</TxRadio>
</TxRadioGroup>
</div>
</template>
A control panel for TxRadioGroup props to quickly validate variants/params.
Demo will load when visible.
<script setup lang="ts">
import { computed, ref } from 'vue'
type GroupType = 'button' | 'standard' | 'card'
type OptionValue = 'a' | 'b' | 'c'
const type = ref<GroupType>('button')
const direction = ref<'row' | 'column'>('row')
const disabled = ref(false)
type IndicatorVariant = 'solid' | 'outline' | 'glass' | 'blur'
const indicatorVariant = ref<IndicatorVariant>('solid')
const glass = ref(false)
const blur = ref(false)
const elastic = ref(true)
const stiffness = ref(110)
const damping = ref(12)
const blurAmount = ref(1)
const value = ref<OptionValue>('a')
const shouldShowDirection = computed(() => type.value !== 'button')
const shouldShowIndicatorProps = computed(() => type.value === 'button')
const resolvedGlass = computed(() => {
if (!shouldShowIndicatorProps.value)
return false
if (indicatorVariant.value === 'glass')
return true
return glass.value
})
const resolvedBlur = computed(() => {
if (!shouldShowIndicatorProps.value)
return false
if (indicatorVariant.value === 'blur')
return true
return blur.value
})
const typeOptions = [
{ value: 'button', label: 'button' },
{ value: 'standard', label: 'standard' },
{ value: 'card', label: 'card' },
]
const directionOptions = [
{ value: 'row', label: 'row' },
{ value: 'column', label: 'column' },
]
</script>
<template>
<div class="tx-demo tx-demo__col" style="max-width: 720px;">
<TxCard variant="plain" background="mask" :padding="14" :radius="14">
<div class="tx-demo__col" style="gap: 12px;">
<div class="tx-demo__row">
<label class="tx-demo__row" style="gap: 8px;">
<span class="tx-demo__label">type</span>
<TuffSelect v-model="type" style="min-width: 180px;">
<TuffSelectItem v-for="opt in typeOptions" :key="opt.value" :value="opt.value" :label="opt.label" />
</TuffSelect>
</label>
<label v-if="shouldShowDirection" class="tx-demo__row" style="gap: 8px;">
<span class="tx-demo__label">direction</span>
<TuffSelect v-model="direction" style="min-width: 180px;">
<TuffSelectItem v-for="opt in directionOptions" :key="opt.value" :value="opt.value" :label="opt.label" />
</TuffSelect>
</label>
</div>
<div class="tx-demo__row">
<label class="tx-demo__row" style="gap: 8px;">
<span class="tx-demo__label">disabled</span>
<TxSwitch v-model="disabled" />
</label>
<label class="tx-demo__row" style="gap: 8px;">
<span class="tx-demo__label">elastic</span>
<TxSwitch v-model="elastic" />
</label>
<label v-if="shouldShowIndicatorProps" class="tx-demo__row" style="gap: 8px;">
<span class="tx-demo__label">indicator</span>
<TuffSelect v-model="indicatorVariant" style="min-width: 180px;">
<TuffSelectItem value="solid" label="solid" />
<TuffSelectItem value="outline" label="outline" />
<TuffSelectItem value="glass" label="glass" />
<TuffSelectItem value="blur" label="blur" />
</TuffSelect>
</label>
<label v-if="shouldShowIndicatorProps" class="tx-demo__row" style="gap: 8px;">
<span class="tx-demo__label">blur</span>
<TxSwitch v-model="blur" />
</label>
<label v-if="shouldShowIndicatorProps" class="tx-demo__row" style="gap: 8px;">
<span class="tx-demo__label">glass</span>
<TxSwitch v-model="glass" />
</label>
</div>
<div class="tx-demo__col" style="gap: 10px;">
<div class="tx-demo__label">
stiffness
</div>
<TxSlider v-model="stiffness" :min="60" :max="220" :step="1" show-value />
</div>
<div class="tx-demo__col" style="gap: 10px;">
<div class="tx-demo__label">
damping
</div>
<TxSlider v-model="damping" :min="4" :max="30" :step="1" show-value />
</div>
<div v-if="shouldShowIndicatorProps && blur" class="tx-demo__col" style="gap: 10px;">
<div class="tx-demo__label">
blurAmount
</div>
<TxSlider v-model="blurAmount" :min="0" :max="24" :step="1" show-value />
</div>
</div>
</TxCard>
<TxCard variant="plain" background="mask" :padding="18" :radius="14" style="width: 100%;">
<TxRadioGroup
v-model="value"
:type="type"
:direction="shouldShowDirection ? direction : undefined"
:disabled="disabled"
:indicator-variant="shouldShowIndicatorProps ? indicatorVariant : undefined"
:glass="resolvedGlass"
:blur="resolvedBlur"
:elastic="elastic"
:stiffness="stiffness"
:damping="damping"
:blur-amount="blurAmount"
>
<TxRadio value="a" :disabled="disabled && type !== 'button'">
<template v-if="type === 'card'">
<div class="tx-demo__title">
Option A
</div>
<div class="tx-demo__desc">
Card description A
</div>
</template>
<template v-else>
Option A
</template>
</TxRadio>
<TxRadio value="b">
<template v-if="type === 'card'">
<div class="tx-demo__title">
Option B
</div>
<div class="tx-demo__desc">
Card description B
</div>
</template>
<template v-else>
Option B
</template>
</TxRadio>
<TxRadio value="c" :disabled="type === 'button' ? false : true">
<template v-if="type === 'card'">
<div class="tx-demo__title">
Option C (disabled)
</div>
<div class="tx-demo__desc">
Disabled in non-button types
</div>
</template>
<template v-else>
Option C
</template>
</TxRadio>
</TxRadioGroup>
<div class="tx-demo__meta" style="margin-top: 12px;">
selected: {{ value }}
</div>
</TxCard>
</div>
</template>
| Prop | Description | Type | Default |
|---|
| modelValue / v-model | Selected radio value. | string | number | - |
| disabled | Disables all radios in the group and blocks keyboard selection. | boolean | false |
| type | Visual style shared by child radios. | 'button' | 'standard' | 'card' | 'button' |
| direction | Layout direction. button always resolves to row, standard defaults to row, and card defaults to column. | 'row' | 'column' | - |
| indicatorVariant | Button-group indicator style. When omitted, glass maps to glass, blur maps to blur, otherwise solid is used. | 'solid' | 'outline' | 'glass' | 'blur' | - |
| glass | Shortcut for the glass button indicator variant. | boolean | false |
| blur | Shortcut for the blur button indicator variant. | boolean | false |
| updateOnSettled | For button groups, delays v-model/change emission until the animated indicator settles. Defaults to enabled for glass or blur indicators. | boolean | - |
| stiffness | Spring stiffness for button indicator motion. Higher values settle faster. | number | 110 |
| damping | Spring damping for button indicator motion. Lower values feel more elastic. | number | 12 |
| blurAmount | Backdrop blur strength for the blur indicator. | number | 1 |
| elastic | Enables stretch/impact motion on the button indicator. | boolean | true |
| Event | Description | Params |
|---|
| update:modelValue | v-model update emitted when an enabled child radio selects a new value. | (value: string | number) => void |
| change | Emitted with the selected value after an enabled selection. | (value: string | number) => void |
| Key | Behavior |
|---|
| ArrowRight / ArrowDown | Select the next enabled radio. |
| ArrowLeft / ArrowUp | Select the previous enabled radio. |
| Home / End | Select the first / last enabled radio. |
| Prop | Description | Type | Default |
|---|
| value | Value written to the parent TxRadioGroup when selected. | string | number | - |
| label | Fallback visible label when no default slot is provided. | string | '' |
| disabled | Disables this radio and removes it from group keyboard navigation. | boolean | false |
| type | Standalone visual style when the radio is not controlled by a group; grouped radios inherit the group type. | 'button' | 'standard' | 'card' | 'button' |
| Event | Description | Params |
|---|
| click | Emitted after an enabled, unchecked radio selects itself through the parent group. | (value: string | number) => void |