Input
Headless text input with integrated validation and automatic form registration.
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.
Anatomy
<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.
Examples
Try submitting with taken@example.com to see server-side error injection.
Recipes
validateOn Modes
Control when validation runs with the validateOn prop and optional lazy/eager modifiers:
<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:
<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:
<template>
<Input.Control class="data-[focused]:border-primary data-[state=invalid]:border-error" />
</template>| Attribute | Values | Components |
|---|---|---|
data-state | pristine, valid, invalid | Root, Control |
data-dirty | true | Root |
data-focused | true | Root, Control |
data-disabled | true | Root, Control |
data-readonly | true | Root, Control |
Accessibility
Input.Control renders as a native <input> and manages all ARIA attributes automatically.
ARIA Attributes
| Attribute | Value | Notes |
|---|---|---|
aria-invalid | true | When validation fails or error prop is set |
aria-label | Label text | From Root’s label prop |
aria-describedby | Description ID | Only present when Input.Description is mounted |
aria-errormessage | Error ID | Only present when Input.Error is mounted and errors exist |
aria-required | true | From Root’s required prop |
required | true | Native attribute, from Root’s required prop |
disabled | true | Native attribute, from Root’s disabled prop |
readonly | true | Native 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
validate-on="blur lazy" skips validation until the first blur, then validates on every blur afterward — so errors don’t show while the user is still typing their first attempt. eager flips it: after the first error, the field validates on every keystroke.
Set the error and error-messages props on Input.Root. They push the message through the same display channel as client-side rules, so no custom error UI is needed.
Input.Root exposes value as a plain writable Ref. A composable can watch it directly — there’s no @input to bind, since composables never attach DOM listeners.