Joystick
Joystick captures two-axis input as a spring-return stick or persistent XY pad.
Source
LLM docs
Package
Examples
5
Properties
57
Playground
Basics
A stick that springs back to centre on release. Drag anywhere on the pad — the gesture keeps tracking even when the finger leaves it.
Loading demo…
import { useState } from 'react';
import { Joystick } from '@plocks/ui';
export function Demo() {
const [value, setValue] = useState({ x: 0, y: 0 });
return (
<Joystick accessibilityLabel="Joystick"
value={value}
onChange={setValue}
showCrosshair
valueLabel
/>
);
}Variants
Compare the joystick presets, including the unstyled base for custom skins.
Loading demo…
import { Column, Flex, Joystick, Text } from '@plocks/ui';
const variants = ['default', 'filled', 'outline', 'minimal', 'unstyled'] as const;
export function Demo() {
return (
<Flex direction="row" gap="lg" wrap="wrap">
{variants.map(variant => (
<Column key={variant} gap="xs" align="center">
<Text size="sm">{variant}</Text>
<Joystick variant={variant} accessibilityLabel={`${variant} joystick`} />
</Column>
))}
</Flex>
);
}XY pad
shape="square" clamps each axis independently so the corners are reachable, and the handle holds its position on release — the shape a filter or effect pad wants.Loading demo…
import { useState } from 'react';
import { Flex, Joystick, Text } from '@plocks/ui';
export function Demo() {
const [value, setValue] = useState({ x: -0.4, y: 0.6 });
// Map the pad onto a pair of parameters the way an effect unit would.
const cutoff = Math.round(((value.x + 1) / 2) * 18000 + 200);
const resonance = ((value.y + 1) / 2).toFixed(2);
return (
<Flex direction="column" gap="md">
<Joystick
shape="square"
size="lg"
value={value}
onChange={setValue}
showCrosshair
label="Filter"
/>
<Text size="sm" c="muted">Cutoff {cutoff} Hz · Resonance {resonance}</Text>
</Flex>
);
}Dead zone and steps
deadZone ignores small deflections around centre and rescales the rest, so full travel still reports 1. step snaps each axis onto a grid.Loading demo…
import { useState } from 'react';
import { Flex, Joystick } from '@plocks/ui';
export function Demo() {
const [free, setFree] = useState({ x: 0, y: 0 });
const [stepped, setStepped] = useState({ x: 0, y: 0 });
return (
<Flex gap="xl" wrap="wrap">
<Joystick
label="Dead zone 0.25"
deadZone={0.25}
value={free}
onChange={setFree}
valueLabel
/>
<Joystick
label="Step 0.25"
shape="square"
step={0.25}
returnToCenter={false}
value={stepped}
onChange={setStepped}
showCrosshair
valueLabel
/>
</Flex>
);
}Axis lock
lockAxis restricts travel to one direction. A single-axis pad also leaves the perpendicular direction to the page, so vertical scrolling still works over a horizontal control.Loading demo…
import { useState } from 'react';
import { Flex, Joystick, Text } from '@plocks/ui';
export function Demo() {
const [pan, setPan] = useState({ x: 0, y: 0 });
const position = pan.x === 0
? 'Center'
: `${pan.x < 0 ? 'L' : 'R'} ${Math.round(Math.abs(pan.x) * 100)}`;
return (
<Flex direction="column" gap="md" align="flex-start">
<Joystick
label="Pan"
lockAxis="x"
size="sm"
value={pan}
onChange={setPan}
/>
<Text size="sm" c="muted">{position}</Text>
</Flex>
);
}React Native design for iOS, Android, and Web.
Quick Links
Resources