Input
A single-line text field. It supports v-model, an optional leading slot for an icon or prefix, and integrates with Field for labels, descriptions, and error states.
Binding is optional by design: with v-model it's controlled; without it, the field runs uncontrolled. Like the other controls, it's label-less, pair it with a Label, or wrap it in a Field (see Field) for the full labelled pattern.
Usage
Bind the value with v-model.
<script setup lang="ts">
import { ref } from "vue";
import { Input } from "@dlbcodes/ui";
const value = ref("");
</script>
<template>
<Input v-model="value" placeholder="Type something…" class="max-w-sm" />
</template>
With a leading icon
The default slot renders before the input, use it for a search icon, a currency symbol, or any prefix. Clicking the slot focuses the input, so an icon never becomes a dead zone.
<script setup lang="ts">
import { ref } from "vue";
import { Input } from "@dlbcodes/ui";
import { PhMagnifyingGlass } from "@phosphor-icons/vue";
const query = ref("");
</script>
<template>
<Input v-model="query" placeholder="Search…" class="max-w-sm">
<PhMagnifyingGlass class="size-4" />
</Input>
</template>
Sizes
Four sizes via the size prop: sm, base (default), lg, and xl.
<script setup lang="ts">
import { ref } from "vue";
import { Input } from "@dlbcodes/ui";
const a = ref("");
const b = ref("");
</script>
<template>
<div class="flex flex-col gap-3 max-w-sm">
<Input v-model="a" size="base" placeholder="Base" />
<Input v-model="b" size="sm" placeholder="Small" />
</div>
</template>
| Size | Height |
|---|---|
sm | 32px |
base | 40px |
lg | 48px |
xl | 56px |
These are the same heights as Button at the same size, so an input and a button line up when placed in a row, such as a search field with a submit button. Corners follow the theme's --radius.
Variants
primary (the default) has a tinted fill. contrast has a plain fill, for inputs that sit on a card, modal or popover, where the tinted fill would blend in. Both use the same border.
States
disabled makes the field non-interactive and dims it; invalid applies error styling in the theme's --destructive color. Inside a Field, both are driven by the field's state automatically.
<script setup lang="ts">
import { ref } from "vue";
import { Input } from "@dlbcodes/ui";
const a = ref("Can't edit this");
const b = ref("wrong value");
</script>
<template>
<div class="flex flex-col gap-3 max-w-sm">
<Input v-model="a" disabled />
<Input v-model="b" invalid />
</div>
</template>
Mobile keyboards
On touch devices, the right keyboard matters. Native input attributes pass straight through to the underlying <input>, so set inputmode, enterkeyhint, and autocomplete to control the on-screen keyboard and autofill. Each field below requests the keyboard suited to its content, open this page on a phone to see them differ.
<script setup lang="ts">
import { ref } from "vue";
import { Field, FieldLabel, FieldContent, Input } from "@dlbcodes/ui";
const email = ref("");
const phone = ref("");
const code = ref("");
const amount = ref("");
</script>
<template>
<div class="flex w-full max-w-xs flex-col gap-4">
<Field>
<FieldLabel>Email</FieldLabel>
<FieldContent>
<Input
v-model="email"
type="email"
inputmode="email"
autocomplete="email"
placeholder="you@example.com"
/>
</FieldContent>
</Field>
<Field>
<FieldLabel>Phone</FieldLabel>
<FieldContent>
<Input
v-model="phone"
type="tel"
inputmode="tel"
autocomplete="tel"
placeholder="(555) 000-0000"
/>
</FieldContent>
</Field>
<Field>
<FieldLabel>Verification code</FieldLabel>
<FieldContent>
<Input
v-model="code"
type="text"
inputmode="numeric"
autocomplete="one-time-code"
maxlength="6"
placeholder="123456"
/>
</FieldContent>
</Field>
<Field>
<FieldLabel>Amount</FieldLabel>
<FieldContent>
<Input v-model="amount" inputmode="decimal" placeholder="0.00">
<span class="text-sm">€</span>
</Input>
</FieldContent>
</Field>
</div>
</template>
Prefer type="text" with inputmode="numeric" over type="number" for things like PINs, codes, and quantities. type="number" allows characters like e and strips leading zeros, while inputmode just picks the keyboard and leaves the value alone.
The input text is 16px on small screens (and 14px from the md breakpoint up for sm and base). That's deliberate: iOS Safari zooms the page when it focuses a field whose text is smaller than 16px.
Native attributes
Any attribute not listed in the props below is forwarded to the underlying <input>, not the wrapper. So name, maxlength, pattern, readonly, autocomplete, autocapitalize, and the keyboard attributes above all work as expected:
<Input name="email" autocomplete="email" maxlength="120" v-model="email" />Inside a Field
The common case: wrap the input in a Field to get a label, description, error message, and all the accessibility wiring without managing ids by hand. See Field for the full pattern.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | number | null | none | The field's value. Use with v-model. Optional, runs uncontrolled if unbound. |
variant | "primary" | "contrast" | "primary" | Visual style. primary has a tinted fill; contrast a plain fill for use on cards, modals and popovers. |
size | "sm" | "base" | "lg" | "xl" | "base" | Field size. Heights are 32, 40, 48 and 56px, matching Button. |
type | string | "text" | Native input type (text, email, password, etc.). |
placeholder | string | none | Placeholder text. |
disabled | boolean | none | Disables the field. Inherited from a surrounding Field. |
required | boolean | none | Marks required. Inherited from a Field. |
invalid | boolean | none | Applies error styling. Inherited from a Field's error state. |
class | string | none | Classes merged onto the wrapper. |
Plus any native <input> attribute (name, inputmode, autocomplete, maxlength, pattern, readonly, ...), which forwards to the input element.
Accessibility
- Set
typeandinputmodeappropriately (email,tel,numeric, ...) so browsers and assistive tech provide the right keyboard and autofill behavior. - Give the input a name: pair it with a
Label, or wrap it in aField. A bare input with only a placeholder is not accessibly labelled. - Inside a
Field,aria-invalid,aria-required, andaria-describedbyare wired automatically; standalone, setinvalid/requiredas props. - Keyboard focus is shown with a strong border and ring in the theme's
--foregroundcolor, which keeps enough contrast against the field.