Text Input
Basic Usage
The TextInput field renders a standard text input. Pass the field name to make() and it will auto-generate a label.
TextInput::make('name');To customize the label:
TextInput::make('name')->label('Full Name');Input Types
You may change the HTML input type using shortcut methods. The preview shows common email, password, and date inputs; each shortcut method automatically adds the appropriate validation rule.
TextInput::make('email')->email();
TextInput::make('password')->password();
TextInput::make('birthday')->date();Password fields do not read their initial value from a bound model. This keeps stored password hashes out of serialized form data on edit screens while the field still renders, submits, and validates normally.
TextInput::make('password')->password();If a rare workflow intentionally stores a plain temporary password and must show it again, opt in deliberately:
TextInput::make('temporary_password')
->password()
->withModelBinding();Available shortcut methods:
TextInput::make('name')->string(); // type="text", validates string
TextInput::make('email')->email(); // type="email", validates string|email
TextInput::make('password')->password(); // type="password", validates string
TextInput::make('age')->number(); // type="number", validates numeric
TextInput::make('age')->integer(); // type="number", validates integer
TextInput::make('phone')->tel(); // type="tel", validates string
TextInput::make('website')->url(); // type="url", validates string|url
TextInput::make('color')->color(); // type="color", validates string|hex_color
TextInput::make('query')->search(); // type="search", validates string
TextInput::make('birthday')->date(); // type="date", validates date
TextInput::make('starts_at')->datetime(); // type="datetime-local", validates date
TextInput::make('alarm')->time(); // type="time", validates date_format:H:iOr set the type manually:
TextInput::make('code')->type('text');type() sets the native input type only and does not add validation. Use it for browser-supported types without a shortcut, then add the matching Laravel rule yourself. The frontend type surface recognizes text, email, password, number, tel, url, search, date, datetime-local, time, month, week, color, and range.
Min, Max, and Step
For numeric inputs, you may set min, max, and step attributes. The min and max methods also add the corresponding validation rules.
TextInput::make('quantity')
->number()
->min(1)
->max(100)
->step(1);
TextInput::make('price')
->number()
->min(0.01)
->max(9999.99)
->step(0.01);Use floats for decimal steps when the value should accept cents, percentages, weights, or other fractional values. step() is a native browser constraint only; it does not add a Laravel rule. Passing null to min(), max(), or step() clears that current option. Generated min and max validation rules follow the latest non-null values.
Length Limits
Use minLength and maxLength to constrain the input length. Both methods add validation rules automatically.
TextInput::make('username')
->minLength(3)
->maxLength(20);Input Mask
Apply an input mask to format the value as the user types. The preview shows phone and card masks; use 9 for digits, a for letters, and * for any character.
TextInput::make('phone_number')->mask('(999) 999-9999');
TextInput::make('card')->mask('9999 9999 9999 9999');Use any mask pattern that fits the value:
TextInput::make('zip')->mask('99999');Masking is frontend formatting, not validation. The unmasked model value is submitted, so add Laravel rules for the required server-side shape. Pass null to mask() to disable it.
There are also shortcut methods for common masks:
TextInput::make('phone_number')->phone(); // tel type + (999) 999-9999 mask
TextInput::make('card_number')->creditCard(); // 9999 9999 9999 9999 maskClearable
Add a clear button that empties the input value:
TextInput::make('search')->clearable();Copyable
Add a copy-to-clipboard button. This is especially useful for readonly values:
TextInput::make('api_key')->readonly()->copyable();Viewable
For password fields, add a toggle to reveal the password:
TextInput::make('password')->password()->viewable();Keyboard Shortcut
Display a keyboard shortcut hint inside the input and register it to focus and select the control:
TextInput::make('search')->kbd('Ctrl+K');Icons
Add a leading or trailing icon to the input by passing an icon name. The frontend resolves that name with your configured icon resolver:
TextInput::make('email')
->email()
->leadingIcon('MailIcon');
TextInput::make('website')
->url()
->trailingIcon('ExternalLinkIcon');See Icons for global resolver setup, per-form resolvers, direct component usage, and Lucide examples.
Currency and Number Formatting
Format the input value as currency or a plain number. The control becomes a text input for editing, while the bound value is parsed to a raw number on blur. An empty formatted control writes an empty string. Formatting does not add a Laravel numeric rule.
TextInput::make('price')->currency();
TextInput::make('population')->numberFormat();
TextInput::make('euro_price')->currency('de-DE', 'EUR');
TextInput::make('yen_price')->currency('ja-JP', 'JPY', 0);Customize fixed-currency formatting when the locale, currency, or decimal precision differs.
For number formatting without a currency symbol:
TextInput::make('population')->numberFormat(); // 1,234 (en-US, 0 decimals)
TextInput::make('percentage')->numberFormat('en-US', 2); // 1,234.56
TextInput::make('weight')->numberFormat('nl-NL', 1); // 1.234,6Variable-currency amounts use a native select addon; see Prepend and Append. Fixed-currency amounts should continue to use currency().
| Parameter | Default | Description |
|---|---|---|
locale | 'en-US' | Locale for number formatting |
currency | 'USD' (currency) / null (numberFormat) | Currency code or null |
decimals | 2 (currency) / 0 (numberFormat) | Number of decimal places |
Enter Key Hint
Set the enterkeyhint attribute to customize the Enter key label on mobile keyboards. The preview shows search and send hints; the same pattern works for the other shortcut methods.
TextInput::make('search')->enterKeySearch();
TextInput::make('message')->enterKeySend();Other shortcut methods:
TextInput::make('first_name')->enterKeyNext();
TextInput::make('last_name')->enterKeyDone();
TextInput::make('url')->enterKeyGo();Or set it manually:
TextInput::make('field')->enterkeyhint('enter');Autocomplete
Set the autocomplete attribute to help browsers autofill the input. The preview shows email and new-password hints.
TextInput::make('email')->autocomplete('email');
TextInput::make('password')->password()->autocomplete('new-password');Use any browser-supported autocomplete token:
TextInput::make('name')->autocomplete('name');Pass null to remove the configured token.
Autofocus
Automatically focus the input when the page loads:
TextInput::make('name')->autofocus();Pass false to turn autofocus off.
Prepend and Append
Add text, icons, native selects, or native submit buttons before or after the input:
TextInput::make('website')->url()->leadingAddon('https://');
TextInput::make('amount')->leadingAddon('USD')->trailingAddon('.00');
TextInput::make('status')
->leadingAddonIcon('ShieldCheckIcon')
->trailingAddonIcon('CheckIcon');
TextInput::make('localized_amount')
->label('Amount currency')
->numberFormat('en-US', 2)
->leadingSelectAddon(
'localized_amount_currency',
[
'usd' => 'USD',
'eur' => 'EUR',
'gbp' => 'GBP',
],
default: 'usd',
label: 'Currency',
);
TextInput::make('coupon_code')
->trailingButtonAddon(
label: 'Apply',
name: 'text_input_action',
value: 'apply_coupon',
);For addon icons that should render as their own segment outside the input, use the leadingAddonIcon() / trailingAddonIcon() helpers. These use the same icon resolver as inline input icons.
Native select addons submit a sibling form value under their configured name. Use leadingSelectAddon() or trailingSelectAddon() for ordinary select segments:
TextInput::make('amount')
->leadingSelectAddon(
'amount_currency',
[
'usd' => 'USD',
'eur' => 'EUR',
'gbp' => 'GBP',
],
default: 'usd',
rules: ['required'],
label: 'Currency',
);
// Submitted data contains both keys:
// ['amount' => 99, 'amount_currency' => 'usd']Select addons support options, a default value, and validation rules. Omitting the default uses the first resolved option value; an explicit null remains empty. Resolved options always add an allowed-value rule, while additional rules default to an empty array.
Select addon options may also come from Laravel models or queries:
TextInput::make('amount')
->leadingSelectAddon(
'amount_currency_id',
Currency::query()->where('active', true)->orderBy('code'),
optionLabel: 'code',
optionValue: 'id',
);Use closures when labels or groups need custom PHP logic:
TextInput::make('estimate')
->leadingSelectAddon(
'assignee_id',
User::query()->with('role')->orderBy('role_id')->orderBy('name'),
optionLabel: fn (User $user) => "{$user->id} {$user->name}",
optionValue: 'id',
grouped: 'role_id',
groupLabel: fn (User $user) => $user->role->name,
);Use array maps when a resolved value needs a simple lookup:
TextInput::make('estimate')
->leadingSelectAddon(
'assignee_id',
User::query()->orderBy('role_id')->orderBy('name'),
optionLabel: 'name',
optionValue: 'id',
grouped: 'role_id',
groupLabel: [
1 => 'Administrators',
2 => 'Editors',
],
);Select addons are native selects, so grouped options render real <optgroup> elements:
TextInput::make('estimate')
->leadingSelectAddon(
'assignee_id',
User::query()->orderBy('role')->orderBy('name'),
optionLabel: 'name',
optionValue: 'id',
grouped: 'role',
);For variable-currency amounts, currencySelectAddon() is a shortcut for the same select addon behavior. It also calls numberFormat() for the amount:
TextInput::make('amount')
->currencySelectAddon('amount_currency', [
'usd' => 'USD',
'eur' => 'EUR',
'gbp' => 'GBP',
], default: 'usd');
// Submitted data contains both keys:
// ['amount' => 99, 'amount_currency' => 'usd']currencySelectAddon() accepts the same option sources and mapping arguments as native select addons, plus formatting options:
TextInput::make('amount')->currencySelectAddon(
name: 'amount_currency',
options: Currency::query()->orderBy('code'),
default: 'usd',
locale: 'en-US',
decimals: 2,
rules: ['required'],
label: 'Currency',
position: 'leading',
optionLabel: 'code',
optionValue: 'id',
grouped: 'region',
groupLabel: 'region_name',
);Unlike ordinary select addons, currencySelectAddon() defaults rules to ['required']. Passing default: null preserves an explicit empty default. Pass label to override the validation attribute and native select label. Use position: 'trailing' when the currency selector should render after the amount input.
Button addons render native submit buttons inside the joined input control. The helper arguments are the visible label, the native submitter name, and the native submitter value:
TextInput::make('coupon_code')
->trailingButtonAddon(
label: 'Apply',
name: 'text_input_action',
value: 'apply_coupon',
);
// Clicking Apply submits:
// ['coupon_code' => 'SAVE20', 'text_input_action' => 'apply_coupon']The rendered button uses type="submit". The generated <Form> adds the clicked button's name and value to the request, just like multiple submit actions. This is submitter behavior, not a custom frontend callback. Button addon values are not part of the form's initial data.
Using the Component Directly
You may render TextInput outside the generated <Form> by passing serialized form.getField(...) props or local state.
<script setup lang="ts">
import { ref } from 'vue'
import { TextInput } from '@inertiaui/form-vue/components'
import { Check, DollarSign, Search } from '@lucide/vue'
import type { CurrencyFormat, IconResolverFn, InputAddonSegment } from '@inertiaui/form-vue'
const query = ref('winter catalog')
const amount = ref(99)
const phone = ref('')
const password = ref('')
const apiKey = ref('sk_live_123')
const icons = { CheckIcon: Check, DollarSignIcon: DollarSign, SearchIcon: Search }
const iconResolver: IconResolverFn = (icon) => icons[icon as keyof typeof icons] ?? null
const currency: CurrencyFormat = { locale: 'en-US', currency: null, decimals: 2 }
const amountAddons: InputAddonSegment[] = [
{
position: 'leading',
type: 'icon',
icon: 'DollarSignIcon',
},
{
position: 'leading',
type: 'select',
name: 'currency',
label: 'Currency',
default: 'usd',
required: true,
options: [
{ value: 'usd', label: 'USD' },
{ value: 'eur', label: 'EUR' },
],
},
{
position: 'trailing',
type: 'text',
content: 'net',
},
{
position: 'trailing',
type: 'button',
content: 'Apply',
name: 'text_input_action',
value: 'apply_amount',
},
]
const fieldParts = {
input: 'font-mono',
addon: 'min-w-16 justify-center',
action: 'text-blue-600',
}
const trackBlur = () => {}
</script>
<template>
<div class="grid gap-4">
<TextInput
v-model="query"
id="product-search"
name="query"
label="Search"
help="Search by product name or SKU."
placeholder="Search products..."
input-type="search"
autocomplete="off"
autofocus
clearable
kbd="Ctrl+K"
leading-icon="SearchIcon"
trailing-icon="CheckIcon"
:icon-resolver="iconResolver"
badge="Live"
badge-class="bg-zinc-100 text-zinc-700"
label-trailing="Optional"
label-trailing-class="text-zinc-500"
tooltip="Filters the product list."
help-position="below"
layout="stacked"
control-position="end"
label-class="font-medium"
help-class="text-zinc-500"
error-class="text-red-600"
wrapper-class="max-w-xl"
control-class="bg-white"
class="font-medium"
:part-classes="fieldParts"
data-testid="product-search"
aria-label="Product search"
@blur="trackBlur"
/>
<TextInput
v-model="amount"
name="amount"
label="Amount"
input-type="number"
:min="0"
:max="9999"
:step="0.01"
pattern="[0-9]+([.,][0-9]{2})?"
:currency="currency"
:addons="amountAddons"
enterkeyhint="done"
required
precognitive
/>
<TextInput
v-model="phone"
name="phone"
label="Phone"
input-type="tel"
mask="(999) 999-9999"
:min-length="14"
:max-length="14"
autocomplete="tel"
enterkeyhint="next"
/>
<TextInput
v-model="password"
name="password"
label="Password"
input-type="password"
placeholder="Enter password"
viewable
required
invalid
error="Password is required."
/>
<TextInput
v-model="apiKey"
name="api_key"
label="API key"
readonly
disabled
copyable
label-sr-only
/>
</div>
</template>import { useState } from 'react'
import { TextInput } from '@inertiaui/form-react/components'
import { CheckIcon, DollarSignIcon, SearchIcon } from 'lucide-react'
import type { CurrencyFormat, IconResolverFn, InputAddonSegment } from '@inertiaui/form-react'
const icons = { CheckIcon, DollarSignIcon, SearchIcon }
const iconResolver: IconResolverFn = (icon) => icons[icon as keyof typeof icons] ?? null
const currency: CurrencyFormat = { locale: 'en-US', currency: null, decimals: 2 }
const amountAddons: InputAddonSegment[] = [
{
position: 'leading',
type: 'icon',
icon: 'DollarSignIcon',
},
{
position: 'leading',
type: 'select',
name: 'currency',
label: 'Currency',
default: 'usd',
required: true,
options: [
{ value: 'usd', label: 'USD' },
{ value: 'eur', label: 'EUR' },
],
},
{
position: 'trailing',
type: 'text',
content: 'net',
},
{
position: 'trailing',
type: 'button',
content: 'Apply',
name: 'text_input_action',
value: 'apply_amount',
},
]
const fieldParts = {
input: 'font-mono',
addon: 'min-w-16 justify-center',
action: 'text-blue-600',
}
export function DirectTextInputs() {
const [query, setQuery] = useState('winter catalog')
const [amount, setAmount] = useState(99)
const [phone, setPhone] = useState('')
const [password, setPassword] = useState('')
const [apiKey, setApiKey] = useState('sk_live_123')
const trackBlur = () => {}
return (
<div className="grid gap-4">
<TextInput
value={query}
onValueChange={setQuery}
id="product-search"
name="query"
label="Search"
help="Search by product name or SKU."
placeholder="Search products..."
inputType="search"
autocomplete="off"
autofocus
clearable
kbd="Ctrl+K"
leadingIcon="SearchIcon"
trailingIcon="CheckIcon"
iconResolver={iconResolver}
badge="Live"
badgeClass="bg-zinc-100 text-zinc-700"
labelTrailing="Optional"
labelTrailingClass="text-zinc-500"
tooltip="Filters the product list."
helpPosition="below"
layout="stacked"
controlPosition="end"
labelClass="font-medium"
helpClass="text-zinc-500"
errorClass="text-red-600"
wrapperClass="max-w-xl"
controlClass="bg-white"
class="font-medium"
partClasses={fieldParts}
data-testid="product-search"
aria-label="Product search"
onBlur={trackBlur}
/>
<TextInput
value={amount}
onValueChange={setAmount}
name="amount"
label="Amount"
inputType="number"
min={0}
max={9999}
step={0.01}
pattern="[0-9]+([.,][0-9]{2})?"
currency={currency}
addons={amountAddons}
enterkeyhint="done"
required
precognitive
/>
<TextInput
value={phone}
onValueChange={setPhone}
name="phone"
label="Phone"
inputType="tel"
mask="(999) 999-9999"
minLength={14}
maxLength={14}
autocomplete="tel"
enterkeyhint="next"
/>
<TextInput
value={password}
onValueChange={setPassword}
name="password"
label="Password"
inputType="password"
placeholder="Enter password"
viewable
required
invalid
error="Password is required."
/>
<TextInput
value={apiKey}
onValueChange={setApiKey}
name="api_key"
label="API key"
readonly
disabled
copyable
labelSrOnly
/>
</div>
)
}Vue uses v-model. React uses controlled value plus onValueChange. Native attributes and listeners target the underlying <input>; see Native Attributes And Events.