static/app/components/core/input/numberInput.mdx
import {useState} from 'react';
import {NumberInput} 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');
<NumberInput> is a specialized numeric input field that provides increment and decrement buttons for precise value control. Built with React Aria, it offers full keyboard support, internationalization, and accessibility features out of the box.
Use <NumberInput> when users need to enter numeric values with optional bounds and step controls, such as quantities, durations, or numeric settings.
<NumberInput defaultValue={5} min={0} max={10} />
The simplest use case is a number input without constraints. Users can type numbers directly or use the increment/decrement buttons.
<Storybook.Demo> <Stack gap="sm"> Default NumberInput <NumberInput /> </Stack> </Storybook.Demo>
<NumberInput />
<NumberInput> supports two sizes: md (default) and xs. The size controls the height of the input and the scale of the step buttons.
<Storybook.Demo> <Stack gap="sm"> size="md" (default) <NumberInput defaultValue={42} /> </Stack> <Stack gap="sm"> size="xs" <NumberInput size="xs" defaultValue={42} /> </Stack> </Storybook.Demo>
<NumberInput defaultValue={42} />
<NumberInput size="xs" defaultValue={42} />
Use the min and max props to constrain the allowed numeric range. The step buttons will automatically disable when the bounds are reached.
export function MinMaxDemo() { const [value, setValue] = useState(5); return ( <Stack gap="sm"> Value: {value} (constrained between 0 and 10) <NumberInput value={value} onChange={setValue} min={0} max={10} /> </Stack> ); }
<Storybook.Demo> <MinMaxDemo /> </Storybook.Demo>
const [value, setValue] = useState(5);
<NumberInput value={value} onChange={setValue} min={0} max={10} />;
[!NOTE] When the value reaches the minimum, the decrement button becomes disabled. When it reaches the maximum, the increment button becomes disabled. Users can still type values outside the bounds, but the component will constrain them on blur.
The step prop controls the increment/decrement amount. This is useful for values that should change in specific intervals.
export function StepDemo() { const [value, setValue] = useState(0); return ( <Stack gap="sm"> Value: {value} (increments by 5) <NumberInput value={value} onChange={setValue} step={5} min={0} max={100} /> </Stack> ); }
<Storybook.Demo> <StepDemo /> </Storybook.Demo>
const [value, setValue] = useState(0);
<NumberInput value={value} onChange={setValue} step={5} min={0} max={100} />;
<NumberInput> supports decimal values. Use the step and formatOptions props to control decimal precision.
export function DecimalDemo() { const [value, setValue] = useState(0.5); return ( <Stack gap="sm"> Value: {value} (increments by 0.1) <NumberInput value={value} onChange={setValue} step={0.1} minValue={0} maxValue={1} formatOptions={{ minimumFractionDigits: 1, maximumFractionDigits: 1, }} /> </Stack> ); }
<Storybook.Demo> <DecimalDemo /> </Storybook.Demo>
const [value, setValue] = useState(0.5);
<NumberInput
value={value}
onChange={setValue}
step={0.1}
minValue={0}
maxValue={1}
formatOptions={{
minimumFractionDigits: 1,
maximumFractionDigits: 1,
}}
/>;
Like standard inputs, <NumberInput> can be controlled or uncontrolled. Use value and onChange for controlled inputs, or defaultValue for uncontrolled.
// Controlled
const [value, setValue] = useState(0);
<NumberInput value={value} onChange={setValue} />
// Uncontrolled
<NumberInput defaultValue={0} />
<NumberInput> supports disabled and readOnly states.
<Storybook.Demo> <Stack gap="sm"> Disabled <NumberInput defaultValue={42} disabled /> </Stack> <Stack gap="sm"> Read-only <NumberInput defaultValue={42} readOnly /> </Stack> </Storybook.Demo>
<NumberInput defaultValue={42} disabled />
<NumberInput defaultValue={42} readOnly />
Use the placeholder prop to provide hint text when the input is empty.
<Storybook.Demo> <Stack gap="sm"> With placeholder <NumberInput placeholder="Enter a number" /> </Stack> </Storybook.Demo>
<NumberInput placeholder="Enter a number" />
Set the monospace prop to render the input value with a monospace font, useful for technical or code-related numeric values.
<Storybook.Demo> <Stack gap="sm"> Regular font <NumberInput defaultValue={12345} /> </Stack> <Stack gap="sm"> Monospace font <NumberInput defaultValue={12345} monospace /> </Stack> </Storybook.Demo>
<NumberInput defaultValue={12345} />
<NumberInput defaultValue={12345} monospace />
<NumberInput> provides comprehensive keyboard support:
step valuemin is set)max is set)These keyboard shortcuts work regardless of locale and are automatically provided by React Aria.
Use for selecting item quantities in forms or shopping interfaces:
<label>
Quantity
<NumberInput defaultValue={1} min={1} max={99} />
</label>
Combine with appropriate step values for time-based inputs:
<label>
Timeout (seconds)
<NumberInput defaultValue={30} min={0} max={300} step={5} />
</label>
Use for percentage values with decimal precision:
<NumberInput
defaultValue={50}
min={0}
max={100}
step={0.1}
formatOptions={{
style: 'percent',
minimumFractionDigits: 1,
}}
/>
Use for numeric configuration settings with sensible bounds:
<label>
Max retries
<NumberInput defaultValue={3} min={0} max={10} />
</label>
<NumberInput> is built on React Aria's useNumberField hook, which provides:
This means the component automatically handles complex scenarios like:
<NumberInput> is built with React Aria and automatically meets WCAG 2.2 AA standards:
The component automatically includes:
role="spinbutton" on the inputaria-valuemin, aria-valuemax, aria-valuenow attributesaria-label on increment/decrement buttonsLabels (WCAG 3.3.2)
<label> element, aria-label, or aria-labelledby// Good: Visible label
<label>
Quantity
<NumberInput defaultValue={1} min={1} />
</label>
// Good: aria-label
<NumberInput defaultValue={1} min={1} aria-label="Quantity" />
Error Handling
isInvalid prop to mark validation errorserrorMessage to describe the erroraria-describedby<NumberInput
value={value}
onChange={setValue}
min={1}
max={10}
isInvalid={value > 10}
aria-describedby="error"
/>;
{
value > 10 && (
<span id="error" role="alert">
Value must be 10 or less
</span>
);
}
Required Fields
required prop for required fields