Skip to content
WANCRAFT

A.96Buttons & Controls

XY Pad

A 2D control surface: drag a puck to set two values at once, with grid, crosshair, readout and keyboard nudging.

NewReactMotionSVGTailwindintermediate

Overview

XY Pad sets two related values with one gesture — filter cutoff and resonance on a synth, colour temperature and tint on a light, pan and depth in a mixer. Press anywhere to grab the puck and drag; the pad uses pointer capture so the drag keeps going outside its bounds, the puck follows the pointer through motion values, and React state only changes when a stepped value actually changes. Guides cross at the puck, a corner readout shows both values, and an optional spring returns the puck to the centre on release like a joystick. For keyboards and screen readers it is a single focusable slider whose value text names both axes, with arrow-key nudges, Shift for bigger steps and Home to recentre.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/xy-pad.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/xy-pad.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/xy-pad.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { XYPad, type XYValue } from "@/components/ui/xy-pad";

export function XYPadExample() {
  return (
    <div className="w-72">
      <XYPad
        label="Filter"
        xLabel="Cutoff"
        yLabel="Resonance"
        onValueChange={(value: XYValue) => console.log(value)}
      />
    </div>
  );
}

03Props

PropTypeDefaultDescription
value / defaultValue{ x: number; y: number }— / centreControlled value, or the initial value. Snapped to step and clamped to the ranges.
onValueChange(value: XYValue) => void—Called whenever the stepped value changes, including during a drag.
onValueCommit(value: XYValue) => void—Called when a drag ends and after keyboard changes.
xRange / yRange[number, number][0, 100]Axis ranges. Larger y values are higher up.
stepnumber | [number, number]1Step for both axes, or [xStep, yStep]; Shift + arrow moves ten steps.
xLabel / yLabelstring"X" / "Y"Axis names used in labels, readout and aria-valuetext.
formatX / formatY(value: number) => string—Format each axis for the readout, bounds and aria-valuetext.
labelstring"XY pad"Accessible name of the pad.
gridLinesnumber4Grid divisions per axis; the centre line is drawn stronger.
crosshairbooleantrueGuide lines through the puck.
returnToCenterbooleanfalseSpring back to the centre on release (value included).
spring{ stiffness: number; damping: number; mass: number }{ stiffness: 300, damping: 22, mass: 0.7 }Spring for keyboard moves, releases and controlled updates.
showReadoutbooleantrueLive value readout in the top-left corner.
accentstring"#FF7448"Puck, crosshair and focus colour.
disabledbooleanfalseDisable pointer and keyboard input.
...propsHTMLAttributes<HTMLDivElement>—Forwarded to the root element.

04Features

  • 01Drag anywhere with pointer capture; the puck follows the pointer through motion values
  • 02Re-renders only when a stepped value changes, never per pointer move
  • 03Grid, centre cross, crosshair guides and a live readout
  • 04Optional spring return to centre on release
  • 05Custom ranges, step and per-axis formatters (units, Kelvin, Hz)
  • 06Controlled or uncontrolled, with change and commit callbacks
motion · springinput · draginput · pointerinput · keyboardinput · focus

05Accessibility

  • One focusable role="slider" with aria-roledescription="2D slider"; aria-valuetext names both axes (e.g. “Cutoff 62%, Resonance 30%”).
  • aria-valuenow / min / max report the horizontal axis, since ARIA has no native two-axis slider.
  • Left / Right change x, Up / Down change y, Shift multiplies the step by ten, Page Up / Page Down move y by ten steps, Home recentres.
  • Grid, guides, readout and axis labels are aria-hidden; the slider carries the values.
  • Visible focus outline; with prefers-reduced-motion the puck jumps instead of springing.

06Limitations

  • Screen readers announce the value text, not the 2D position; describe what the axes control in nearby text.
  • The pad is square (aspect-square); override with className if you need another ratio.