Skip to content

Accessibility

raqam implements the WAI-ARIA spinbutton pattern. All ARIA attributes are generated automatically — you don’t need to add them manually.

AttributeValueNotes
role"spinbutton"Always set
aria-valuenownumberValueCurrent numeric value
aria-valueminminValueOnly set when minValue is provided
aria-valuemaxmaxValueOnly set when maxValue is provided
aria-valuetextformatted stringLocalized display value for screen readers
aria-invalid"true" when invalidSet by validate callback or allowOutOfRange
aria-disabled"true" when disabled
aria-readonly"true" when readOnly
aria-required"true" when requiredPass required prop
aria-labelledbyAuto-wired from <NumberField.Label>
aria-describedbyAuto-wired from <NumberField.Description>
inputMode"decimal"Surfaces the numeric keyboard on mobile
type"text"Always text (not number) to avoid browser UI conflicts
autoComplete"off"Prevents autocomplete interference

Increment and decrement buttons receive:

  • aria-label: "Increase value" / "Decrease value" (localizable via prop)
  • tabIndex={-1}: Buttons are intentionally outside the Tab order
  • disabled: When at minValue/maxValue or when the field is disabled
KeyAction
Increment by step
Decrement by step
Shift + ↑Increment by largeStep (default 10)
Shift + ↓Decrement by largeStep
Ctrl/Cmd + ↑Increment by smallStep (default 0.1)
Ctrl/Cmd + ↓Decrement by smallStep
Page UpIncrement by largeStep
Page DownDecrement by largeStep
HomeJump to minValue (if set)
EndJump to maxValue (if set)
EnterCommit the current value (triggers formatting + clamping)
TabMove focus out of the field
  • The input is the only focusable element by default
  • Stepper buttons have tabIndex={-1} (keyboard-accessible via ↑/↓, not Tab)
  • data-focused="" is set on the root element while the input is focused
  • onFocus/onBlur prop forwarding is supported

Use aria-errormessage (via NumberField.ErrorMessage) to announce errors:

<NumberField.Root
locale="en-US"
validate={(v) => v === null ? "Required" : v > 0 ? true : "Must be positive"}
>
<NumberField.Label>Quantity</NumberField.Label>
<NumberField.Input />
<NumberField.ErrorMessage />
{/* Gets role="alert" — announced on change */}
</NumberField.Root>

NumberField.ErrorMessage has role="alert" which causes screen readers to announce the message immediately when it appears.

<NumberField.Root locale="en-US">
<NumberField.Label>Price</NumberField.Label>
<NumberField.Input />
</NumberField.Root>
<NumberField.Root locale="en-US">
<NumberField.Input aria-label="Price in USD" />
</NumberField.Root>
<h2 id="price-heading">Configure price</h2>
<NumberField.Root locale="en-US">
<NumberField.Input aria-labelledby="price-heading" />
</NumberField.Root>

raqam is tested with jest-axe for WCAG compliance:

import { axe, toHaveNoViolations } from "jest-axe";
import { render } from "@testing-library/react";
import { NumberField } from "raqam";
expect.extend(toHaveNoViolations);
test("has no accessibility violations", async () => {
const { container } = render(
<NumberField.Root locale="en-US" defaultValue={42}>
<NumberField.Label>Amount</NumberField.Label>
<NumberField.Input />
</NumberField.Root>
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});