static/app/components/core/input/numberDragInput.mdx
import {useState} from 'react';
import {NumberDragInput} from '@sentry/scraps/input'; import {Stack} from '@sentry/scraps/layout';
import * as Storybook from 'sentry/stories';
export const documentation = import('!!type-loader!@sentry/scraps/input');
<NumberDragInput> is a specialized numeric input that combines typing and dragging interactions. Users can enter values directly or click and drag the arrow indicators to adjust values dynamically. This provides an intuitive, continuous way to explore numeric ranges.
Use <NumberDragInput> for numeric parameters that benefit from interactive exploration, such as threshold adjustments, animation timing, or visual property tuning.
<NumberDragInput
value={value}
onChange={e => setValue(Number(e.target.value))}
min={0}
max={100}
/>
Click and hold the arrow indicators on the right side of the input, then move the mouse horizontally or vertically (depending on the axis prop) to adjust the value. The input uses pointer lock to provide smooth, unbounded dragging.
export function HorizontalDemo() { const [value, setValue] = useState(50); return ( <Stack gap="sm"> Value: {value} <NumberDragInput axis="x" value={value} onChange={e => setValue(Number(e.target.value))} min={0} max={100} placeholder="None" /> </Stack> ); }
<Storybook.Demo> <HorizontalDemo /> </Storybook.Demo>
const [value, setValue] = useState(50);
<NumberDragInput
axis="x"
value={value}
onChange={e => setValue(Number(e.target.value))}
min={0}
max={100}
/>;
[!NOTE] Dragging uses pointer lock, which hides the cursor and provides unbounded movement. The pointer is automatically released when you stop dragging.
The axis prop controls the drag direction: x for horizontal (left/right) or y for vertical (up/down). The default is x.
export function AxesDemo() { const [horizontalValue, setHorizontalValue] = useState(10); const [verticalValue, setVerticalValue] = useState(10); return ( <Storybook.SideBySide> <Stack gap="sm"> Horizontal (axis="x") <NumberDragInput axis="x" onChange={e => setHorizontalValue(Number(e.target.value))} min={0} max={100} value={horizontalValue} placeholder="None" /> </Stack> <Stack gap="sm"> Vertical (axis="y") <NumberDragInput axis="y" onChange={e => setVerticalValue(Number(e.target.value))} min={0} max={100} value={verticalValue} placeholder="None" /> </Stack> </Storybook.SideBySide> ); }
<Storybook.Demo> <AxesDemo /> </Storybook.Demo>
// Horizontal dragging (left/right)
<NumberDragInput axis="x" value={value} onChange={handleChange} />
// Vertical dragging (up/down)
<NumberDragInput axis="y" value={value} onChange={handleChange} />
The step prop controls how much the value changes per drag increment. The default step is 1.
export function StepDemo() { const [value, setValue] = useState(0); return ( <Stack gap="sm"> Value: {value} (step=5) <NumberDragInput value={value} onChange={e => setValue(Number(e.target.value))} step={5} min={0} max={100} /> </Stack> ); }
<Storybook.Demo> <StepDemo /> </Storybook.Demo>
<NumberDragInput value={value} onChange={handleChange} step={5} min={0} max={100} />
Hold Shift while dragging to increase the step size by a multiplier (default: 10×). This provides fine-grained control for precise adjustments. You can customize the multiplier with the shiftKeyMultiplier prop.
export function ShiftKeyDemo() { const [value, setValue] = useState(50); return ( <Stack gap="sm"> Value: {value}
<small>Hold Shift while dragging for 10× speed</small>
<NumberDragInput
value={value}
onChange={e => setValue(Number(e.target.value))}
min={0}
max={100}
shiftKeyMultiplier={10}
/>
</Stack>
); }
<Storybook.Demo> <ShiftKeyDemo /> </Storybook.Demo>
<NumberDragInput
value={value}
onChange={handleChange}
min={0}
max={100}
shiftKeyMultiplier={10}
/>
[!TIP] The shift key modifier is useful for workflows where users need both coarse adjustments (dragging) and fine-tuning (shift + dragging).
Use the min and max props to constrain the allowed numeric range. Values are automatically clamped during dragging and typing.
export function MinMaxDemo() { const [value, setValue] = useState(5); return ( <Stack gap="sm"> Value: {value} (constrained between 0 and 10) <NumberDragInput value={value} onChange={e => setValue(Number(e.target.value))} min={0} max={10} /> </Stack> ); }
<Storybook.Demo> <MinMaxDemo /> </Storybook.Demo>
<NumberDragInput value={value} onChange={handleChange} min={0} max={10} />
In addition to dragging, <NumberDragInput> supports keyboard controls:
step valuestep valueThese keyboard shortcuts provide an accessible alternative to mouse-based dragging.
Like standard inputs, <NumberDragInput> can be controlled or uncontrolled. Use value and onChange for controlled inputs, or defaultValue for uncontrolled.
export function UncontrolledDemo() { return ( <Storybook.SideBySide> <Stack gap="sm"> Controlled <NumberDragInput value={25} onChange={() => {}} min={0} max={100} /> </Stack> <Stack gap="sm"> Uncontrolled <NumberDragInput defaultValue={25} min={0} max={100} /> </Stack> </Storybook.SideBySide> ); }
<Storybook.Demo> <UncontrolledDemo /> </Storybook.Demo>
// Controlled
const [value, setValue] = useState(25);
<NumberDragInput
value={value}
onChange={e => setValue(Number(e.target.value))}
/>
// Uncontrolled
<NumberDragInput defaultValue={25} />
Use the placeholder prop to provide hint text when the input is empty.
<Storybook.Demo> <Stack gap="sm"> With placeholder <NumberDragInput placeholder="Enter a value" min={0} max={100} /> </Stack> </Storybook.Demo>
<NumberDragInput placeholder="Enter a value" min={0} max={100} />
Use for adjusting numeric thresholds in real-time:
<label>
Error Threshold (%)
<NumberDragInput
value={errorThreshold}
onChange={e => setErrorThreshold(Number(e.target.value))}
min={0}
max={100}
step={1}
/>
</label>
Allow users to explore animation durations interactively:
<label>
Animation Duration (ms)
<NumberDragInput
value={duration}
onChange={e => setDuration(Number(e.target.value))}
min={0}
max={5000}
step={100}
/>
</label>
Enable interactive adjustment of visual properties:
<label>
Border Radius (px)
<NumberDragInput
value={borderRadius}
onChange={e => setBorderRadius(Number(e.target.value))}
min={0}
max={50}
step={1}
/>
</label>
Adjust decimal values with fine control using shift modifier:
<NumberDragInput
value={opacity}
onChange={e => setOpacity(Number(e.target.value))}
min={0}
max={1}
step={0.01}
shiftKeyMultiplier={0.1}
/>
<NumberDragInput> is built on top of <InputGroup> and uses:
The component automatically:
<NumberDragInput> provides multiple interaction methods to ensure accessibility:
The component includes:
Labels (WCAG 3.3.2)
<label> element, aria-label, or aria-labelledby// Good: Visible label
<label>
Threshold
<NumberDragInput value={value} onChange={handleChange} />
</label>
// Good: aria-label
<NumberDragInput value={value} onChange={handleChange} aria-label="Threshold" />
Interaction Guidance
Mouse-Only Concerns
<NumberInput> alternative for users who prefer traditional inputs[!WARNING] The drag interaction relies on pointer lock, which may not work in all browsers or contexts. Always test keyboard functionality as the primary accessibility method.