Zetta v1.6

Components · Forms & selection

Number input

Numeric input supporting integer and decimal (float) values with increment/decrement controls. Split from zetta-form-v1_4.md (removed in v1.5) in v1.4.

Preview

Build with the skill

No package to install — hand this to your AI to generate or migrate the component in your stack.

Prompt for your AI
Apply the Zetta "Number input" component to this project using the zetta-design-md skill.

WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `number-input` spec and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `number-input` spec in this project's stack.

AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.

ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility.

Anatomy & rules

┌──────────────────────────┬───┐
│  42                      │ ▲ │  ← increment stepper
│                          │ ▼ │  ← decrement stepper
└──────────────────────────┴───┘

Stepper buttons are optional (showStepper={false} hides them). The field always accepts keyboard arrow keys regardless of stepper visibility.

State Visual
Default {colors.border-strong} border, {colors.surface-card} fill
Hover {colors.muted} border
Focus {colors.primary} border + glow
Error {colors.danger} border + glow
Disabled Muted border, disabled fill, not-allowed cursor
Read-only {colors.hairline} border, {colors.surface-muted} fill

When to use

  • Numeric entry where the value must be a number: quantities, ages, prices, dimensions.
  • Fields where increment/decrement controls help the user adjust a value.

When not to use

  • Free-form text with occasional numbers — use Text Input.
  • Selecting from a range with a visual slider — use Range.
  • Phone numbers, zip codes, credit cards — use Text Input with type="tel" / inputmode="numeric".

Do

  • Set min and max constraints when the valid range is known.
  • Use step="0.01" or appropriate decimal precision for monetary or measured values.
  • Show the unit (kg, %, $) in a suffix or in the label — not inside the field.

Don't

  • Don't use type="number" for values that aren't truly numeric (phone, zip) — use inputmode="numeric" on a text input instead.
  • Don't omit min/max for fields with logical bounds (e.g. quantity ≥ 1).
  • Label: always visible, never placeholder-only.
  • Placeholder: show expected format or example — "0", "1.5", "100".
  • Unit: show unit in suffix or label — "Weight (kg)", not inline in the field.
  • Error: be specific — "Value must be between 1 and 100." not "Invalid number."
  • Stepper aria-label: "Increase [field name]" / "Decrease [field name]".

Keyboard

Key Behavior
Tab Focus input
Up Arrow Increment by step
Down Arrow Decrement by step
Page Up Increment by step * 10
Page Down Decrement by step * 10
Home Set to min (if defined)
End Set to max (if defined)

Accessibility

  • Use type="number" — native semantics handle most AT behavior.
  • Add aria-valuemin, aria-valuemax, aria-valuenow if using a custom implementation.
  • Always pair with a <label>. Do not rely on placeholder as label.
  • Stepper buttons require aria-label ("Increase value", "Decrease value").
  • Error state: aria-invalid="true" and aria-describedby.

Spec

Values and token references resolved straight from the Zetta spec.

input-number
backgroundColor
{colors.surface-card}
textColor
{colors.ink}
borderColor
{colors.border-strong}
borderWidth
1px
borderRadius
{rounded.base}
fontFamily
Geist
fontSize
14px
fontWeight
400
lineHeight
1.5
height
36px
paddingX
{spacing.sm}
paddingY
0
width
100%
textAlign
left
transition
border-color {motion.duration-fast}, box-shadow {motion.duration-fast}
input-number-hover
borderColor
{colors.muted}
input-number-focus
borderColor
{colors.primary}
outline
none
boxShadow
0 0 0 2px {colors.primary-bg}
input-number-disabled
backgroundColor
{colors.disabled-bg}
textColor
{colors.disabled-text}
borderColor
{colors.disabled-border}
cursor
not-allowed
input-number-error
borderColor
{colors.danger}
boxShadow
0 0 0 2px {colors.danger-bg}
input-number-read-only
backgroundColor
{colors.surface-muted}
textColor
{colors.ink}
borderColor
{colors.hairline}
input-number-stepper
display
flex
flexDirection
column
borderLeft
1px solid {colors.hairline}
height
36px
input-number-stepper-button
width
24px
height
18px
display
flex
alignItems
center
justifyContent
center
backgroundColor
transparent
textColor
{colors.muted}
cursor
pointer
borderRadius
0
input-number-stepper-button-hover
backgroundColor
{colors.surface-muted}
textColor
{colors.ink}
input-number-stepper-button-disabled
textColor
{colors.disabled-text}
cursor
not-allowed