XY Pad

A two-dimensional control for two parameters at once, with pointer, wheel and keyboard input.

Audio UI

Controls two values at once, such as filter cutoff and resonance, pan and mix, or two macros. The registry component wraps the XYPad primitive from @audio-ui/react and adds a grid, a crosshair, a cursor and a value display.

Overview

  • Input: press or drag on the pad, or use the mouse wheel, a trackpad or the keyboard, on both axes.
  • Axes: X and Y have their own range and step (minX, maxX, stepX; minY, maxY, stepY).
  • Display: grid lines, a crosshair, a cursor and an optional value display. The position is exposed as the CSS variables --xy-pad-x (from the left) and --xy-pad-y (from the top).
  • Accessibility: the cursor holds one native <input type="range"> per axis. Both carry the accessible name (aria-label becomes "… X" and "… Y", or set each with getAriaLabel on the primitive's Thumb). The X input takes focus, and the arrow keys move both axes.

Installation

pnpm dlx shadcn@latest add @audio-ui/xypad

Anatomy

Import the primitive and assemble its parts.

import { XYPad } from "@audio-ui/react";

<XYPad.Root>
  <XYPad.Label />
  <XYPad.Control>
    <XYPad.Thumb />
  </XYPad.Control>
  <XYPad.Value />
</XYPad.Root>

Usage

import { XYPad } from "@/components/audio-ui/xypad";

Uncontrolled: set only the initial value.

<XYPad
  defaultValue={{ x: 0, y: 0 }}
  maxX={100}
  maxY={1}
  minX={-100}
  minY={-1}
  stepX={1}
  stepY={0.01}
/>

Controlled: drive the value from state.

const [position, setPosition] = useState({ x: 50, y: 50 });

<XYPad
  maxX={100}
  maxY={100}
  minX={0}
  minY={0}
  onValueChange={setPosition}
  onValueCommitted={(value, details) => console.log("Committed:", value, details.reason)}
  value={position}
/>

Interaction

Pointer

Behavior · 4

Press / drag on pad
A press jumps the value to the pointer; a drag follows it. Reported as eventDetails.reason "press" then "drag".
Clamping
Values always clamp to the configured axis ranges and snap to stepX / stepY.
Commit event
onValueCommitted fires when a gesture that changed the value ends.
Lagging value
While dragging, the cursor follows the pointer even if a controlled value updates late. Reject a value with eventDetails.cancel().

Keyboard

On the focused pad:

Keys · 5

ArrowLeft / ArrowRight
Decrease / increase X by stepX
ArrowUp / ArrowDown
Increase / decrease Y by stepY
Shift + arrow
Move by 10 steps
PageUp / PageDown
Increase / decrease Y by stepY × 10
Home / End
X to minX / maxX

Wheel

Wheel and trackpad input over the pad moves the point by the scrolled distance: scrolling up raises Y, scrolling right raises X. Reported as eventDetails.reason === "wheel".

API reference

Root

Groups all parts of the pad and owns its two-dimensional value. Exposes the value position as CSS variables for crosshairs and guides: --xy-pad-x (from the left) and --xy-pad-y (from the top), as percentages. Renders a <div> element.

Props · 14

Data attributes · 2

data-disabled
Present when disabled.
data-dragging
Whether a pointer is dragging on the pad.

Control

The pad surface: a press jumps the value to the pointer and a drag follows it; the mouse wheel / trackpad moves it too. Sets touch-action: none so touch drags don't scroll the page. Renders a <div> element.

Data attributes · 2

data-disabled
Present when disabled.
data-dragging
Whether a pointer is dragging on the pad.

Thumb

Marks the value on the pad (left/top as percentages, centered with translate). Holds two native <input type="range">s, one per axis: the X input takes focus, both carry the accessible name, and the arrow keys move both axes. Renders a <div> element.

Props · 2

Data attributes · 2

data-disabled
Present when disabled.
data-dragging
Whether a pointer is dragging on the pad.

Value

The formatted value (format/locale on Root), "x, y" by default. Renders a <output> element.

Props · 1

Data attributes · 2

data-disabled
Present when disabled.
data-dragging
Whether a pointer is dragging on the pad.

Label

Accessible label, associated with both axis inputs. Renders a <div> element.

Data attributes · 2

data-disabled
Present when disabled.
data-dragging
Whether a pointer is dragging on the pad.

Registry component

XYPad accepts style props (size, formatValue, valueDisplay, className) plus primitive props from XYPadPrimitive.Root.Props. aria-label, aria-labelledby, and aria-describedby are forwarded to the axis inputs.

Value and state

Props · 12

Layout and display

Props · 4

@audio-ui/react/xypad exposes XYPad.Root, XYPad.Control (the pad surface), XYPad.Thumb (positioned at the value, holds the axis inputs), XYPad.Value (formatted <output>), and XYPad.Label. Every part accepts a render prop, className / style as functions of state, and exposes data-disabled / data-dragging. Draw grids and crosshairs with your own elements and the --xy-pad-x / --xy-pad-y variables.

Examples

Live value readout

Audio UI

Bipolar ranges (pan and mix style)

Audio UI

Pocket synth

An oscillator, a filter and an XY pad that plays them: drag the pad to sweep the sound.

Audio UI

Wave shaper

Distortion, tone and detune on a synth, with the XY pad and knobs side by side.

Audio UI

More Shadcn XY Pad components

Browse 4 production-ready audio/ui XY Pad components for players, mixers, and synth interfaces. These examples use the headless primitives from @audio-ui/react and stay fully compatible with Shadcn Create so radius, color, and typography match your configured theme.

Browse all 4 Shadcn XY Pad components for copy-ready layouts, dashboards, and forms built with Tailwind CSS in the audio/ui library.

Built by Ouest Labs, the source code is available on GitHub.
RSS