Skip to main content
react-md

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:

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" />;
}

Press Enter to start editing.

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} />;
}

Press Enter to start editing.

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:

-
--
HH
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}
      />
    </>
  );
}

Press Enter to start editing.

Controlling the value

The value can be controlled by providing a value and onValueChange. The onValueChange callback provides an object with:

--

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>
    </>
  );
}

Press Enter to start editing.

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"

00
"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);
          }
        }}
      />
    </>
  );
}

Press Enter to start editing.

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.

hh
:
mm
:
ss
"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>
  );
}

Press Enter to start editing.