Components

Elastic Slider

A budget slider drawn as a ruler of ticks that swell under your cursor and stretch like an accordion when you pull past the ends.

A slider drawn as a ruler of ticks with a big rolling number above it. Move your cursor over the ruler and the ticks swell in a smooth wave that follows it. Ticks up to the value fill with the accent. Drag and the wave grows taller and the indicator stretches. Pull past either end and the whole ruler stretches apart like an accordion: the further you pull, the more it resists. Let go and it springs back. The minus and plus buttons step the value by buttonStep. Press one at the limit and the ruler gives a small elastic kick instead. Every digit rolls with Number Flow. Driven by GSAP.

New tab

Installation

pnpm dlx shadcn@latest add @tween-ui/elastic-slider

Using an AI editor? Set up MCP once, then just ask for it in plain words.

Usage

app/page.tsx
app/page.tsx
'use client';
 
import { useState } from 'react';
import ElasticSlider from '@/components/tweenui/elastic-slider';
 
export default function Page() {
  const [budget, setBudget] = useState(7500);
  return (
    <ElasticSlider
      label="Monthly budget"
      min={0}
      max={20000}
      step={100}
      buttonStep={100}
      value={budget}
      onValueChange={setBudget}
      prefix="$"
    />
  );
}

How it moves

  • Wave. Each tick grows by a bell curve around a focus point 30px wide. The focus follows your cursor while you hover or drag and glides back to the value when you leave, on a 500ms power3.out ease.
  • Indicator. Follows the value on a 400ms power3.out ease, so button presses and arrow keys glide instead of jumping.
  • Stretch. Overdrag runs through a rubber-band curve that tops out at 28px. The ruler is pinned at the far end, so ticks near your pointer move the most. On release it springs back on elastic.out(1, 0.32) over 900ms.
  • Buttons. The icon pops on back.out. At the limit the ruler kicks 14px toward the button and springs back.

Props

PropTypeDefaultDescription
labelstring—Visible label and accessible name.
minnumber0Lowest value.
maxnumber100Highest value.
stepnumber1Dragged and typed values snap to this step.
buttonStepnumber—Shows the minus/plus pill, stepping by this amount.
valuenumber—Controlled value. Pair it with onValueChange.
defaultValuenumber50Starting value when uncontrolled.
onValueChange(value) => void—Called with every new snapped value.
formatFormatgrouping onIntl-style options passed to Number Flow.
prefixstring—Text before the number, e.g. $.
suffixstring—Text after the number, e.g. %.
ticksnumber41Number of ticks. Every fifth one is taller.
disabledbooleanfalseDims the slider and ignores input.
classNamestring—Extra classes for the root.

The accent comes from --elastic-accent. Override it with a class, e.g. [--elastic-accent:#e11d48]. Text and the unfilled ticks take currentColor.

Accessibility

Under prefers-reduced-motion: reduce the ticks stay flat, the ruler never stretches, the indicator jumps straight to the value and the digits change without rolling.

Under the ruler sits a real <input type="range"> with a <label>, so arrow keys, Page Up and Down, Home and End, and screen readers all work natively. aria-valuetext reads the formatted value, such as "$7,500". The minus and plus are real buttons labelled "Decrease Monthly budget by $100" and "Increase Monthly budget by $100". Pressing the ruler focuses the input, and keyboard focus draws a ring around it.