Spin Button
In most cases, use the NumberField instead.
The SpinButton component can be used to implement the
spinbutton role.
when a <TextField type="number" /> cannot be used. The SpinButton does not have styles
by default and will need to be provided manually.
To help with the demos on this page, the following styles have been applied:
@use "everything" as *;
div[role="spinbutton"] {
@include divider-border-style;
@include interaction-outline;
align-items: center;
display: inline-flex;
justify-content: center;
min-height: 3rem;
min-width: 3rem;
padding: 0.5rem;
&:focus-visible {
@include interaction-focus-styles;
}
}
Simple SpinButton
A SpinButton has no styles by default and requires an aria-label or
aria-labelledby for accessibility. The value can be changed by:
- using the ArrowUp or ArrowDown keys
- typing a number
- clearing the field with
BackspaceorDelete
Any non-digit keys will be ignored.
All SpinButton on this page will be updated to have minimal styles
to help show the component.
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
import { type ReactElement } from "react";
export default function SimpleExample(): ReactElement {
return <SpinButton aria-label="Simple Example" />;
}
Ranged SpinButton
It is recommended to provide the min and max props to improve accessibility
and limit the range. This also adds support for updating the value using the
Home and End keys and will prevent the user from typing values outside of
this range.
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
import { type ReactElement } from "react";
export default function RangedSpinButtonExample(): ReactElement {
return <SpinButton aria-label="Range Example" min={0} max={10} />;
}
Using a Fallback
In the previous examples, the SpinButton content was either empty, a hyphen,
or the current value. When there is no value, the SpinButton has a few
default text content:
- if
minDigitsis provided, return-for each digit - if
minis provided, return-for each digit - if
fallbackis provided, use that value - otherwise, return an empty string
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
import { type ReactElement } from "react";
export default function UsingAFallbackExample(): ReactElement {
return (
<>
<SpinButton aria-label="Fallback Example" min={1} />
<SpinButton
aria-label="Fallback Example"
min={1}
max={12}
minDigits={2}
/>
<SpinButton
aria-label="Custom Fallback Example"
fallback="HH"
min={1}
max={12}
/>
</>
);
}
Controlling the value
The value can be controlled by providing a value and onValueChange. The
onValueChange callback provides an object with:
event- either aKeyboardEvent,FormEvent, orFocusEventreason- either"type","cleared","typed-to-completion", or"change"value- the next value
Current value: "null"
"use client";
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
import { type SpinButtonValue } from "@react-md/core/spinbutton/types";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useState } from "react";
export default function ControllingTheValueExample(): ReactElement {
const [value, setValue] = useState<SpinButtonValue>(null);
return (
<>
<SpinButton
aria-label="Example"
value={value}
min={0}
max={10}
minDigits={2}
onValueChange={({ value }) => setValue(value)}
/>
<Typography>Current value: {`"${value}"`}</Typography>
</>
);
}
Setting a default value
An alternative to controlling the value is to use a defaultValue instead.
This can be useful alongside the onValueChange callback to only update a
local state value when a specific change reason occurs.
Current value: "0"
"use client";
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
import { type SpinButtonValue } from "@react-md/core/spinbutton/types";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useState } from "react";
export default function OnValueChangeCallbackExample(): ReactElement {
const [value, setValue] = useState<SpinButtonValue>(0);
return (
<>
<Typography>Current value: {`"${value}"`}</Typography>
<SpinButton
aria-label="Minutes"
min={0}
max={59}
fallback="MM"
defaultValue={0}
onValueChange={(options) => {
const { reason, value } = options;
if (reason !== "type") {
setValue(value);
}
}}
/>
</>
);
}
SpinButtonGroup Example
When multiple SpinButton should be used together to create a single value,
use the SpinButtonGroupProvider and useSpinButtonGroupProvider as a wrapper
for the SpinButton components. When the user triggers the
"typed-to-completion" event, the next SpinButton will automatically be
focused.
"use client";
import { Box } from "@react-md/core/box/Box";
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
import { SpinButtonGroupProvider } from "@react-md/core/spinbutton/SpinButtonGroupProvider";
import { useSpinButtonGroupProvider } from "@react-md/core/spinbutton/useSpinButtonGroupProvider";
import { type ReactElement } from "react";
export default function SpinButtonGroupExample(): ReactElement {
const { movementProps, movementContext } = useSpinButtonGroupProvider();
return (
<Box {...movementProps}>
<SpinButtonGroupProvider value={movementContext}>
<SpinButton aria-label="Hours" min={1} max={12} fallback="hh" />:
<SpinButton aria-label="Minutes" min={0} max={59} fallback="mm" />:
<SpinButton aria-label="Seconds" min={0} max={59} fallback="ss" />
</SpinButtonGroupProvider>
</Box>
);
}