Skip to main content
Vuetify0 v1.0 is here
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

Input

Headless text input with integrated validation and automatic form registration.

Edit this page
Report a Bug
Open issues
View on GitHub
Copy Markdown

PreviewRenders elementIntermediateJul 15, 2026

Usage

The Input supports text, email, password, and other native input types. Validation rules run on blur by default, with lazy and eager modifiers available.

We'll never share your email.
<script setup lang="ts">
  import { Input } from '@vuetify/v0'
  import { shallowRef } from 'vue'

  const email = shallowRef('')
</script>

<template>
  <div class="flex flex-col gap-1 max-w-sm mx-auto">
    <Input.Root
      id="email"
      v-model="email"
      label="Email"
      :rules="[
        (v: string) => !!v || 'Email is required',
        (v: string) => /.+@.+\..+/.test(v) || 'Must be a valid email',
      ]"
      type="email"
    >
      <label class="text-sm font-medium text-on-surface" for="email">
        Email
      </label>

      <Input.Control
        class="w-full px-3 py-2 rounded-lg border border-divider bg-surface text-on-surface placeholder:text-on-surface-variant/50 outline-none data-[focused]:border-primary data-[state=invalid]:border-error transition-colors"
        placeholder="you@example.com"
      />

      <Input.Description class="text-xs text-on-surface-variant">
        We'll never share your email.
      </Input.Description>

      <Input.Error v-slot="{ errors }" class="text-xs text-error">
        <span v-for="error in errors" :key="error">{{ error }}</span>
      </Input.Error>
    </Input.Root>
  </div>
</template>

Anatomy

vue
<script setup lang="ts">
  import { Input } from '@vuetify/v0'
</script>

<template>
  <Input.Root>
    <Input.Control />

    <Input.Description />

    <Input.Error />
  </Input.Root>
</template>

Architecture

Root creates a validation context, provides it to children, and manages focus/validation lifecycle. Control is the native <input> (or any element via as). Description and Error auto-wire their IDs into Control’s ARIA attributes.

Input Architecture

Use controls to zoom and pan. Click outside or press Escape to close.

Input Architecture

Examples

Contact Form

A multi-field form showing createForm integration, lazy validation, and server-side error injection. Each Input.Root creates its own createValidation instance and auto-registers with the parent Form on mount — no manual wiring is required.

The validateOn="blur lazy" modifier defers validation until the first blur, then validates on every subsequent blur. This avoids showing errors while the user is still typing their first attempt. After the initial blur, the field becomes responsive again so corrections are caught immediately.

Server-side error injection is demonstrated on the email field: entering taken@example.com and submitting causes the composable to push an error string through the :error-messages prop. This pattern keeps client-side rules and server responses in the same display channel without any custom error UI.

FileRole
useContact.tsComposable — form instance, field refs, submit with server-side validation
ContactForm.vueReusable component — three Input fields with different rules and types
contact-form.vueDemo — wires composable to form, shows submitted data

Try submitting with taken@example.com to see server-side error injection.

Debounced search with validateOn="input" for real-time validation as the user types. The composable watches the Input’s value ref directly — no event wiring needed.

Because Input.Root exposes value as a plain writable Ref, any composable can watch it without subscribing to DOM events. useSearch.ts uses useTimer from @vuetify/v0 for debouncing — staying within the v0 primitive layer rather than reaching for raw setTimeout. Minimum 2 characters are required before the search fires.

Visual states are driven exclusively through data attributes: data-[focused]:border-primary and data-[state=invalid]:border-error on Input.Control with no slot props or conditional classes. This is the recommended styling approach — it keeps the template clean and decouples visual states from layout logic.

FileRole
useSearch.tsComposable — debounced search with mock results, watches the query ref
SearchInput.vueReusable component — Input with search icon, loading spinner, result count
search.vueDemo — renders SearchInput with a result list

Recipes

validateOn Modes

Control when validation runs with the validateOn prop and optional lazy/eager modifiers:

vue
<template>
  <!-- Validate on blur (default) -->
  <Input.Root validate-on="blur" />

  <!-- Validate on every keystroke -->
  <Input.Root validate-on="input" />

  <!-- Only validate on form submit -->
  <Input.Root validate-on="submit" />

  <!-- Lazy: skip validation until first blur, then validate on blur -->
  <Input.Root validate-on="blur lazy" />

  <!-- Eager: after first error, validate on every keystroke -->
  <Input.Root validate-on="blur eager" />
</template>

Manual Error State

Override validation with the error and error-messages props for server-side errors:

vue
<template>
  <Input.Root
    :error="!!serverError"
    :error-messages="serverError"
    :rules="[(v) => !!v || 'Required']"
  >
    <Input.Control />
    <Input.Error v-slot="{ errors }">
      <span v-for="e in errors" :key="e">{{ e }}</span>
    </Input.Error>
  </Input.Root>
</template>

Data Attributes

Style interactive states without slot props:

vue
<template>
  <Input.Control class="data-[focused]:border-primary data-[state=invalid]:border-error" />
</template>
AttributeValuesComponents
data-statepristine, valid, invalidRoot, Control
data-dirtytrueRoot
data-focusedtrueRoot, Control
data-disabledtrueRoot, Control
data-readonlytrueRoot, Control

Accessibility

Input.Control renders as a native <input> and manages all ARIA attributes automatically.

ARIA Attributes

AttributeValueNotes
aria-invalidtrueWhen validation fails or error prop is set
aria-labelLabel textFrom Root’s label prop
aria-describedbyDescription IDOnly present when Input.Description is mounted
aria-errormessageError IDOnly present when Input.Error is mounted and errors exist
aria-requiredtrueFrom Root’s required prop
requiredtrueNative attribute, from Root’s required prop
disabledtrueNative attribute, from Root’s disabled prop
readonlytrueNative attribute, from Root’s readonly prop

Keyboard Navigation

Standard native <input> keyboard behavior. No custom key handlers — the browser handles focus, selection, and editing.

FAQ

Discord
Need help? Join our community for support and discussions ↗
Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/