A text field with its label, hint and error already wired to it.
Import it from rei-kit:
import { BaseInput } from 'rei-kit'
Open BaseInput in the live documentation, where every prop below is a control you can change, in four materials and ten palettes.
<script setup lang="ts">
import { ref } from 'vue'
import { BaseInput } from 'rei-kit'
const email = ref('')
const weight = ref(0)
</script>
<template>
<BaseInput v-model="email" label="Email" type="email" hint="We never share it." />
<!-- A unit attached to the field. It is `aria-hidden` because the label
already says what the number is; a mark that is the only place the
unit appears belongs in the label instead. -->
<BaseInput v-model="weight" label="Weight in kilograms" type="number">
<template #suffix><span aria-hidden="true">kg</span></template>
</BaseInput>
</template>
| Prop | Type | Required or default | What it is for |
|---|---|---|---|
modelValue | T | undefined | The value, with `v-model`. Its type is whatever you bind. |
label | string | yes | |
error | string | '' | |
hint | string | '' | |
labelHidden | boolean | false | Hides the label visually but keeps it for assistive tech. For fields whose surrounding row already names them — dropping the label entirely would leave the input with no accessible name at all. |
type | 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url' | 'date' | 'time' | 'datetime-local' | 'text' | Every type a text field can be, because the ones missing were the ones apps needed: `date` and `search` were hand-written three times each and `url` twice, in files that already imported this component. |
size | 'sm' | 'md' | 'md' | `sm` for a field inside a row rather than in a form of its own. |
variant | 'default' | 'unstyled' | 'default' | `unstyled` keeps the wiring and drops the surface. The label, the generated id, `aria-describedby` and the error are what a field is; the border and the height are what it looks like. A search box inside a bordered row, a url field in an editor popover — those places were hand-writing the whole thing to avoid the appearance, and losing the wiring with it. The same reasoning as `BaseButton`'s `unstyled`, and the same test: a primitive is finished when the app can take its behaviour without its paint. |
fieldId | string | undefined | Ties the generated id to a name you choose, so `ErrorSummary` can link to this field. Its links point at nothing without it, and the kit's own `FormField` has taken one since 2.22.0 while the fields built on it did not — which left building a form out of these and summarising its errors impossible without hand-writing the control. |
controlClass | string | undefined | Classes for the control itself, rather than for the field around it. `class` lands on the field, because that is where Vue puts a component's class and because a layout class means "space this field". That leaves `variant="unstyled"` with no way to paint the control it just stripped — the point of stripping it — which is why an app with a large amount field wrote the whole thing by hand instead. It appends, so with the default variant a conflict is settled by Tailwind's own order rather than by writing it here; reach for it with `unstyled`, where there is nothing to settle. Under `unstyled` it also takes over the 16px floor that stops iOS zooming on focus: `text-base` cannot be beaten by a larger utility, so the kit stops asserting it rather than overrule a caller who asked to paint the control. Keep the control at 16px or more. |
rei-kit is an accessible Vue 3 and Tailwind CSS 4 component library: 106 components, themed by role rather than by colour on three independent axes — ten WCAG-checked palettes, four materials and light or dark, each switched with one attribute. Typed, tested, SSR-safe, no runtime dependencies, MIT.