Text input
Introduction
Section titled “Introduction”TextInput is the workhorse field for single-line strings — titles, slugs, emails, and numeric values. Orbit wraps native inputs with consistent label, hint, helper, prefix/suffix affixes, and validation rules that dehydrate with the form. Variants below show type modifiers, affix chrome, and interaction states you can combine on one field.
Each variation below includes a short explanation, the fluent API to paste into your schema, and a screenshot of the rendered control.
Basic text input
Section titled “Basic text input”Label, placeholder, and helper text — the default starting point.
TextInput.make('title') .label('Title') .placeholder('Enter a title…') .helper_text('Shown on the public page.')

Sets input type to email and adds an email validation rule.
TextInput.make('email') .email() .label('Email') .placeholder('you@acme.test')

Password (revealable)
Section titled “Password (revealable)”Password type with an optional reveal toggle for accessibility.
TextInput.make('password') .password() .revealable() .label('Password')

URL input type with built-in url validation.
TextInput.make('website') .url() .label('Website') .placeholder('https://')

Telephone
Section titled “Telephone”Tel input type for phone numbers.
TextInput.make('phone') .tel() .label('Phone')

Numeric
Section titled “Numeric”Number input with optional min/max bounds.
TextInput.make('quantity') .numeric() .label('Quantity') .min_value(0) .max_value(99)

Prefix text
Section titled “Prefix text”Static text before the control — common for currency symbols.
TextInput.make('price') .label('Price') .prefix('$') .numeric()

Suffix text
Section titled “Suffix text”Static text after the control — units, domains, etc.
TextInput.make('weight') .label('Weight') .suffix('kg') .numeric()

Prefix icon
Section titled “Prefix icon”Heroicon rendered inside the affix rail.
TextInput.make('search') .label('Search') .prefix_icon('heroicon-o-magnifying-glass')![]()
![]()
Suffix icon
Section titled “Suffix icon”Trailing icon affix for links, locks, or status.
TextInput.make('slug') .label('Slug') .suffix_icon('heroicon-o-link')![]()
![]()
Required
Section titled “Required”Shows the required asterisk and injects a required rule.
TextInput.make('name') .label('Name') .required()

Disabled
Section titled “Disabled”Non-interactive state for read-only contexts.
TextInput.make('locked') .label('Locked field') .disabled()

Readonly
Section titled “Readonly”Value visible but not editable — good for generated IDs.
TextInput.make('id') .label('Record ID') .readonly()

Copyable
Section titled “Copyable”Adds a one-click copy button beside the input.
TextInput.make('token') .label('API token') .copyable() .readonly()

Input mask
Section titled “Input mask”Client-side mask pattern for structured values like card numbers.
TextInput.make('card') .label('Card number') .mask('9999 9999 9999 9999')

Datalist suggestions
Section titled “Datalist suggestions”Native datalist autocomplete from a string list.
TextInput.make('city') .label('City') .datalist(['Nairobi', 'London', 'Berlin'])

With hint
Section titled “With hint”Inline hint text and optional hint icon above the control.
TextInput.make('slug') .label('Slug') .hint('Used in the public URL.') .hint_icon('heroicon-o-information-circle')

.trim() marks the field so dehydrate strips leading and trailing whitespace before any custom .dehydrate_state_using() callback runs. It does not change the live input type or strip characters mid-typing — only the value that leaves the form. Use it on titles, slugs, and free-text fields where accidental spaces would break uniqueness or URLs.
TextInput.make('title') .label('Title') .trim()

Strip characters
Section titled “Strip characters”.strip_characters() removes every listed character from the dehydrated string. Pass a string (each char is stripped) or a sequence of strings that are joined into the removal set. Strip runs before trim inside apply_dehydrate_transforms, so you can clear dashes/spaces and then trim leftovers. Ideal for SKUs, IBAN paste clean-up, and phone numbers that should store digits only.
TextInput.make('sku') .label('SKU') .strip_characters(['-', ' ']) .trim()

Exact length
Section titled “Exact length”.length(n) records an exact character length and appends a size:n validation rule. Pair it with .mask() or OTP-style inputs when the UI already constrains width. Prefer .min_length() / .max_length() (mapped to enforced min: / max: rules) when you need a range — Form.validate currently enforces min/max/between/regex, while size: is registered by the fluent helper so hosts and future rule coverage can pick it up.
TextInput.make('pin') .label('PIN') .length(4) .numeric()

Telephone regex
Section titled “Telephone regex”.tel() only sets type="tel" for the mobile keyboard. .tel_regex(pattern) stores the pattern and adds a regex:… rule that Form.validate enforces. Combine both when you want native tel chrome plus a project-specific E.164 or national format.
TextInput.make('phone') .label('Phone') .tel() .tel_regex(r'^\+?[0-9\s\-]{7,20}$')

Autocapitalize
Section titled “Autocapitalize”.autocapitalize(value) emits the HTML autocapitalize attribute on the input (for example sentences, words, characters, or none). Browsers on mobile honor it for soft-keyboard behavior; desktop may ignore it. It does not rewrite state on dehydrate — pair with .trim() if you also want whitespace normalized.
TextInput.make('full_name') .label('Full name') .autocapitalize('words')

Mark as required
Section titled “Mark as required”.mark_as_required() controls the required asterisk independently of the required validation rule. Use it when the field is visually mandatory (asterisk) but validation is conditional via .required(callable) or sibling rules, or to hide the asterisk while still validating with .required(). shows_required_asterisk() prefers _mark_as_required when set; otherwise it mirrors is_required().
TextInput.make('nickname') .label('Nickname') .mark_as_required() .helper_text('Shown as required, but only validated when publishing.')

Prefix and suffix icon colors
Section titled “Prefix and suffix icon colors”.prefix_icon_color() / .suffix_icon_color() add an or-color-{color} class on the affix icon span when a matching .prefix_icon() / .suffix_icon() is present. Colors follow Orbit’s semantic tokens (primary, success, warning, danger, gray, …). Text affixes (.prefix() / .suffix()) are unchanged — these helpers only tint icon chrome.
TextInput.make('amount') .label('Amount') .numeric() .prefix_icon('heroicon-o-currency-dollar') .prefix_icon_color('success') .suffix_icon('heroicon-o-check-circle') .suffix_icon_color('primary')![]()
![]()
Content slots
Section titled “Content slots”Every Field can inject HTML around the label, control, and error region via nine slots: .above_label(), .below_label(), .before_label(), .after_label(), .above_content(), .below_content(), .before_content(), .after_content(), and .below_error(). Slot bodies accept strings or callables and are not HTML-escaped (unlike labels), so you can drop muted captions, badges, or small links. Prefer slots over wrapping the field in a custom layout when you only need adjacent chrome.
TextInput.make('bio') .label('Bio') .above_label('<span class="or-badge">Public</span>') .below_label('Shown on the author page.') .above_content('Keep it under two sentences.') .below_error('Fix validation before saving.')

Closures work on .label(), .helper_text(), .placeholder(), content slots, .visible(), .disabled(), and .required() where applicable — see Form closures.