Since 1.0.0BETA

Radio

`TxRadioGroup` + `TxRadio`****+********

This page was migrated by AI, please review carefully

Migration is complete, but please validate against source code and manual review.

Radio

TxRadioGroup + TxRadio****+********

Standard Single-Select

Standard single-select with a dot + label, good for many options.

Radio (standard)

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>

Card Single-Select Style

Single-select emphasizing hierarchy, good for explanatory text.

Radio (card)

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>

Button Group Style

Compact button group, good for few options.

Radio (simple)

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>

Indicator Animation

Button group animation with indicator: default → normal → blur → glass.

Radio (indicator)

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>

Disabled State

Radio (disabled)

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>

Playground

A control panel for TxRadioGroup props to quickly validate variants/params.

RadioGroup (playground)

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>

API

TxRadioGroup

Props

PropDescriptionTypeDefault
modelValue / v-modelSelected radio value.string | number-
disabledDisables all radios in the group and blocks keyboard selection.booleanfalse
typeVisual style shared by child radios.'button' | 'standard' | 'card''button'
directionLayout direction. button always resolves to row, standard defaults to row, and card defaults to column.'row' | 'column'-
indicatorVariantButton-group indicator style. When omitted, glass maps to glass, blur maps to blur, otherwise solid is used.'solid' | 'outline' | 'glass' | 'blur'-
glassShortcut for the glass button indicator variant.booleanfalse
blurShortcut for the blur button indicator variant.booleanfalse
updateOnSettledFor button groups, delays v-model/change emission until the animated indicator settles. Defaults to enabled for glass or blur indicators.boolean-
stiffnessSpring stiffness for button indicator motion. Higher values settle faster.number110
dampingSpring damping for button indicator motion. Lower values feel more elastic.number12
blurAmountBackdrop blur strength for the blur indicator.number1
elasticEnables stretch/impact motion on the button indicator.booleantrue

Events

EventDescriptionParams
update:modelValuev-model update emitted when an enabled child radio selects a new value.(value: string | number) => void
changeEmitted with the selected value after an enabled selection.(value: string | number) => void

Keyboard

KeyBehavior
ArrowRight / ArrowDownSelect the next enabled radio.
ArrowLeft / ArrowUpSelect the previous enabled radio.
Home / EndSelect the first / last enabled radio.

TxRadio

Props

PropDescriptionTypeDefault
valueValue written to the parent TxRadioGroup when selected.string | number-
labelFallback visible label when no default slot is provided.string''
disabledDisables this radio and removes it from group keyboard navigation.booleanfalse
typeStandalone visual style when the radio is not controlled by a group; grouped radios inherit the group type.'button' | 'standard' | 'card''button'

Events

EventDescriptionParams
clickEmitted after an enabled, unchecked radio selects itself through the parent group.(value: string | number) => void