Components
Switch
shadcn's Switch with a goo thumb. It stretches across the track, splats against the wall, and floods the track with color.
Customize
GoopSwitch wraps reka-ui's SwitchRoot and SwitchThumb, the same primitive as shadcn-vue's Switch, with the same API, classes, and sizes. v-model, disabled, name, required, value, asChild, keyboard handling, role="switch", and aria-checked all come from reka-ui. The goo is a layer inside the track.
Installation
Install @goopui/nuxt or @goopui/vue as shown on the Button page. The Nuxt module auto-imports GoopSwitch. It uses the same shadcn tokens as the plain Switch (--primary, --input, --background, --foreground, --primary-foreground, --ring).
Usage
<script setup lang="ts">
// Nuxt auto-imports this. In plain Vue, import it:
import { GoopSwitch } from '@goopui/vue'
const airplane = ref(true)
</script>
<template>
<label class="flex items-center gap-2 text-sm">
<GoopSwitch v-model="airplane" />
Airplane mode
</label>
</template>
What it does
Each toggle is one move with several parts. Each part is an effect you can turn off:
- stretch. The thumb leaves slowly, because the goo sticks. It speeds up, leans, and trails a teardrop tail.
- bridge. A blob stays stuck at the start, joined by a neck that thins and snaps.
- squish. The wobble after it lands.
- splat. The thumb hits the far wall and flattens like a water balloon, then rebounds.
- droplets. A few drops fly off the wall on impact and fall back in.
- wave. The track keeps its old color while the thumb travels, so the thumb always contrasts it. On impact the new color floods in from the wall. If you toggle again mid-wave, it keeps moving from where it is, with no color flash.
- fill. The primary color is goo, not a flat background.
aria-checked and data-state flip on click, as in any reka-ui switch. Only the colors wait for the impact.
Sizes
The same two sizes as shadcn:
<GoopSwitch size="sm" default-value />
<GoopSwitch default-value />
Effects and presets
Every effect is a prop. false turns it off, true uses the preset's strength, and a number scales it (0 is off). fill is on or off only. preset, speed, and renderer set the base.
<GoopSwitch default-value :splat="false" :droplets="false" />
<GoopSwitch :fill="false" default-value />
<GoopSwitch preset="wild" />
<GoopSwitch preset="subtle" />
<GoopSwitch :speed="0.5" />
Disabled and goo off
A disabled switch is shadcn's: dimmed, and it ignores clicks. :goo="false" renders a plain shadcn Switch.
<GoopSwitch disabled />
<GoopSwitch disabled default-value />
<GoopSwitch :goo="false" />
Colors
The goo reads two CSS variables from the switch, set from the shadcn tokens. Override them on the element or an ancestor:
| Variable | Default | What it colors |
|---|---|---|
--goop-switch-fill | var(--primary) | The track fill, the wave, and the droplets. |
--goop-switch-thumb | var(--background) | The thumb. In dark mode it follows --foreground when off and --primary-foreground when on, like shadcn. |
<GoopSwitch class="[--goop-switch-fill:var(--color-emerald-500)] data-checked:border-emerald-500" />
Props
GoopSwitch keeps the shadcn-vue Switch API and adds the goo props.
| Prop | Type | Default | Notes |
|---|---|---|---|
modelValue | boolean | The controlled state (v-model). | |
defaultValue | boolean | false | The initial state when uncontrolled. |
size | 'default' | 'sm' | 'default' | Same as shadcn. |
disabled | boolean | false | |
id, name, value, required | Passed to reka-ui. name and value make it submit with a form. | ||
asChild, as | The element to render, via reka-ui. | ||
class | string | array | object | Merged onto the track. | |
goo | boolean | true | Master switch. false renders a plain shadcn Switch. |
stretch | boolean | number | true | The thumb leaves slowly, because the goo sticks, then speeds up, leans, and trails a teardrop tail. |
bridge | boolean | number | true | A blob stays stuck at the start, joined by a neck that thins and snaps. |
squish | boolean | number | true | The wobble after it lands. |
splat | boolean | number | true | The thumb hits the far wall and flattens like a water balloon, then rebounds. |
droplets | boolean | number | true | A few drops fly off the wall on impact and fall back in. |
wave | boolean | number | true | The new colour floods the track from the wall on impact, instead of switching at once. |
fill | boolean | true | The primary colour is goo, not a flat background. |
speed | number | 1 | Speed multiplier. |
preset | 'subtle' | 'playful' | 'wild' | app config, then playful | The feel. |
renderer | 'svg' | 'canvas' | 'svg' | How the goo is drawn. |
The thumb slot renders inside the thumb (for an icon, say) and receives reka-ui's slot props. GoopSwitch also exposes handle (the live switch handle, null before mount or with goo off) and el (the track).
Before it mounts, during SSR, and with :goo="false", it is a plain shadcn Switch, so first paint and hydration match.
Accessibility
It's a real reka-ui switch: role="switch", aria-checked, focus ring, Space to toggle. The goo layers are aria-hidden and can't be clicked. The real thumb stays in place, transparent, so hit area and focus behave as usual.
Reduced motion keeps the goo shape and turns off the simulation: the thumb and fill jump straight to the end state, with no travel, splat, droplets, or wave. See Accessibility & reduced motion.