XY Pad
xy-padControlsTwo parameters under one finger, on XYPadHeadless. Grid, crosshair, and a dot that leaves a short trail behind the pointer. Each axis carries its own range, step and formatter, and the Y axis reads bottom-up the way a filter/resonance pad is expected to. It wears the kit's five shared skins, so a pad, a knob and a fader on one panel read as the same instrument.
Install
Copies the source into your project. Anything it depends on comes with it.
$ npx shadcn add @syntho-ui/xy-padPulls in
xy-pad-headlesscontrol-mathcontrol-formatcontrol-themecontrol-skinuse-controlcc-dnd
First time? Add the registry to your components.json once:
{
"registries": {
"@syntho-ui": "https://engine.syntho.app/r/{name}.json"
}
}Usage
Controlled or uncontrolled, whichever suits the surrounding state.
import { XYPad } from "@/components/ui/xy-pad"
<XYPad
x={{ min: 20, max: 20000, label: "Cutoff", unit: "Hz" }}
y={{ min: 0, max: 1, label: "Reso" }}
defaultValue={{ x: 2200, y: 0.4 }}
onChange={({ x, y }) => patch.set({ cutoff: x, reso: y })}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | { x: number; y: number } | — | Controlled position. Omit for uncontrolled operation. |
| defaultValue | { x: number; y: number } | { x: 0.5, y: 0.5 } | Starting position, and the double-click reset target. |
| onChange | (value: { x: number; y: number }) => void | — | Fires on every move. |
| onChangeEnd | (value: { x: number; y: number }) => void | — | Fires once when the gesture ends. |
| x / y | XYAxisConfig | — | Per-axis min, max, step, label, unit and format. Defaults to a 0..1 continuous axis. |
| height | number | 180 | Pad height in pixels; width defaults to 100%. |
| maxWidth | number | string | null | 300 | Upper bound on the rendered width, so a full-width pad fills its column only up to a sane size. Pass null to remove the cap. |
| variant | "default" | "flat" | "vintage" | "minimal" | "neon" | "default" | Visual preset, shared with every other control in the kit: flat drops the accent wash for a matte dot, vintage puts a cream bead on the plane, minimal keeps only the crosshair and the dot, and neon keeps the dot's halo lit at rest. Omitted, the pad takes whatever a surrounding ControlSkinProvider declares. |
| grid | number | 4 | Grid divisions per axis. 0 hides the grid. |
| trailLength | number | 10 | Trail samples kept behind the dot. 0 disables the trail. |
| showReadout | boolean | true | Show the formatted x/y values in the corner. |
| midi | { x?: ControlMidiProps; y?: ControlMidiProps } | — | Makes both halves of the plane CC drop targets and shows one assignment badge per axis. |
Interaction
- Drag with pointer capture, so the gesture keeps tracking past the edge of the control.
- Shift while dragging for a fine pass; release it mid-gesture and coarse tracking resumes.
- Double-click resets to
defaultValue. - Arrow keys step normally, Shift+arrow steps finely, PageUp/PageDown jump by ten, Home/End go to the extremes.
- Proper
sliderandswitchroles with livearia-valuetext, so a screen reader announces the formatted value rather than the raw number. - The scroll wheel adjusts the value while the pointer is over the control; stepped ranges move at least one declared step per notch by default.