Toggle

Components

Toggle New · experimental

shadcn's Toggle and ToggleGroup with a goo fill. Pressing floods the button from where you pressed, and in a group the goo flows from one item to the next.

Press it, then press it again.

Customize

Preset
Colour
Variant
Size
Speed 1.00×

GoopToggle wraps reka-ui's Toggle, the same primitive as shadcn-vue's Toggle, with the same API, variants, sizes, and classes. v-model, disabled, asChild, keyboard handling, and aria-pressed all come from reka-ui. The pressed background is goo, drawn on a layer inside the button.

Installation

Install @goopui/nuxt or @goopui/vue as shown on the Button page. The Nuxt module auto-imports GoopToggle, GoopToggleGroup, and GoopToggleGroupItem. The goo fill uses shadcn's pressed-toggle token, --accent.

Usage

<script setup lang="ts">
// Nuxt auto-imports this. In plain Vue, import it:
import { GoopToggle } from '@goopui/vue'

const bold = ref(false)
</script>

<template>
  <GoopToggle v-model="bold" aria-label="Toggle bold">
    <span class="font-bold">B</span> Bold
  </GoopToggle>
</template>

What it does

  • flood. Pressing floods the button with goo from the press point (its center for keyboard presses). Un-pressing drains it back into that point.
  • wobble. The flood front has a crest that sloshes against the walls, and the full fill does a small jelly wobble.
  • droplets. A few droplets splash off the top edge as the front reaches it.

aria-pressed and data-state flip on click, as in any reka-ui toggle. Only the fill animates.

Variants and sizes

The same variants and sizes as shadcn:

<GoopToggle default-value>Default</GoopToggle>
<GoopToggle variant="outline" default-value>Outline</GoopToggle>
<GoopToggle size="sm">sm</GoopToggle>
<GoopToggle size="lg">lg</GoopToggle>

Effects, disabled, and goo off

Every effect is a prop. false turns it off, true uses the preset's strength, and a number scales it. A disabled toggle is shadcn's. :goo="false" renders a plain shadcn Toggle.

<GoopToggle disabled>Disabled</GoopToggle>
<GoopToggle :droplets="false" :wobble="false">Flood only</GoopToggle>
<GoopToggle preset="wild">Wild</GoopToggle>
<GoopToggle :goo="false">Plain</GoopToggle>

Toggle group

GoopToggleGroup and GoopToggleGroupItem wrap reka-ui's ToggleGroupRoot and ToggleGroupItem, like shadcn-vue's ToggleGroup. All the pressed fills are one goo layer on the group, which adds two effects:

  • flow. In a type="single" group, the goo leaves the old item: it gathers into a blob, stretches out on a neck, pinches off, travels over the items in between, and floods the new item from the side it arrived on.
  • merge. Neighbouring pressed items join through goo bridges across their seam, so a run of pressed items reads as one body. Un-press one and it drains while the bridges neck and tear.
Pick an alignment. The goo travels between items.
<script setup lang="ts">
import { GoopToggleGroup, GoopToggleGroupItem } from '@goopui/vue'

const align = ref('left')
</script>

<template>
  <GoopToggleGroup v-model="align" type="single" variant="outline" aria-label="Text alignment">
    <GoopToggleGroupItem value="left">Left</GoopToggleGroupItem>
    <GoopToggleGroupItem value="center">Center</GoopToggleGroupItem>
    <GoopToggleGroupItem value="right">Right</GoopToggleGroupItem>
    <GoopToggleGroupItem value="justify">Justify</GoopToggleGroupItem>
  </GoopToggleGroup>
</template>

With type="multiple", pressed neighbours merge:

Press neighbours to merge them, then pull one out.
<GoopToggleGroup v-model="marks" type="multiple" aria-label="Text style">
  <GoopToggleGroupItem value="bold" aria-label="Bold">B</GoopToggleGroupItem>
  <GoopToggleGroupItem value="italic" aria-label="Italic">I</GoopToggleGroupItem>
  <GoopToggleGroupItem value="underline" aria-label="Underline">U</GoopToggleGroupItem>
  <GoopToggleGroupItem value="strike" aria-label="Strikethrough">S</GoopToggleGroupItem>
</GoopToggleGroup>

Set spacing to put a gap between items (0, the default, joins them like shadcn).

Colors

The goo reads --goop-toggle-fill, which defaults to var(--accent), the color shadcn fills a pressed toggle with. Override it on the toggle, the group, or an ancestor:

<GoopToggle class="[--goop-toggle-fill:var(--color-emerald-500)]">Bold</GoopToggle>

Props

GoopToggle

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

GoopToggle props
PropTypeDefaultNotes
modelValuebooleanThe pressed state (v-model).
defaultValuebooleanfalseThe initial pressed state when uncontrolled.
variant'default' | 'outline''default'Same as shadcn.
size'default' | 'sm' | 'lg''default'Same as shadcn.
disabledbooleanfalse
name, requiredPassed to reka-ui.
asChild, asThe element to render, via reka-ui.
classstring | array | objectMerged onto the button.
goobooleantrueMaster switch. false renders a plain shadcn Toggle.
floodboolean | numbertruePressing floods the item with goo from the press point (its center for keyboard presses); un-pressing drains it back there. Off: the fill snaps.
wobbleboolean | numbertrueThe crest of the flood front sloshing against the walls, and a jelly wobble once full.
dropletsboolean | numbertrueA few droplets splash off the top edge as the front reaches it.
textFollowbooleantrueThe label's color follows the goo's edge. false flips it all at once.
speednumber1Animation speed multiplier.
preset'subtle' | 'playful' | 'wild'app config, then playfulThe feel.
renderer'svg' | 'canvas''svg'How the goo is drawn.

The default slot receives reka-ui's slot props. GoopToggle also exposes handle (the live toggle handle, null before mount or with goo off) and el (the button).

GoopToggleGroup

Keeps the shadcn-vue ToggleGroup API and adds the goo props, including flow and merge.

GoopToggleGroup props
PropTypeDefaultNotes
type'single' | 'multiple'single: at most one pressed item, and the value is that item's. multiple: an array.
modelValuevalue | value[]The pressed value(s) (v-model).
defaultValuevalue | value[]The initial value when uncontrolled.
variant'default' | 'outline'Shared with every item.
size'default' | 'sm' | 'lg'Shared with every item.
spacingnumber0Gap between items in Tailwind spacing units. 0 joins them, like shadcn.
orientation'horizontal' | 'vertical'Arrow-key direction, via reka-ui.
rovingFocusbooleantrueArrow keys move focus between items.
loopbooleantrueArrow keys wrap around.
dir'ltr' | 'rtl'Reading direction.
disabledbooleanfalseDisables every item.
name, requiredPassed to reka-ui.
asChild, asThe element to render, via reka-ui.
classstring | array | objectMerged onto the root.
goobooleantrueMaster switch. false renders a plain shadcn ToggleGroup.
floodboolean | numbertruePressing floods the item with goo from the press point (its center for keyboard presses); un-pressing drains it back there. Off: the fill snaps.
wobbleboolean | numbertrueThe crest of the flood front sloshing against the walls, and a jelly wobble once full.
dropletsboolean | numbertrueA few droplets splash off the top edge as the front reaches it.
flowboolean | numbertrueSingle mode: the old item drains toward the new one, its goo stretches out on a neck, pinches off, travels over the items between and floods the new item from that side.
mergeboolean | numbertrueNeighbouring pressed items join through goo bridges across their seam, and tear apart when one is un-pressed.
textFollowbooleantrueThe label's color follows the goo's edge. false flips it all at once.
speednumber1Animation speed multiplier.
preset'subtle' | 'playful' | 'wild'app config, then playfulThe feel.
renderer'svg' | 'canvas''svg'How the goo is drawn.

It exposes handle and el (the root, role="group").

GoopToggleGroupItem

Keeps the shadcn-vue ToggleGroupItem API: value (required), disabled, variant, size, asChild, as, and class. It has no goo props of its own. variant and size come from the group when the group sets them. Inside an active GoopToggleGroup, a pressed item drops its own background, because the group's goo is the fill.

Before they mount, during SSR, and with :goo="false", they are plain shadcn toggles, so first paint and hydration match.

Accessibility

They're real reka-ui toggles: buttons with aria-pressed, a focus ring, and Space or Enter to press. A group uses roving focus with arrow keys. The goo layers are aria-hidden and can't be clicked, and labels and icons are never filtered.

Under reduced motion the goo keeps its shape and the animation stops: pressed items are full and unpressed ones empty, merged neighbours stay merged, and nothing floods, drains, splashes, or travels. See Accessibility & reduced motion.