Components
Spinner
shadcn's Spinner, drawn in goo. Blobs chase, merge, and split. Poke it and it sloshes.
Customize
GoopSpinner keeps the shadcn-vue Spinner API: a role="status" element with aria-label="Loading", sized with classes like size-6, colored with currentColor. shadcn's spinner is a lucide icon, so there is no reka-ui primitive underneath. The goo is added on top.
Installation
Install @goopui/nuxt or @goopui/vue as shown on the Button page. The Nuxt module auto-imports GoopSpinner. In plain Vue, import it from @goopui/vue.
Usage
<script setup lang="ts">
// Nuxt auto-imports this. In plain Vue, import it:
import { GoopSpinner } from '@goopui/vue'
</script>
<template>
<GoopSpinner class="size-6" />
</template>
The default size is size-4, like shadcn's. Set any size with a class. The color is currentColor, so text-primary or text-destructive recolors it.
<GoopSpinner class="size-4" />
<GoopSpinner class="size-6" />
<GoopSpinner class="size-8" />
<GoopSpinner class="size-12" />
<GoopSpinner class="size-6 text-primary" />
<GoopSpinner class="size-6 text-destructive" />
Variants
Five shapes. orbit is the default.
orbit: blobs chase each other round a circle. They surge apart and catch up, so they merge and split.comet: a spinning arc with a tail.ring: a bead rolling round a donut.dots: a bead rolling along three dots.pulse: a core that buds satellites.
<GoopSpinner variant="orbit" />
<GoopSpinner variant="comet" />
<GoopSpinner variant="ring" />
<GoopSpinner variant="dots" />
<GoopSpinner variant="pulse" />
In a button
Put it in a disabled GoopButton, the same way shadcn does:
<GoopButton disabled>
<GoopSpinner data-icon="inline-start" />
Saving
</GoopButton>
<GoopButton variant="outline" disabled>
<GoopSpinner data-icon="inline-start" variant="comet" />
Loading
</GoopButton>
Tuning
Props change the feel. A preset sets the base, and the props override it.
<GoopSpinner :blobs="6" />
<GoopSpinner :speed="2" />
<GoopSpinner drips />
<GoopSpinner preset="wild" />
<GoopSpinner :interactive="false" />
Play
A spinner is playful by default (interactive):
- Move the pointer over it and the goo sloshes (
slosh). - Press and drag to pull it out in lumps. Release and it snaps back (
grab). - Click or tap to squish it. It swells back with a ripple (
pop).
:interactive="false" turns all three off and makes the spinner pointer-events: none again. Use it for a spinner that sits on top of something clickable. slosh, grab, and pop each turn off on their own.
Props
GoopSpinner keeps the shadcn-vue Spinner API (class) and adds the goo props.
| Prop | Type | Default | Notes |
|---|---|---|---|
class | string | array | object | Merged onto the root, like shadcn. Set the size here. | |
variant | 'orbit' | 'comet' | 'ring' | 'dots' | 'pulse' | 'orbit' | The shape. |
blobs | number | 4 | Number of blobs, 3 to 6. Used by orbit and pulse. |
speed | number | 1 | Speed multiplier. 1 is about 1.15s per turn on playful. |
preset | 'subtle' | 'playful' | 'wild' | app config, then playful | The feel. |
drips | boolean | false | Drips hang off the bottom and fall away as it turns. orbit only. |
interactive | boolean | true | Master switch for slosh, grab, and pop. |
slosh | boolean | true | Pointer movement sloshes the goo. |
grab | boolean | true | Press and drag pulls lumps out. |
pop | boolean | true | Click or tap squishes it, then it swells back. |
renderer | 'svg' | 'canvas' | 'svg' | How the goo is drawn. |
It exposes handle (the live spinner handle, null before mount) and el.
Before it mounts, and during SSR, the spinner renders a static inline SVG of its merged pose. First paint and hydration match, and nothing shifts when the live version takes over. It pauses when it is offscreen or the tab is hidden.
Accessibility
The root is role="status" with aria-label="Loading", the same as shadcn's. Override the label with aria-label.
Reduced motion keeps the goo shape and turns off the simulation: the spinner shows a still, merged pose, and the slosh, grab, and pop interactions are off. See Accessibility & reduced motion.