Notes

Readability & fun

Contrast
Text Size
100

Extras

Notes

Component Apr 2026

Split Slider

A split-ratio slider for React. Two labeled sides, draggable handle, momentum snap, dynamic role caption β€” minimal and composable.

Split Slider
Tap to interact

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.

KeyAction
← / ↓Decrease by step
β†’ / ↑Increase by step
Shift + arrowMove by 2 Γ— step

Styling

Default styles ship in split-slider/styles.css. Override any class in your own CSS.

Track

PropertyValue
Height64px
Border radius8px
Left panelvar(--primary)
Right panelvar(--subtle)

Handle

PropertyValue
Width / Height3px Γ— 24px
Border radius999px
Color#888888 (50% gray)
Visibleonly while dragging

Hash marks

PropertyValue
Count1 / step βˆ’ 1 (e.g. 19 marks at step 0.05)
Visibleonly while dragging
Hidden zones13% from each edge, 10% around split

API Reference

SplitSlider

PropTypeDefault
leftstringrequired
rightstringrequired
valuenumberβ€”
defaultValuenumber0.5
onValueChange(value: number) => voidβ€”
stepnumber0.05
minnumber0.1
maxnumber0.9
titlestringβ€”
formatCaption(value: number) => stringβ€”
formatValue(value: number) => string(v) => Math.round(v * 100) + '%'
momentumbooleantrue
disabledbooleanfalse
classNamestringβ€”

Events

EventPayload
onValueChangevalue: number (continuous, fires on drag and snap)
onValueCommitvalue: number (fires once on release after snap settles)

Accessibility

  • role="slider" with aria-valuemin, aria-valuemax, aria-valuenow
  • aria-label from the left / right props by default; override with aria-label
  • Focusable via keyboard, supports arrow-key navigation
  • Selection suppressed during drag to prevent text-highlight bleed