Liquid Slider
A physics-based liquid glass range slider with refractive bezel effects, specular highlights, and smooth drag interactions.
Preview
Value: 50
'use client';
import { useState } from 'react';
import { LiquidSlider } from '@/components/ui/liquid-slider';
export default function Demo() {
const [value, setValue] = useState(50);
return (
<LiquidSlider
min={0}
max={100}
value={value}
className="w-full"
events={{ input: ({ value }) => setValue(value) }}
/>
);
}Installation
Install the dependency
LiquidSlider is powered by the @avenra/liquid-glass package.
npm install @avenra/liquid-glasspnpm add @avenra/liquid-glassyarn add @avenra/liquid-glassbun add @avenra/liquid-glassAdd the component via shadcn CLI
npx shadcn@latest add https://avenra.online/r/liquid-slider.jsonpnpm dlx shadcn@latest add https://avenra.online/r/liquid-slider.jsonyarn dlx shadcn@latest add https://avenra.online/r/liquid-slider.jsonbunx shadcn@latest add https://avenra.online/r/liquid-slider.jsonThis places the component at components/ui/liquid-slider.tsx.
Manual installation
Create components/ui/liquid-slider.tsx with the following content:
'use client';
import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
import {
createLiquidSlider,
LiquidSliderHandle,
LiquidSliderEventMap,
LiquidSliderOptions,
} from '@avenra/liquid-glass';
export interface LiquidSliderProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
min: number;
max: number;
value: number;
options?: LiquidSliderOptions;
events?: {
[K in keyof LiquidSliderEventMap]?: (payload: LiquidSliderEventMap[K]) => void;
};
}
export const LiquidSlider = forwardRef<HTMLDivElement, LiquidSliderProps>(
({ min, max, value, options, events, ...props }, ref) => {
const containerRef = useRef<HTMLDivElement>(null);
const handleRef = useRef<LiquidSliderHandle | null>(null);
useImperativeHandle(ref, () => containerRef.current as HTMLDivElement);
useEffect(() => {
if (!containerRef.current) return;
if (containerRef.current.children.length > 0) return; // already initialized
handleRef.current = createLiquidSlider(containerRef.current, {
min, max, value, blur: 0, ...options,
});
return () => { handleRef.current?.destroy(); handleRef.current = null; };
}, []);
useEffect(() => {
if (!handleRef.current) return;
handleRef.current.value = value;
}, [value]);
useEffect(() => {
if (!handleRef.current) return;
handleRef.current.value = Math.min(Math.max(value, min), max);
}, [min, max]);
useEffect(() => {
if (!handleRef.current || !events) return;
const handle = handleRef.current;
for (const key in events) {
const event = key as keyof LiquidSliderEventMap;
const handler = events[event];
if (handler) handle.on(event, handler as (payload: LiquidSliderEventMap[typeof event]) => void);
}
return () => {
if (!handleRef.current || !events) return;
for (const key in events) {
const event = key as keyof LiquidSliderEventMap;
const handler = events[event];
if (handler) handleRef.current.off(event, handler as (payload: LiquidSliderEventMap[typeof event]) => void);
}
};
}, [events]);
return <div ref={containerRef} {...props} />;
},
);
LiquidSlider.displayName = 'LiquidSlider';Usage
import { LiquidSlider } from '@/components/ui/liquid-slider';'use client';
import { useState } from 'react';
import { LiquidSlider } from '@/components/ui/liquid-slider';
export default function Example() {
const [value, setValue] = useState(50);
return (
<LiquidSlider
min={0}
max={100}
value={value}
className="w-full"
events={{ input: ({ value }) => setValue(value) }}
/>
);
}
LiquidSlideris a controlled component — you must supply avalueprop and update it via theevents.inputhandler. The component will not update its position without an external state change.
Examples
Controlled with external buttons
Drive the slider value from outside the component using React state. The glass handle position updates reactively whenever value changes.
'use client';
import { useState } from 'react';
import { LiquidSlider } from '@/components/ui/liquid-slider';
export default function Example() {
const [value, setValue] = useState(30);
return (
<div className="flex flex-col gap-4">
<LiquidSlider
min={0}
max={100}
value={value}
className="w-full"
events={{ input: ({ value }) => setValue(value) }}
/>
<div className="flex items-center gap-3">
<button onClick={() => setValue((v) => Math.max(0, v - 10))}>−10</button>
<span>{value}</span>
<button onClick={() => setValue((v) => Math.min(100, v + 10))}>+10</button>
</div>
</div>
);
}Step snapping
Use the step option inside options to snap the thumb to discrete intervals.
Snaps to: 0 · 25 · 50 · 75 · 100 — current: 25
<LiquidSlider
min={0}
max={100}
value={value}
options={{ step: 25 }}
className="w-full"
events={{ input: ({ value }) => setValue(value) }}
/>Custom range
min and max accept any numeric range — negative values are supported. The slider clamps the current value automatically when the range changes.
Temperature: 20°C
<LiquidSlider
min={-20}
max={50}
value={temp}
className="w-full"
events={{ input: ({ value }) => setTemp(value) }}
/>input vs change events
input fires continuously while dragging (like oninput on a native range). change fires once on pointer-up (like onchange). Use change for expensive operations such as API calls.
<LiquidSlider
min={0}
max={100}
value={live}
className="w-full"
events={{
input: ({ value }) => setLive(value), // every frame
change: ({ value }) => commitToServer(value), // pointer-up only
}}
/>Forwarded ref
LiquidSlider forwards its ref to the underlying <div> container, giving you direct DOM access.
'use client';
import { useRef } from 'react';
import { LiquidSlider } from '@/components/ui/liquid-slider';
export default function Example() {
const sliderRef = useRef<HTMLDivElement>(null);
return (
<LiquidSlider
ref={sliderRef}
min={0}
max={100}
value={50}
className="w-full"
/>
);
}Glass appearance options
All GlassOptions from the shared engine are available via the options prop — the same knobs exposed by LiquidButton.
<LiquidSlider
min={0}
max={100}
value={value}
className="w-full"
options={{
bezelWidth: 12,
refractiveIndex: 1.8,
specularSlope: 1,
saturation: 1.6,
profile: 'convexSquircle',
}}
events={{ input: ({ value }) => setValue(value) }}
/>API Reference
LiquidSliderProps
Prop
Type
All standard HTMLDivElement attributes (className, style, aria-*, etc.) are forwarded via rest props. onChange is intentionally excluded — use events.change instead.
LiquidSliderOptions
LiquidSliderOptions extends GlassOptions with slider-specific fields.
Prop
Type
LiquidSliderEventMap
Prop
Type
Liquid Button
A physics-based liquid glass button with refractive bezel effects, specular highlights, and smooth interaction animations.
Liquid Switch
A physics-based liquid glass toggle switch with refractive bezel effects, specular highlights, and fluid on/off animations. Supports both controlled and uncontrolled usage.