Readability & fun
Extras
Notes
Component Apr 2026Split Slider
A split-ratio slider for React. Two labeled sides, draggable handle, momentum snap, dynamic role caption β minimal and composable.
Getting Started
Split Slider is a draggable two-sided slider for React. Drag the divider to set the ratio between two labeled regions; the slider snaps to the nearest step and reports the value.
Installation
Install the component from your command line.
// Terminal
npm install split-slider
Add it to your app
Import the component and the stylesheet. The default styles match a neutral light/dark design system.
// page.tsx
'use client'
import 'split-slider/styles.css'
import { SplitSlider } from 'split-slider'
export default function Page() {
return (
<SplitSlider
left="Design"
right="Code"
defaultValue={0.6}
onValueChange={(v) => console.log(v)}
/>
)
}
SplitSlider
Default
Renders the slider with two labels and a starting ratio. Drag the divider or use the arrow keys to adjust.
// default.tsx
'use client'
import { SplitSlider } from 'split-slider'
export default function Default() {
return (
<SplitSlider
left="Design"
right="Code"
defaultValue={0.6}
/>
)
}
Controlled
Pass value and onValueChange for full control over the state.
// controlled.tsx
'use client'
import { useState } from 'react'
import { SplitSlider } from 'split-slider'
export default function Controlled() {
const [ratio, setRatio] = useState(0.5)
return (
<SplitSlider
left="Design"
right="Code"
value={ratio}
onValueChange={setRatio}
/>
)
}
Caption
Pass formatCaption to display a dynamic label below the slider based on the current value.
// caption.tsx
'use client'
import { SplitSlider } from 'split-slider'
export default function WithCaption() {
return (
<SplitSlider
left="Design"
right="Code"
defaultValue={0.6}
formatCaption={(v) => {
const pct = Math.round(v * 100)
if (pct <= 15) return 'Pure engineer'
if (pct <= 30) return 'Engineer-forward'
if (pct <= 45) return 'Code-leaning'
if (pct <= 55) return 'Design engineer'
if (pct <= 70) return 'Design-leaning'
if (pct <= 85) return 'Art director'
return 'Pure designer'
}}
/>
)
}
Step
Set step to control snap granularity. The slider snaps to the nearest multiple on release.
// step.tsx
'use client'
import { SplitSlider } from 'split-slider'
export default function Step() {
return (
<SplitSlider
left="Design"
right="Code"
defaultValue={0.5}
step={0.1}
/>
)
}
Bounds
Use min and max to clamp the range. Defaults to 0.1 and 0.9 so neither side fully collapses.
// bounds.tsx
'use client'
import { SplitSlider } from 'split-slider'
export default function Bounds() {
return (
<SplitSlider
left="Design"
right="Code"
defaultValue={0.5}
min={0.2}
max={0.8}
/>
)
}
Title
Pass a title prop to render a heading above the slider.
// title.tsx
'use client'
import { SplitSlider } from 'split-slider'
export default function WithTitle() {
return (
<SplitSlider
title="What's your title?"
left="Design"
right="Code"
defaultValue={0.6}
/>
)
}
Behavior
Drag
Click and drag the divider β or anywhere on the track β to move the split. The handle fades in only while dragging and follows the pointer. Hash marks appear at every step interval as drag affordance.
Snap
On release, the slider snaps to the nearest step value. Snap uses an overdamped spring (no overshoot) so the motion settles cleanly.
Momentum
Velocity carried at release contributes to the snap target β fling the divider and it can snap to the next step instead of the closest one. Disable with momentum={false}.
Keyboard
The slider is focusable. Arrow keys move by step; Shift + arrow doubles the step.
| Key | Action |
|---|---|
| β / β | Decrease by step |
| β / β | Increase by step |
| Shift + arrow | Move by 2 Γ step |
Styling
Default styles ship in split-slider/styles.css. Override any class in your own CSS.
Track
| Property | Value |
|---|---|
| Height | 64px |
| Border radius | 8px |
| Left panel | var(--primary) |
| Right panel | var(--subtle) |
Handle
| Property | Value |
|---|---|
| Width / Height | 3px Γ 24px |
| Border radius | 999px |
| Color | #888888 (50% gray) |
| Visible | only while dragging |
Hash marks
| Property | Value |
|---|---|
| Count | 1 / step β 1 (e.g. 19 marks at step 0.05) |
| Visible | only while dragging |
| Hidden zones | 13% from each edge, 10% around split |
API Reference
SplitSlider
| Prop | Type | Default |
|---|---|---|
left | string | required |
right | string | required |
value | number | β |
defaultValue | number | 0.5 |
onValueChange | (value: number) => void | β |
step | number | 0.05 |
min | number | 0.1 |
max | number | 0.9 |
title | string | β |
formatCaption | (value: number) => string | β |
formatValue | (value: number) => string | (v) => Math.round(v * 100) + '%' |
momentum | boolean | true |
disabled | boolean | false |
className | string | β |
Events
| Event | Payload |
|---|---|
onValueChange | value: number (continuous, fires on drag and snap) |
onValueCommit | value: number (fires once on release after snap settles) |
Accessibility
role="slider"witharia-valuemin,aria-valuemax,aria-valuenowaria-labelfrom theleft/rightprops by default; override witharia-label- Focusable via keyboard, supports arrow-key navigation
- Selection suppressed during drag to prevent text-highlight bleed