Radio Group

Components

Radio Group

shadcn's Radio Group where the selected dot is a blob of goo that stretches, flies, and splats onto the next item.

v-model: pro

Click an item, or focus the group and use the arrow keys.

Customize

Preset
Colour
Speed 1.00×

GoopRadioGroup and GoopRadioGroupItem wrap reka-ui's RadioGroupRoot, RadioGroupItem, and RadioGroupIndicator, the same primitive as shadcn-vue's RadioGroup, with the same API and classes. v-model, orientation, loop, dir, disabled, name, required, asChild, roving focus, and arrow-key selection all come from reka-ui. The goo is one layer on the group root.

Installation

Install @goopui/nuxt or @goopui/vue as shown on the Button page. The Nuxt module auto-imports GoopRadioGroup and GoopRadioGroupItem. It uses the same shadcn tokens as the plain Radio Group (--primary, --primary-foreground, --input, --ring).

Usage

<script setup lang="ts">
// Nuxt auto-imports these. In plain Vue, import them:
import { GoopRadioGroup, GoopRadioGroupItem } from '@goopui/vue'

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

<template>
  <GoopRadioGroup v-model="plan">
    <label v-for="p in ['free', 'pro', 'team']" :key="p" class="flex items-center gap-2 text-sm capitalize">
      <GoopRadioGroupItem :id="`plan-${p}`" :value="p" />
      {{ p }}
    </label>
  </GoopRadioGroup>
</template>

What it does

Selecting a different item moves one blob of goo from the old item to the new one:

  • bridge. The blob stretches out of the old item on a fat goo bridge. The bridge snaps.
  • Flight. It flies to the new item, stretched along its path with a teardrop tail. It makes a small hop on sideways jumps.
  • splat. It lands flat along the flight, bulging across it, then rebounds and wobbles.
  • droplets. A few drops splash off the landing.

The dot is the same unfiltered one shadcn renders. It is held back until the splat has mostly settled. The animation works in any direction, so keyboard selection animates too.

Horizontal

Set orientation="horizontal" and lay the group out yourself, as with shadcn:

<GoopRadioGroup v-model="size" orientation="horizontal" class="flex gap-6">
  <label v-for="s in ['s', 'm', 'l', 'xl']" :key="s" class="flex items-center gap-2 text-sm uppercase">
    <GoopRadioGroupItem :id="`size-${s}`" :value="s" />
    {{ s }}
  </label>
</GoopRadioGroup>

Effects, presets, and sizes

The goo props go on GoopRadioGroup. Each effect takes false (off), true (the preset's strength), or a number that scales it (0 is off). The item size is a class on GoopRadioGroupItem, as in shadcn, and the blob follows it.

<!-- No bridge -->
<GoopRadioGroup default-value="a" orientation="horizontal" class="flex gap-6" :bridge="false">
  <GoopRadioGroupItem value="a" />
  <GoopRadioGroupItem value="b" />
  <GoopRadioGroupItem value="c" />
</GoopRadioGroup>

<!-- Wild -->
<GoopRadioGroup default-value="a" orientation="horizontal" class="flex gap-6" preset="wild">
  ...
</GoopRadioGroup>

<!-- Bigger items -->
<GoopRadioGroup default-value="a" orientation="horizontal" class="flex gap-10">
  <GoopRadioGroupItem value="a" class="size-7" />
  ...
</GoopRadioGroup>

Disabled and goo off

A disabled item is shadcn's: dimmed, and it can't be selected. :goo="false" on the group renders a plain shadcn Radio Group.

<GoopRadioGroup default-value="one">
  <GoopRadioGroupItem value="one" />
  <GoopRadioGroupItem value="two" />
  <GoopRadioGroupItem value="three" disabled />
</GoopRadioGroup>

<GoopRadioGroup default-value="a" :goo="false">
  <GoopRadioGroupItem value="a" />
  <GoopRadioGroupItem value="b" />
</GoopRadioGroup>

Colors

The blob uses the --goop-radio-fill variable on the group, which defaults to var(--primary), the color shadcn fills the checked item with. Override it on the group or an ancestor:

<GoopRadioGroup class="[--goop-radio-fill:var(--color-emerald-500)]">
  <GoopRadioGroupItem value="a" class="data-checked:border-emerald-500" />
  ...
</GoopRadioGroup>

Props

GoopRadioGroup

Keeps the shadcn-vue RadioGroup API and adds the goo props.

GoopRadioGroup props
PropTypeDefaultNotes
modelValuestring | number | boolean | objectThe controlled value (v-model).
defaultValuesameThe initial value when uncontrolled.
orientation'vertical' | 'horizontal'Passed to reka-ui. It sets arrow-key direction.
dir'ltr' | 'rtl'Reading direction.
loopbooleantrueArrow keys wrap around.
disabledbooleanfalseDisables every item.
name, requiredPassed to reka-ui.
asChild, asThe element to render, via reka-ui.
classstring | array | objectMerged onto the root. The default is grid gap-2 w-full.
goobooleantrueMaster switch. false renders a plain shadcn Radio Group.
bridgeboolean | numbertrueThe blob stretches from the old item to the new one on a neck that snaps.
splatboolean | numbertrueThe blob lands in the new item with a splat and a wobble.
dropletsboolean | numbertrueA few droplets fly off on landing.
speednumber1Speed multiplier.
preset'subtle' | 'playful' | 'wild'app config, then playfulThe feel.
renderer'svg' | 'canvas''svg'How the goo is drawn.

It exposes handle (the live radio group handle, null before mount or with goo off) and el (the root).

GoopRadioGroupItem

Keeps the shadcn-vue RadioGroupItem API. It has no goo props of its own. Inside an active GoopRadioGroup, the checked item drops its own fill, because the group's blob is the fill.

GoopRadioGroupItem props
PropTypeDefaultNotes
valuestring | number | boolean | objectThis item's value.
disabledbooleanfalse
id, name, requiredPassed to reka-ui.
asChild, asThe element to render, via reka-ui.
classstring | array | objectMerged onto the item. Set the size here.

The default slot replaces the dot.

Before it mounts, during SSR, and with :goo="false", it is a plain shadcn Radio Group, so first paint and hydration match.

Accessibility

It's a real reka-ui radio group: role="radiogroup" with role="radio" items, roving focus, and arrow keys that move and select. The goo layer is aria-hidden and can't be clicked.

Reduced motion keeps the goo shape and turns off the simulation: the blob sits on the selected item straight away, with no bridge, flight, splat, or droplets. See Accessibility & reduced motion.