Skip to content

NumberInput

A number input field with optional slider, percent mode, and validation.

Examples

Basic

vue
<script setup>
import { ref } from "vue";
const days = ref(10);
</script>

<NumberInput v-model="days" label="Days" placeholder="Number of days" />

With hint and validation

vue
<NumberInput
  v-model="population"
  label="Population"
  hint="Total number of individuals"
  :min="1000"
  :max="100000"
  :step="1"
/>

Percent mode

vue
<NumberInput v-model="coverage" label="Vaccination coverage" percent :max="1" />

Slider

vue
<NumberInput
  v-model="r0"
  label="R0"
  hint="Basic reproduction number"
  :step="0.1"
  :min="1"
  :max="18"
  slider
/>

Range slider

Bind v-model:range with a [low, high] tuple to render a two-handle slider. Range mode is enabled automatically by the binding — there's no explicit toggle prop.

vue
<script setup>
import { ref } from "vue";
const ageRange = ref([18, 65]);
</script>

<NumberInput
  v-model:range="ageRange"
  label="Age range"
  :min="0"
  :max="100"
  number-type="integer"
/>

Range slider with split bindings

When your state stores the bounds in separate refs (rather than as a tuple), bind them directly with v-model:lower and v-model:upper. You can bind either pair or combine them with v-model:range — writes from the component go to every bound sink.

vue
<script setup>
import { ref } from "vue";
const minAge = ref(18);
const maxAge = ref(65);
</script>

<NumberInput
  v-model:lower="minAge"
  v-model:upper="maxAge"
  label="Age range"
  :min="0"
  :max="100"
  number-type="integer"
/>

Range mode works with percent and live as well:

vue
<NumberInput
  v-model:range="coverageRange"
  label="Coverage range"
  percent
  live
  :max="1"
/>

Custom display format

Pass format to control how the value is displayed in the text input and in slider thumb/min/max labels. Accepts a NumberFormat — a preset name (optionally with a :N digits suffix, e.g. "percent:1"), a printf-style format string ("%.2f"), or a (value: number) => string function. The internal model stays a number — only the displayed text changes.

When unset, the default formatting follows the percent and decimals props. When set, format overrides both. Formats that add suffixes or scale the value (e.g. "percent:1""12.3%") may not round-trip through the text input — pair them with percent: true for value scaling and use format for display shaping.

The older slider-display prop (a (value: number) => string function that only affected slider thumb/min/max labels) is deprecated but still honored when format is unset. Prefer format for new code.

vue
<script setup>
import { ref } from "vue";
const dayMs = 24 * 60 * 60 * 1000;
const dateStart = Date.UTC(2024, 0, 1);
const dateEnd = Date.UTC(2024, 11, 31);
const dateRange = ref([Date.UTC(2024, 2, 1), Date.UTC(2024, 8, 30)]);
const formatDate = (ms) =>
  new Date(ms).toLocaleDateString("en-US", { month: "short", day: "numeric" });
</script>

<NumberInput
  v-model:range="dateRange"
  label="Date range"
  :min="dateStart"
  :max="dateEnd"
  :step="dayMs"
  :format="formatDate"
/>

Live slider

With live, the model updates while dragging the slider thumb rather than only on release.

vue
<NumberInput
  v-model="coverage"
  label="Vaccination coverage"
  percent
  slider
  live
  :max="1"
/>

Live input

With live on a regular input, the model updates as you type (debounced 300ms). Arrow keys and spinner buttons commit immediately.

vue
<NumberInput v-model="days" label="Days" live />

Integer type

With number-type="integer", decimal values are truncated to whole numbers on commit. When combined with percent, the display value (e.g. 42%) is treated as the integer — so internal values like 0.42 are valid.

vue
<NumberInput v-model="days" label="Steps" number-type="integer" />

Decimal places

Display precision is inferred from step (e.g. step="0.001" in percent mode shows tenths of a percent). Set decimals explicitly to override.

vue
<NumberInput
  v-model="coverage"
  label="Coverage"
  percent
  :step="0.001"
  :max="1"
/>
<NumberInput v-model="r0" label="R0" :decimals="3" :min="0" :max="18" />

Required

With required, clearing the field shows a validation error on commit.

vue
<NumberInput v-model="days" label="Days" required />

Combine required with live to validate as the user types (debounced).

vue
<NumberInput v-model="days" label="Days (on blur)" required />
<NumberInput v-model="days" label="Days (live)" required live />

Hidden label

Use hide-label to visually hide the label while keeping it available to screen readers. Useful when a heading or surrounding context already describes the input visually.

vue
<NumberInput v-model="days" label="Days" hide-label />

Model

NameType
v-modelnumber
v-model:rangeNumberRange
v-model:lowernumber
v-model:uppernumber

Props

PropTypeRequiredDefault
labelstringNo
hideLabelbooleanNo
placeholderstringNo
stepnumberNo
minnumberNo
maxnumberNo
hintstringNo
percentbooleanNo
sliderbooleanNo
livebooleanNo
numberType"integer" | "float"No
requiredbooleanNo
decimalsnumberNo
percent1") may not round-trip through the text input — use
// percent: true for value scaling and format for display shaping.
format?: NumberFormat`Yes
sliderDisplay(value: number) =&gt; stringNo