Number Field

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJSApache-2.0
Installation mix mishka.ui.gen.headless number_field
Source View Source

Overview of the Phoenix and LiveView Headless Number Field Component

The Mishka Chelekom Headless Number Field ships zero opinionated styling - only the semantic markup, full WAI-ARIA spinbutton wiring, press-and-hold stepper buttons, optional drag-to-scrub, and locale-aware formatting via Intl.NumberFormat. Every part - scrub-area, decrement, input, increment and hidden-input - is addressable through a *_class attribute, so you style it entirely with your own Tailwind classes while the accessibility and behavior keep working underneath.

The root takes class. The parts take scrub_area_class, scrub_area_label_class, scrub_cursor_class, group_class (the wrapper around the buttons and the input), decrement_class, input_class, increment_class and hidden_class. The hidden input is drawn only when the field has a name. An inline style keeps it visually hidden, and hidden_class adds your classes to it.

Base UI Number Field example (Phoenix headless)

Base UI Number Field example (Phoenix headless)

heex
<.number_field
  id="baseui-number_field-hero"
  value={100}
  scrub_cursor
  class="flex flex-col items-start gap-1"
  scrub_area_class="cursor-ew-resize font-bold select-none"
  scrub_area_label_class="cursor-ew-resize text-sm font-bold text-neutral-950 dark:text-white"
  scrub_cursor_class="drop-shadow-[0_1px_1px_#0008] filter"
  group_class="flex h-8"
  decrement_class="flex h-full w-8 items-center justify-center border border-r-0 border-neutral-950 bg-white bg-clip-padding text-neutral-950 outline-0 select-none dark:border-white dark:bg-neutral-950 dark:text-white hover:not-data-[disabled]:bg-neutral-100 dark:hover:not-data-[disabled]:bg-neutral-800 active:not-data-[disabled]:bg-neutral-200 dark:active:not-data-[disabled]:bg-neutral-700 data-[disabled]:border-neutral-500 data-[disabled]:text-neutral-500 dark:data-[disabled]:border-neutral-400 dark:data-[disabled]:text-neutral-400"
  increment_class="flex h-full w-8 items-center justify-center border border-l-0 border-neutral-950 bg-white bg-clip-padding text-neutral-950 outline-0 select-none dark:border-white dark:bg-neutral-950 dark:text-white hover:not-data-[disabled]:bg-neutral-100 dark:hover:not-data-[disabled]:bg-neutral-800 active:not-data-[disabled]:bg-neutral-200 dark:active:not-data-[disabled]:bg-neutral-700 data-[disabled]:border-neutral-500 data-[disabled]:text-neutral-500 dark:data-[disabled]:border-neutral-400 dark:data-[disabled]:text-neutral-400"
  input_class="h-full w-[7ch] border border-neutral-950 bg-white px-2 text-left text-sm font-normal text-neutral-950 tabular-nums any-pointer-coarse:text-base dark:border-white dark:bg-neutral-950 dark:text-white focus:z-1 focus:outline-2 focus:-outline-offset-1 focus:outline-neutral-950 dark:focus:outline-white"
>
  <:scrub_area>Amount</:scrub_area>
  <:scrub_cursor_icon>
    <svg width="26" height="14" viewBox="0 0 24 14" fill="black" stroke="white" style="display: block;">
      <path d="M19.5 5.5L6.49737 5.51844V2L1 6.9999L6.5 12L6.49737 8.5L19.5 8.5V12L25 6.9999L19.5 2V5.5Z" />
    </svg>
  </:scrub_cursor_icon>
  <:decrement_icon>
    <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="round" style="display: block;">
      <path d="M1.5 8h13" />
    </svg>
  </:decrement_icon>
  <:increment_icon>
    <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="round" style="display: block;">
      <path d="M1.5 8h13M8 14.5v-13" />
    </svg>
  </:increment_icon>
</.number_field>

Parts & data attributes

Each part carries a stable data-part and a structural chelekom-number_field__* class you can target, plus root-level state attributes reflecting disabled/readonly/ required/scrubbing.

Partdata-partState / ARIAPurpose
Rootrootdata-disabled, data-readonly, data-required, data-scrubbingCarries the NumberField hook; reads min/max/step/format/locale from data attributes; takes class.
Scrub area / label / cursorscrub-area / scrub-area-label / scrub-area-cursordata-direction, data-pixel-sensitivityOptional drag region that scrubs the value, with its label and an optional custom cursor shown while scrubbing; they take scrub_area_class, scrub_area_label_class and scrub_cursor_class.
Groupgroup-Wraps the stepper buttons and the input; takes group_class.
Decrement / Incrementdecrement / incrementaria-label, aria-controls, tabindex="-1"Stepper buttons; press-and-hold to auto-repeat; they take decrement_class and increment_class.
Inputinputaria-roledescriptionThe visible, locale-formatted text input; takes input_class.
Hidden inputhidden-inputaria-hidden="true", tabindex="-1"Visually hidden type=number input carrying the raw value for form submission; drawn only when name is set, and takes hidden_class.

Accessibility

Follows the WAI-ARIA Spinbutton pattern: Arrow Up/Down step the value by step (Shift for large_step, Alt for small_step), PageUp/PageDown step by the large step, Home/End jump to min/max, and the mouse wheel can scrub the value when focused (allow_wheel_scrub). The input exposes aria-roledescription and the stepper buttons carry aria-label and aria-controls.