.agents/skills/tanstack-ranger/SKILL.md
Headless utilities for building range and multi-range sliders in TS/JS, React, Vue, Solid, Svelte & Angular.
npx skillsauth add iEnergyy/opsflow tanstack-rangerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
TanStack Ranger provides headless utilities for building fully accessible range and multi-range slider components. It handles all the complex logic for single value, range, and multi-thumb sliders while giving you complete control over styling and markup.
Package: @tanstack/react-ranger
Core: @tanstack/ranger-core (framework-agnostic)
Status: Stable
npm install @tanstack/react-ranger
import { useRanger } from '@tanstack/react-ranger'
function RangeSlider() {
const [values, setValues] = useState([25, 75])
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 0,
max: 100,
stepSize: 1,
onChange: (instance) => setValues(instance.sortedValues),
})
const rangerRef = useRef<HTMLDivElement>(null)
return (
<div
ref={rangerRef}
style={{
position: 'relative',
height: '8px',
background: '#ddd',
borderRadius: '4px',
width: '100%',
}}
>
{/* Track segments */}
{rangerInstance.getSteps().map(({ left, width }, i) => (
<div
key={i}
style={{
position: 'absolute',
left: `${left}%`,
width: `${width}%`,
height: '100%',
background: i === 1 ? '#3b82f6' : '#ddd',
borderRadius: '4px',
}}
/>
))}
{/* Thumbs */}
{rangerInstance.handles.map((handle, i) => (
<button
key={i}
{...handle.getHandleProps()}
style={{
position: 'absolute',
left: `${handle.getPercentage()}%`,
transform: 'translateX(-50%)',
width: '20px',
height: '20px',
borderRadius: '50%',
background: '#3b82f6',
border: '2px solid white',
cursor: 'grab',
}}
/>
))}
</div>
)
}
| Option | Type | Description |
|--------|------|-------------|
| getRangerElement | () => Element \| null | Returns the slider track element |
| values | number[] | Current thumb values |
| min | number | Minimum value |
| max | number | Maximum value |
| onChange | (instance) => void | Called when values change |
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| stepSize | number | 1 | Step increment between values |
| steps | number[] | - | Custom step positions (overrides stepSize) |
| tickSize | number | - | Size of tick marks |
| ticks | number[] | - | Custom tick positions |
| interpolator | Interpolator | linear | Value interpolation function |
| onDrag | (instance) => void | - | Called during drag operations |
// Get sorted values (always ascending order)
rangerInstance.sortedValues: number[]
// Get handles for rendering thumbs
rangerInstance.handles: Handle[]
// Get track segments between handles
rangerInstance.getSteps(): { left: number; width: number }[]
// Get tick marks
rangerInstance.getTicks(): { value: number; percentage: number }[]
// Programmatically set values
rangerInstance.setValues(newValues: number[])
interface Handle {
// Get percentage position on track (0-100)
getPercentage(): number
// Get the current value
getValue(): number
// Get props to spread on handle element
getHandleProps(): {
role: 'slider'
tabIndex: number
'aria-valuemin': number
'aria-valuemax': number
'aria-valuenow': number
onKeyDown: (e: KeyboardEvent) => void
onMouseDown: (e: MouseEvent) => void
onTouchStart: (e: TouchEvent) => void
}
}
function SingleSlider() {
const [values, setValues] = useState([50])
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 0,
max: 100,
stepSize: 1,
onChange: (instance) => setValues(instance.sortedValues),
})
const rangerRef = useRef<HTMLDivElement>(null)
return (
<div ref={rangerRef} className="slider-track">
{rangerInstance.handles.map((handle, i) => (
<button key={i} {...handle.getHandleProps()} className="slider-thumb">
{handle.getValue()}
</button>
))}
</div>
)
}
function MultiRangeSlider() {
const [values, setValues] = useState([10, 40, 60, 90])
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 0,
max: 100,
stepSize: 5,
onChange: (instance) => setValues(instance.sortedValues),
})
const rangerRef = useRef<HTMLDivElement>(null)
return (
<div ref={rangerRef} className="slider-track">
{rangerInstance.getSteps().map(({ left, width }, i) => (
<div
key={i}
className={`segment ${i % 2 === 1 ? 'active' : ''}`}
style={{ left: `${left}%`, width: `${width}%` }}
/>
))}
{rangerInstance.handles.map((handle, i) => (
<button key={i} {...handle.getHandleProps()} className="slider-thumb" />
))}
</div>
)
}
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 0,
max: 100,
steps: [0, 10, 25, 50, 75, 100], // Only these values allowed
onChange: (instance) => setValues(instance.sortedValues),
})
function SliderWithTicks() {
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 0,
max: 100,
stepSize: 10,
ticks: [0, 25, 50, 75, 100],
onChange: (instance) => setValues(instance.sortedValues),
})
return (
<div>
<div ref={rangerRef} className="slider-track">
{/* Handles */}
</div>
<div className="tick-container">
{rangerInstance.getTicks().map((tick, i) => (
<div
key={i}
style={{ left: `${tick.percentage}%` }}
className="tick"
>
<span className="tick-label">{tick.value}</span>
</div>
))}
</div>
</div>
)
}
import { logarithmicInterpolator } from '@tanstack/react-ranger'
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 1,
max: 1000,
interpolator: logarithmicInterpolator,
onChange: (instance) => setValues(instance.sortedValues),
})
TanStack Ranger provides built-in accessibility:
role="slider" on handlesaria-valuemin, aria-valuemax, aria-valuenow attributes// Add aria-label for screen readers
<button
{...handle.getHandleProps()}
aria-label={`Value: ${handle.getValue()}`}
/>
// Controlled (recommended)
const [values, setValues] = useState([50])
const ranger = useRanger({
values,
onChange: (instance) => setValues(instance.sortedValues),
// ...
})
// With validation
const handleChange = (instance) => {
const [min, max] = instance.sortedValues
// Ensure minimum gap of 10
if (max - min >= 10) {
setValues(instance.sortedValues)
}
}
/* Track */
.slider-track {
position: relative;
height: 8px;
background: #e5e7eb;
border-radius: 4px;
width: 100%;
}
/* Active segment */
.segment.active {
background: #3b82f6;
}
/* Thumb */
.slider-thumb {
position: absolute;
transform: translateX(-50%);
width: 20px;
height: 20px;
border-radius: 50%;
background: #3b82f6;
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
cursor: grab;
}
.slider-thumb:active {
cursor: grabbing;
}
.slider-thumb:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
| Framework | Package | Status |
|-----------|---------|--------|
| React | @tanstack/react-ranger | Stable |
| Vue | @tanstack/vue-ranger | Stable |
| Solid | @tanstack/solid-ranger | Stable |
| Svelte | @tanstack/svelte-ranger | Stable |
| Angular | @tanstack/angular-ranger | Stable |
| Core | @tanstack/ranger-core | Stable |
sortedValues from onChange - handles may cross during draggetRangerElement callback to prevent unnecessary re-renders<button> elements for accessibilityaria-label to describe each handle's purposetranslateX) for positioning instead of left for better performanceonDrag for real-time feedback during drag operationsposition: relative on the track containervalues instead of sortedValues (handles can swap positions)getRangerElement as a callbackleft instead of transform: translateX()development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
tools
UI/UX design intelligence for web and mobile. Includes 50+ styles, 161 color palettes, 57 font pairings, 161 product types, 99 UX guidelines, and 25 chart types across 10 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui, and HTML/CSS). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, and check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, and mobile app. Elements: button, modal, navbar, sidebar, card, table, form, and chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, and flat design. Topics: color systems, accessibility, animation, layout, typography, font pairing, spacing, interaction states, shadow, and gradient. Integrations: shadcn/ui MCP for component search and examples.
development
Headless UI for virtualizing large element lists at 60FPS in TS/JS, React, Vue, Solid, Svelte, Lit & Angular.
development
Headless UI for building powerful tables & datagrids for TS/JS, React, Vue, Solid, Svelte, Qwik, Angular, and Lit.