Morphos
Inputs

NumberField

A spinbutton number input with increment and decrement controls and optional number formatting.

Interactive example

Installation

npm install @morphos/inputs
pnpm add @morphos/inputs
yarn add @morphos/inputs
bun add @morphos/inputs

Import

import { NumberField } from '@morphos/inputs'

Usage

@Component()
class MyComponent extends StatefulComponent {
  render() {
    return (
      <NumberField
        class="morphos-number-field"
        defaultValue={1}
        min={1}
        max={99}
        step={1}
        aria-label="Quantity"
      />
    )
  }
}

Controlled

@Component()
class MyComponent extends StatefulComponent {
  @State() offset = 0

  render() {
    return (
      <NumberField
        class="morphos-number-field"
        value={this.offset}
        min={-100}
        max={100}
        step={5}
        onValueChange={(value) => (this.offset = value)}
        aria-label="Offset"
      />
    )
  }
}

Currency formatting

@Component()
class MyComponent extends StatefulComponent {
  render() {
    return (
      <NumberField
        class="morphos-number-field"
        defaultValue={9.99}
        min={0}
        step={0.01}
        formatOptions={{ style: 'currency', currency: 'USD', minimumFractionDigits: 2 }}
        name="price"
        aria-label="Price"
      />
    )
  }
}

Percentage formatting

@Component()
class MyComponent extends StatefulComponent {
  render() {
    return (
      <NumberField
        class="morphos-number-field"
        defaultValue={0.5}
        min={0}
        max={1}
        step={0.01}
        formatOptions={{ style: 'percent' }}
        aria-label="Completion"
      />
    )
  }
}

Props — NumberField

PropTypeDefaultDescription
valuenumberControlled current value
defaultValuenumberUncontrolled initial value
minnumberMinimum allowed value
maxnumberMaximum allowed value
stepnumber1Increment/decrement amount; also determines the display rounding precision
disabledbooleanfalseDisables the field and both buttons
requiredbooleanfalseMarks the input as required
namestringForm field name for submission
formatOptionsIntl.NumberFormatOptionsNumber formatting options (currency, percent, etc.) applied to the displayed value
onValueChange(value: number) => voidFires when the value changes
classstringAdditional CSS classes on the root element
idstringHTML id attribute, applied to the inner input
aria-labelstringAccessible label for the spinbutton input
aria-labelledbystringID of an element that labels the spinbutton input
aria-describedbystringID of an element that describes the spinbutton input

Methods

MethodSignatureDescription
increment()() => voidIncreases the value by step, clamped at max
decrement()() => voidDecreases the value by step, clamped at min

These are instance methods on the NumberField class itself and run the same clamping/rounding logic as the +/- buttons.

data-* attributes

AttributeElementWhen present
data-disabledRootdisabled is true

Rendered structure

<div data-disabled?>
  <button aria-label="Decrement">-</button>
  <input role="spinbutton" aria-valuenow aria-valuemin aria-valuemax />
  <button aria-label="Increment">+</button>
</div>

Accessibility

The inner <input> has role="spinbutton" with aria-valuenow, aria-valuemin, and aria-valuemax. The decrement and increment <button> elements have hardcoded accessible labels ("Decrement" / "Increment") and disable automatically once the value reaches min or max. When formatOptions is set, the displayed value is formatted (e.g. "$9.99") while the underlying numeric value is what gets submitted via the form name field and passed to onValueChange.

formatOptions accepts any Intl.NumberFormat options. The formatted string is shown in the visible input, but the raw number is what onValueChange receives.

Styling example

.morphos-number-field > button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.morphos-number-field > input {
  width: 6rem;
  text-align: center;
}

.morphos-number-field[data-disabled] {
  opacity: 0.5;
}

On this page