Range Slider Bubble Slider
beui/range-slider-bubbleFreeComponent
Grab the thumb and a value bubble pops out of it. The bubble tilts and squashes with how fast you drag, then settles upright.
Install it
npx shadcn@latest add https://beui.dev/r/range-slider-bubble.jsonSource
1"use client";2// beui.dev/components/motion/range-slider34import {5 AnimatePresence,6 motion,7 useMotionTemplate,8 useMotionValue,9 useReducedMotion,10 useSpring,11 useTransform,12 useVelocity,13} from "motion/react";14import { useEffect } from "react";1516import { SPRING_GLIDE, SPRING_PANEL, SPRING_PRESS } from "@/lib/ease";17import { type SliderOptions, useSlider } from "@/lib/hooks/use-slider";18import { cn } from "@/lib/utils";1920// Loose enough that the bubble keeps leaning a beat after the pointer stops.21const SPRING_TILT = { stiffness: 260, damping: 22, mass: 0.4 } as const;22/** Drag speed (px/s of track percent) that maxes out lean and squash. */23const FULL_TILT = 320;2425export interface BubbleSliderProps extends SliderOptions {26 /** Formats the value shown in the bubble. */27 format?: (value: number) => string;28 className?: string;29}3031/**32 * Slider with a value bubble that pops out of the thumb on grab and reacts to33 * how fast you drag: it leans into the direction of travel and squashes along34 * the way, then settles upright when you let go.35 */36export function BubbleSlider({ format, className, ...options }: BubbleSliderProps) {37 const reduce = useReducedMotion();38 // A bare number needs no valueText — it would only repeat aria-valuenow.39 const { percent, current, dragging, trackProps, sliderProps } = useSlider({40 ...options,41 formatValueText: options.formatValueText ?? format,42 });43 // The value is already snapped to the step — rounding here would only make44 // the bubble disagree with aria-valuenow on a fractional scale.45 const readout = format ? format(current) : current;4647 const target = useMotionValue(percent);48 useEffect(() => {49 target.set(percent);50 }, [percent, target]);51 const smooth = useSpring(target, SPRING_GLIDE);52 const pos = reduce ? target : smooth;53 const left = useMotionTemplate`${pos}%`;5455 // One spring drives the whole reaction: lean is signed, squash reads its56 // magnitude. Two springs off the same velocity would just run twice.57 const velocity = useVelocity(pos);58 const lean = useSpring(59 useTransform(velocity, [-FULL_TILT, 0, FULL_TILT], [1, 0, -1], { clamp: true }),60 SPRING_TILT,61 );62 const tilt = useTransform(lean, (v) => v * 16);63 const squash = useTransform(lean, (v) => 1 + Math.abs(v) * 0.18);64 const stretch = useTransform(lean, (v) => 1 - Math.abs(v) * 0.12);6566 return (67 <div68 className={cn(69// … truncated
What it pulls in
npm packages
Files it writes
More from beui
All 104 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Action Swap Bluraction-swap-blur | beui | Components | Free | 3 deps · 4 files | |
| Action Swap Cascadeaction-swap-cascade | beui | Components | Free | 3 deps · 4 files | |
| Action Swap Rollaction-swap-roll | beui | Components | Free | 3 deps · 4 files | |
| Agent Activityagent-activity | beui | Components | Free | 4 deps · 9 files | |
| Agent Loading States Agent Progressagent-progress | beui | Components | Free | 3 deps · 3 files | |
| AI Sidebarai-sidebar | beui | Components | Free | 4 deps · 5 files | |
| Animated Badgeanimated-badge | beui | Components | Free | 4 deps · 3 files | |
| Number Animation Animated Numberanimated-number | beui | Components | Free | 3 deps · 3 files |
