Menu


Number field


A text field for a quantity or a measure, with its unit and minus and plus buttons that step it between its limits.

Customize the preview


Choices
Show
Flags
View
kg

Options


Number field options
ChoiceValuesDefaultWhat it does
sizesmbk-number-field--size-<value>mdsmmd
The input’s two heights, with its padding and text. The step buttons’ icons are 16 px at both; at sm each button is a 24 px square, so it fits inside the small well. Small text never goes under 16 px on a phone.
errorsmbk-number-field--errorOn or offOff
The number is not one the form can take. Draws the danger border, kept while focused, and the error icon before the unit.

Partssmbk-number-field__controlsmbk-number-field__unitsmbk-number-field__separatorsmbk-number-field__decrementsmbk-number-field__increment

Examples


Usage

A count with its limits in the help text. Type a number, press the buttons, or use the up and down arrow keys.

From 1 to 50.

With a unit

The unit sits after the number in quieter text, and a screen reader hears it after the number.

kg

Up to 30 kg, in steps of 0.5.

At the limits

At its lowest the minus button is disabled, at its highest the plus button.

Another language

Numbers are read and written as the reader’s language writes them, here German, and can be shown as a percentage.

Written the German way, with a decimal comma.

Error

The input’s error look, with the warning icon before the unit, and the field’s message saying what to enter.

Read only

The number can be read and copied; the buttons are disabled.

kg

Guidelines


Purpose

A text field for a quantity or a measure, where a unit and limits matter: a weight, a count of seats, a width in pixels. The reader types the number or steps it with the minus and plus buttons or the arrow keys, and the field keeps it between its lowest and highest values. It reads and writes numbers the way the reader's language does, so "1,5" and "1.5" both work where each is how one and a half is written.

When to use

  • A quantity or a measure: a weight, a price, a count, a size in pixels, a percentage.
  • A value with limits and a natural step, such as 1 to 50 seats in steps of 1.

When not to use

  • A number that is really a name: a phone number, a postcode, a card number, an order number. Use the input with a numeric keyboard hint; steps and limits mean nothing there.
  • A value picked roughly from a wide range, where the exact number matters less. Use the slider.
  • A choice of a few fixed values. Use the segmented control or the select.

Usage rules

Do

  • Give the unit beside the number, and in the label too when it decides what the reader types ("Weight in kg").
  • Choose a step the reader expects: 1 for a count, 0.5 or 0.1 for a measure.

Don't

  • Use it for a phone number, a postcode or any number that is a name.
  • Leave the limits unsaid: a number changed on leaving the field surprises a reader who did not know them.
kg

Up to 30 kg.

Do

Use it for a quantity, with its unit and its limits said up front.

Don't

A phone number is a name, not a quantity: stepping it means nothing. Use the input.

Anatomy

  • The block, always on the input's well: its markup carries the input's classes as well as its own.
  • The control part, a text control holding the number, which is also the input's control part. Its figures are all one width, so a number that steps does not shift sideways.
  • In error, the input's error icon, a warning sign before the unit.
  • The unit part, optional: the unit after the number, in quieter text, a step smaller than the number at the medium size.
  • The separator part, a hairline between what is typed and the buttons.
  • The decrement and increment parts, ghost icon buttons with a minus and a plus, in the input's trailing action. At the medium size they are small icon buttons; at the small size they are the extra-small square, with the same 16 px icons, so they fit inside the small well.

Behavior

The minus and plus buttons move the number by its step; holding one down keeps stepping. The up and down arrow keys do the same while the field has focus, as do Page Up and Page Down, and Home and End jump to the lowest and highest values when the field has them. At a limit the button that would pass it is disabled. Scrolling the page never changes the number.

The field takes only what can make a number in the reader's language: figures, the decimal mark, the minus sign. A typed number is settled when the reader leaves the field or presses Enter: kept inside the limits and rounded to the step, then written the reader's way. The page hears the new number then, and after every step, not on each key. An emptied field reports null, and the page empties the field by giving it null.

The wrapper holds its own number, or follows the number the page gives it. Given a name, it sends the number with the form as plain digits, whatever the language shows, and nothing while disabled. A required number field stops its form being sent while it is empty: the field takes focus, the browser says why, and it takes its error look until a number is typed. The form's reset puts the starting number back. Read-only, the number can be read and copied and the buttons are disabled; disabled, the field and its buttons are out of reach.

With hideButtons the field is a bare number box: the same well, text and keys without the line and the two buttons, for a row of boxes too narrow to hold them.

Accessibility


What it does for you

  • A screen reader hears the field's name, "number field", the number, and then the unit.
  • The field is one stop in the Tab order. The buttons are left out of it, because the arrow keys do the same from the field; they are named "Decrease" and "Increase" followed by the field's name, such as "Increase seats", so a screen reader on its own can still press them.
  • Each button is a 24 px square or larger, the least a press needs.
  • A number brought back inside the limits when the reader leaves the field is read out, so the change is never silent.
  • In development, a number field with no name logs a warning in the console.
  • In a forced-color mode, the hairline between the unit and the buttons is drawn in the reader's text color.

What you must do

  • Give it a label like any input; inside a field it has one.
  • Say the limits in the field's help text, such as "From 1 to 50", so they are known before the first try.

How to test

  • Tab to the field: it is one stop, and the buttons are skipped. Press Up Arrow and Down Arrow: the number moves by its step, and the page hears it.
  • Type a number outside the limits and press Enter: it is brought inside them and rounded to the step, and a screen reader says the new number.
  • With a screen reader, the field is read with its name, "number field", its number and its unit, and each button as "Increase" or "Decrease" with the field's name.
  • With text at 200% and the page 320 pixels wide, the number, the unit and the two buttons stay whole.
  • In a forced-color mode, the focus border, the number and the hairline stay visible.

Keyboard

Number field keys
KeyWhat it does
Tab
Moves focus to the field, and out of it, which settles a typed number. The two buttons are skipped.
Up ArroworDown Arrow
Moves the number up or down by its step.
Page UporPage Down
Moves the number up or down, as the arrow keys do.
HomeorEnd
Jumps to the lowest or highest value, when the field has limits.
Enter
Settles a typed number: kept inside the limits, rounded to the step and written the reader's way.

Source


The files the number field is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.

Stylesheet
number-field.css
/* smbk-number-field — Number field. Figma "Number field" 7106:947.
 * Built on the input: the block always sits on an smbk-input well, the text
 * control is the input's control part, and the unit, a hairline and the minus
 * and plus buttons (ghost icon buttons) sit in the input's trailing action.
 * The padding, the value's text, hover, focus, error, read-only, disabled and
 * the two sizes are the input's own, so a change to the input reaches the
 * number field. This stylesheet adds the unit, the line, the buttons' place
 * and figures of one width. */

/* Figures of one width, so a value that steps does not shift sideways. */
.smbk-number-field__control,
.smbk-number-field__unit {
  font-variant-numeric: tabular-nums;
}

/* The unit, the line and the two buttons, 4 apart. */
.smbk-number-field .smbk-input__trailing-action {
  gap: var(--smbk-space-inline-xs);
}

/* The unit is quieter than the value and a step smaller beside the md value: Body SM. Figma keeps its color in
   every state, disabled too. */
.smbk-number-field__unit {
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
  white-space: nowrap;
}

.smbk-input--size-sm .smbk-number-field__unit {
  font-family: var(--smbk-typography-field-sm-font-family);
  font-size: var(--smbk-typography-field-sm-font-size);
  font-weight: var(--smbk-typography-field-sm-font-weight);
  line-height: var(--smbk-typography-field-sm-line-height);
  letter-spacing: var(--smbk-typography-field-sm-letter-spacing);
}

/* A hairline between what is typed and the buttons, as tall as a medium icon at both sizes, as Figma draws it. */
.smbk-number-field__separator {
  flex-shrink: 0;
  width: var(--smbk-border-width-hairline);
  height: var(--smbk-size-icon-md);
  background: var(--smbk-color-border-subtle);
}

/* The rule React Aria would write into the page itself (the wrapper keeps it from doing so, since a Site's
   security policy refuses an inline style). A touch on a button starts the press at once, with no wait for a
   double tap, while the page still scrolls and zooms under it; unlike the button's own touch-action: manipulation,
   a scroll that starts on a button also cancels its press in Safari, so a held button never starts stepping while
   the page scrolls. Two classes, so it wins over the button's own rule whichever stylesheet loads first. */
.smbk-number-field__decrement.smbk-btn,
.smbk-number-field__increment.smbk-btn {
  touch-action: pan-x pan-y pinch-zoom;
}

/* Small: Figma puts 32 px buttons in the 32 px well, which would cover the well's edge. Each button here is
   the 24 px square, with the 16 px icon Figma draws, and the margin gives back the rest of Figma's 32 px place,
   so the icons sit where Figma draws them. Two classes on the icon, so it wins over the button's own small icon
   whichever stylesheet loads first. */
.smbk-number-field__decrement.smbk-btn--size-xs,
.smbk-number-field__increment.smbk-btn--size-xs {
  margin-inline: calc((var(--smbk-size-control-sm) - var(--smbk-size-control-xs)) / 2);
}

.smbk-number-field__decrement.smbk-btn--size-xs .smbk-btn__icon,
.smbk-number-field__increment.smbk-btn--size-xs .smbk-btn__icon {
  font-size: var(--smbk-size-icon-sm);
}

/* ---- touch: the two steps stop their press areas halfway to each other ----
   Every button takes an invisible press area on a touch screen; the field's two steps stand closer than that, so
   between them each area reaches only halfway into their gap, while both can be pressed. Nothing drawn moves. */

@media (pointer: coarse) {
  .smbk-input__trailing-action:has(> .smbk-number-field__increment:not(:disabled)) > .smbk-btn.smbk-number-field__decrement::after {
    inset-inline-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-xs) / -2);
  }

  .smbk-input__trailing-action:has(> .smbk-number-field__decrement:not(:disabled)) > .smbk-btn.smbk-number-field__increment::after {
    inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-xs) / -2);
  }

  .smbk-input__trailing-action:has(> .smbk-number-field__increment:not(:disabled)) > .smbk-number-field__decrement.smbk-btn--size-xs::after {
    inset-inline-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (var(--smbk-space-inline-xs) + var(--smbk-size-control-sm) - var(--smbk-size-control-xs)) / -2);
  }

  .smbk-input__trailing-action:has(> .smbk-number-field__decrement:not(:disabled)) > .smbk-number-field__increment.smbk-btn--size-xs::after {
    inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (var(--smbk-space-inline-xs) + var(--smbk-size-control-sm) - var(--smbk-size-control-xs)) / -2);
  }
}

/* ---- high contrast (forced colors) ---- */
/* The hairline is a fill, which forced colors drop; draw it in the text color the reader chose. */
@media (forced-colors: active) {
  .smbk-number-field__separator {
    background: CanvasText;
  }
}
Tokens it reads
Number field tokens
Token and CSSValuePreviewFoundation
space.inline.xs--smbk-space-inline-xs4pxSpacing
color.text.subtle--smbk-color-text-subtlelight: #4e555edark: #9ba5b1changes with themeColor
typography.body-sm--smbk-typography-body-sm-font-family--smbk-typography-body-sm-font-size--smbk-typography-body-sm-font-weight--smbk-typography-body-sm-line-height--smbk-typography-body-sm-letter-spacing--smbk-typography-body-sm-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
typography.field-sm--smbk-typography-field-sm-font-family--smbk-typography-field-sm-font-size--smbk-typography-field-sm-font-weight--smbk-typography-field-sm-line-height--smbk-typography-field-sm-letter-spacing--smbk-typography-field-sm-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: min: 16px · max: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
border-width.hairline--smbk-border-width-hairline1pxBorder width
size.icon.md--smbk-size-icon-md20pxSize
color.border.subtle--smbk-color-border-subtlelight: #c4cdd8dark: #2d3237changes with themeColor
size.control.sm--smbk-size-control-smcomfortable: 32pxcompact: 28pxchanges with densitySize
size.control.xs--smbk-size-control-xs24pxSize
size.icon.sm--smbk-size-icon-sm16pxSize
size.target.comfortable--smbk-size-target-comfortable44pxSize
Manifest
number-field.manifest.json
{
  "name": "number-field",
  "block": "smbk-number-field",
  "summary": "A text field for a quantity or a measure, with its unit and minus and plus buttons that step it between its limits.",
  "adapt": "css",
  "axes": {
    "size": {
      "values": [
        "md",
        "sm"
      ],
      "default": "md",
      "description": "The input’s two heights, with its padding and text. The step buttons’ icons are 16 px at both; at sm each button is a 24 px square, so it fits inside the small well. Small text never goes under 16 px on a phone."
    }
  },
  "flags": {
    "error": {
      "description": "The number is not one the form can take. Draws the danger border, kept while focused, and the error icon before the unit."
    }
  },
  "parts": [
    "control",
    "unit",
    "separator",
    "decrement",
    "increment"
  ],
  "figmaNodeId": "7106:947"
}