Skip to content

TextField

A labeled text input with support for descriptions and validation errors.

We'll never share your email.

import { TextField } from '@ontoui/react';
<TextField.Root>
<TextField.Label>Email</TextField.Label>
<TextField.Control type="email" placeholder="you@example.com" />
<TextField.Description>We'll never share your email.</TextField.Description>
</TextField.Root>;

Anatomy

PartDescription
TextField.RootContainer that groups all parts and manages field state
TextField.LabelAccessible label associated with the control
TextField.ControlThe underlying <input> element
TextField.AnimatedPlaceholderTypes example queries through the control’s placeholder
TextField.DescriptionOptional helper text displayed below the control
TextField.ErrorError message shown when the field is invalid

Examples

Invalid

Set the invalid prop on TextField.Root to mark the field as invalid and reveal the error message.

<TextField.Root invalid>
<TextField.Label>Username</TextField.Label>
<TextField.Control placeholder="Enter username" />
<TextField.Error>Username is required.</TextField.Error>
</TextField.Root>

Animated placeholder

A search box says nothing about what it will accept. TextField.AnimatedPlaceholder cycles example queries through the placeholder with a typing effect, so the field shows what it can be asked for instead of waiting to be guessed at.

<TextField.Root>
<TextField.Label>Search reports</TextField.Label>
<TextField.AnimatedPlaceholder
prefix="Try: "
examples={['March expenses', 'unpaid invoices', 'headcount by team']}
/>
<TextField.Control placeholder="Search reports" />
</TextField.Root>

The part renders nothing of its own — it drives the placeholder of TextField.Control, so the text is a real placeholder and disappears behind a value like any other. Order inside TextField.Root does not matter.

prefix stays in place while the examples are retyped behind it. typeSpeed, deleteSpeed and holdDuration set the pace, and caret is the character standing in for a cursor — pass caret={false} to drop it.

Keep placeholder on TextField.Control as well. It is what shows on the server, before hydration, and with scripting off; the animation upgrades it once it starts.

The animation stops on a whole example — never mid-word — while the field is focused or holds a value, and under prefers-reduced-motion: reduce it never starts, leaving the first example as a still placeholder. Because the text is a placeholder it can end up as the field’s accessible name, so always render a TextField.Label (or pass aria-label to the control) to give the field a name that does not move.

Disabled

Set disabled on TextField.Root to disable the entire field group.

<TextField.Root disabled>
<TextField.Label>Read-only field</TextField.Label>
<TextField.Control placeholder="Disabled input" />
</TextField.Root>

API Reference

TextField.Root

Prop Type Default
children? ReactNode
className? string
style? CSSProperties
name? string
disabled? boolean
invalid? boolean
ref? Ref<HTMLDivElement>

TextField.Label

Prop Type Default
children? ReactNode
className? string
ref? Ref<HTMLDivElement>

TextField.Control

Prop Type Default
id? string
className? string
placeholder? string
type? HTMLInputTypeAttribute
defaultValue? string
value? string
disabled? boolean
required? boolean
readOnly? boolean
onChange? ChangeEventHandler<HTMLInputElement, Element>
onFocus? FocusEventHandler<HTMLInputElement>
onBlur? FocusEventHandler<HTMLInputElement>
ref? Ref<HTMLDivElement>

TextField.AnimatedPlaceholder

Prop Type Default
examples string[]
prefix? string ''
typeSpeed? number 55
deleteSpeed? number 25
holdDuration? number 2000
caret? string | false '|'

TextField.Description

Prop Type Default
children? ReactNode
className? string
ref? Ref<HTMLDivElement>

TextField.Error

Prop Type Default
children? ReactNode
className? string
ref? Ref<HTMLDivElement>