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;
}
}
A SpinButton has no styles by default and requires an aria-label or
aria-labelledby for accessibility. The value can be changed by:
Backspace or DeleteAny 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" />;
}
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} />;
}
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:
minDigits is provided, return - for each digitmin is provided, return - for each digitfallback is provided, use that valueimport { 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}
/>
</>
);
}
The value can be controlled by providing a value and onValueChange. The
onValueChange callback provides an object with:
event - either a KeyboardEvent, FormEvent, or FocusEventreason - either "type", "cleared", "typed-to-completion", or "change"value - the next valueCurrent 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>
</>
);
}
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);
}
}}
/>
</>
);
}
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>
);
}