Number Field
v0.0.10
mix mishka.ui.gen.headless number_field
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)
<.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.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Root | root | data-disabled, data-readonly, data-required, data-scrubbing | Carries the NumberField hook; reads min/max/step/format/locale from data attributes; takes class. |
| Scrub area / label / cursor | scrub-area / scrub-area-label / scrub-area-cursor | data-direction, data-pixel-sensitivity | Optional 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. |
| Group | group | - | Wraps the stepper buttons and the input; takes group_class. |
| Decrement / Increment | decrement / increment | aria-label, aria-controls, tabindex="-1" | Stepper buttons; press-and-hold to auto-repeat; they take decrement_class and increment_class. |
| Input | input | aria-roledescription | The visible, locale-formatted text input; takes input_class. |
| Hidden input | hidden-input | aria-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.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | Unique id (required by the hook) |
name
|
string
| — | — | No | Name for the hidden form input |
value
|
any
| — | — | No | Current/initial numeric value |
min
|
any
| — | — | No | Minimum value |
max
|
any
| — | — | No | Maximum value |
step
|
any
|
1
| — | No | Step for buttons/arrows/scrub (number or "any") |
small_step
|
any
|
0.1
| — | No | Step while holding Alt |
large_step
|
any
|
10
| — | No | Step while holding Shift (and PageUp/PageDown) |
snap_on_step
|
boolean
|
false
| — | No | Snap to the nearest step on increment/decrement |
allow_out_of_range
|
boolean
|
false
| — | No | Let typed values exceed min/max (steps still clamp) |
allow_wheel_scrub
|
boolean
|
false
| — | No | Scrub with the mouse wheel while focused |
format
|
map
| — | — | No | Intl.NumberFormat options map (currency/percent/unit/…) |
locale
|
string
| — | — | No | BCP-47 locale for formatting/parsing |
disabled
|
boolean
|
false
| — | No | Disable interaction (data-disabled) |
readonly
|
boolean
|
false
| — | No | Block changes but stay focusable (data-readonly) |
required
|
boolean
|
false
| — | No | Mark required for form validation (data-required) |
placeholder
|
string
| — | — | No | Input placeholder |
on_change
|
string
| — | — | No | LiveView event pushed on change ({value}) |
on_commit
|
string
| — | — | No | LiveView event pushed on commit — blur/release ({value}) |
form
|
string
| — | — | No | Form id owning the hidden input (when rendered outside the form) |
aria_roledescription
|
string
|
Number field
| — | No | Role description for the input |
buttons
|
boolean
|
true
| — | No | Render the increment/decrement buttons |
scrub_direction
|
string
|
horizontal
| — | No | Scrub axis: horizontal | vertical |
scrub_sensitivity
|
any
|
2
| — | No | Pixels of movement per step while scrubbing |
scrub_teleport
|
any
| — | — | No | Cursor wrap distance (px) from the scrub-area center |
scrub_cursor
|
boolean
|
false
| — | No | Render a custom cursor element for the scrub area |
class
|
any
| — | — | No | Extra classes for the root |
scrub_area_class
|
any
| — | — | No | Extra classes for the scrub-area part |
scrub_area_label_class
|
any
| — | — | No | Extra classes for the scrub-area label |
scrub_cursor_class
|
any
| — | — | No | Extra classes for the scrub-area-cursor part |
group_class
|
any
| — | — | No | Extra classes for the stepper group wrapper part |
decrement_class
|
any
| — | — | No | Extra classes for the decrement button part |
increment_class
|
any
| — | — | No | Extra classes for the increment button part |
input_class
|
any
| — | — | No | Extra classes for the input part |
hidden_class
|
any
| — | — | No | — |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
scrub_area
| No | Optional drag region (e.g. a label) that scrubs the value |
scrub_cursor_icon
| No | Custom icon rendered inside the scrub-area-cursor part |
decrement_icon
| No | Custom icon rendered inside the decrement button (replaces the default −) |
increment_icon
| No | Custom icon rendered inside the increment button (replaces the default +) |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
number_field
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless number_field