Components
Slider
shadcn's Slider with a liquid fill and a jelly thumb. Drag fast, then stop, and watch it settle.
Customize
Installation
Nuxt
pnpm add @goopui/nuxt
export default defineNuxtConfig({
modules: ['@goopui/nuxt'],
})
The module auto-imports GoopSlider and adds goop.css. See Installation for the module options.
Vue
pnpm add @goopui/vue
@import "tailwindcss";
@import "@goopui/vue/goop.css";
Usage
GoopSlider wraps the same reka-ui Slider as shadcn-vue's Slider. The API is the same: v-model is an array with one number per thumb, and min, max, step, orientation, dir, inverted, disabled, name, and minStepsBetweenThumbs pass through to reka-ui.
<script setup lang="ts">
// Nuxt auto-imports this. In plain Vue, import it:
import { GoopSlider } from '@goopui/vue'
const volume = ref([50])
</script>
<template>
<GoopSlider v-model="volume" :max="100" :step="1" />
</template>
It emits update:modelValue while you drag, and valueCommit when you let go.
Behavior
- Jelly. The thumb follows your pointer on a spring. It lags, overshoots, and wobbles to rest (
jelly). - Slosh. The surface of the fill piles up toward the front when you move fast, then sloshes back and waves (
slosh). - Stretch. The thumb stretches along the track with speed (
stretch). - Splat. Click the track and the thumb jumps there, landing with a small splat (
splat). - Layout never changes. The goo is drawn in one
aria-hiddenlayer on the slider root. The real range and thumbs stay in place and go see-through, so reka-ui keeps drag, keyboard, focus, and ARIA exactly as they were. - The thumb keeps its hover and focus ring.
Range
Pass two numbers for a range slider. The liquid fills between the thumbs:
<GoopSlider v-model="range" :max="100" :step="1" />
const range = ref([25, 75])
Vertical
<div class="h-44">
<GoopSlider v-model="level" orientation="vertical" :max="100" />
</div>
Steps
With step, every move is a small jump. The thumb still lags and settles.
<GoopSlider v-model="quality" :max="100" :step="10" />
Effects and presets
Each effect is a prop. false turns it off, true uses the preset's strength, and a number from 0 to 2 scales it. preset picks subtle, playful (default), or wild for the whole slider. The first slider below has no jelly or stretch, then come wild and subtle:
<GoopSlider :default-value="[40]" :jelly="false" :stretch="false" />
<GoopSlider :default-value="[60]" preset="wild" />
<GoopSlider :default-value="[60]" preset="subtle" />
Disabled
A disabled slider ignores the pointer and goes to half opacity, like shadcn's.
<GoopSlider :default-value="[40]" disabled />
Use :goo="false" to render a plain shadcn Slider.
Color
The goo is painted in --primary. To change it, set --goop-slider-color on the slider or any ancestor:
<GoopSlider :default-value="[50]" class="[--goop-slider-color:var(--destructive)]" />
The track keeps shadcn's bg-muted. Style it with class as you would in shadcn.
Driving it from code
GoopSlider exposes the core handle and its root element. handle.measure() re-measures the thumbs after you change the layout by hand. handle is null before mount and when goo is off.
<script setup lang="ts">
const slider = useTemplateRef('slider')
</script>
<template>
<GoopSlider ref="slider" :default-value="[50]" />
<button @click="slider?.handle?.measure()">Re-measure</button>
</template>
Props
GoopSlider keeps the shadcn-vue Slider API and adds the goo props.
| Prop | Type | Default | Notes |
|---|---|---|---|
modelValue | number[] | One number per thumb (v-model). | |
defaultValue | number[] | The initial value when uncontrolled. | |
min, max, step | number | 0, 100, 1 | Same as reka-ui. |
orientation | 'horizontal' | 'vertical' | 'horizontal' | The goo follows it. |
dir | 'ltr' | 'rtl' | Reading direction. | |
inverted | boolean | false | Fill from the other end. |
minStepsBetweenThumbs | number | For range sliders. | |
thumbAlignment | 'contain' | 'overflow' | Passed to reka-ui. | |
disabled, name, required | Same as reka-ui. | ||
as, asChild | Render a different element. | ||
class | string | array | object | Merged onto the root, like shadcn. | |
goo | boolean | true | Master switch. false renders a plain shadcn Slider. |
jelly | boolean | number | true | The thumb lags on a spring, overshoots, and wobbles to rest. |
slosh | boolean | number | true | The fill's surface piles up, sloshes back, and waves. |
stretch | boolean | number | true | The thumb stretches along the slider with speed. |
splat | boolean | number | true | A jumping thumb lands with a small splat. |
speed | number | 1 | Animation speed multiplier. |
preset | 'subtle' | 'playful' | 'wild' | 'playful' | The feel. Falls back to the app config, then --goop-preset. |
renderer | 'svg' | 'canvas' | 'svg' | How the goo is drawn. |
Effect props take true, false, or a number from 0 to 2. Props win over the app-wide config (GoopPlugin or the module options), which wins over the preset.
Accessibility
It's a real reka-ui slider: each thumb has role="slider" and the ARIA values, and arrow keys, Home, End, and Page Up/Down work as in shadcn. The goo layer is aria-hidden and can't be clicked. An aria-label you pass lands on the slider root, as in shadcn. Under reduced motion the goo keeps its shape and the effects turn off: no jelly, slosh, stretch, or splat. The thumb just follows the value. See Accessibility & reduced motion.