Lattice components

Text Field

Single-line text input primitive that owns the value, separates live changes from commit, exposes disabled/readOnly/invalid state, and wires label, description, and message parts together.

@lattice-ui/text-field Stable direction import TextField depends on core , motion

Text Field is the primitive for any single-line input: a username box, a search bar, a price entry, a private-server name. It wraps a Roblox TextBox, owns the current value, and distinguishes a live change (every keystroke) from a commit (when editing ends), so your component decides when to validate or persist.

Reach for Text Field when an input needs controlled or uncontrolled value state, a clear split between change and commit, and shared disabled / readOnly / required / invalid state that flows to a label, helper description, and validation message.

Import

import { TextField } from "@lattice-ui/text-field";

Anatomy

Compose Root around an Input, plus any of the optional text parts. Only Root and Input are required; Label, Description, and Message read shared state from context.

TextField anatomy
<TextField.Root>
<TextField.Label />
<TextField.Input />
<TextField.Description />
<TextField.Message />
</TextField.Root>
PartRequiredResponsibility
TextField.RootyesOwns the value, the commit callback, and shared disabled/readOnly/required/invalid state.
TextField.InputyesThe TextBox that renders the value and reports changes, focus, and commit.
TextField.LabelnoA textbutton that focuses the input when activated.
TextField.DescriptionnoA static textlabel for helper text.
TextField.MessagenoA textlabel for validation text that recolors when invalid is set.

Example

A controlled field with commit-time validation that flips invalid and shows a message.

UsernameField.tsx
import { useState } from "@rbxts/react";
import { TextField } from "@lattice-ui/text-field";
export function UsernameField() {
const [name, setName] = useState("");
const [error, setError] = useState(false);
return (
<TextField.Root
value={name}
onValueChange={setName}
onValueCommit={(committed) => setError(committed.size() < 3)}
invalid={error}
required
name="username"
>
<frame BackgroundTransparency={1} Size={UDim2.fromOffset(240, 96)}>
<uilistlayout Padding={new UDim(0, 4)} />
<TextField.Label>
<textbutton BackgroundTransparency={1} Text="Username" Size={UDim2.fromOffset(240, 22)} />
</TextField.Label>
<TextField.Input>
<textbox PlaceholderText="Enter a name" Size={UDim2.fromOffset(240, 36)} />
</TextField.Input>
{error ? (
<TextField.Message>
<textlabel BackgroundTransparency={1} Text="At least 3 characters." Size={UDim2.fromOffset(240, 20)} />
</TextField.Message>
) : (
<TextField.Description>
<textlabel BackgroundTransparency={1} Text="Visible to other players." Size={UDim2.fromOffset(240, 20)} />
</TextField.Description>
)}
</frame>
</TextField.Root>
);
}

How it behaves

Value state

TextField.Root is controllable. Pass value and onValueChange to control it, or defaultValue to run uncontrolled; when neither is set the value starts empty. The value is mirrored onto the TextBox every render, so the displayed text always reflects the owned state rather than whatever Roblox last typed.

Change and commit

A keystroke fires the TextBox text change, which calls onValueChange with the new text. A commit happens on FocusLost — when the player clicks away, presses Enter, or otherwise ends editing — which calls onValueCommit with the final text. Use onValueChange for live updates (counters, filtering) and onValueCommit for validation or persistence you only want once editing settles.

Disabled, readOnly, and validation

disabled and readOnly both stop edits from updating the value: while either is set, Root.setValue ignores incoming text and the Input rewrites the TextBox back to the owned value. They differ in interaction — disabled also clears Active/Selectable and dims the input text, while readOnly keeps the field selectable but not editable. A disabled field additionally suppresses the commit callback on focus loss. Input can also set disabled/readOnly locally, which combine with Root’s state via OR.

required and invalid are shared flags that carry no enforcement on their own — required is exposed for your own validation, and invalid is a visual/semantic marker. When invalid is set, TextField.Message recolors its text to the error color. The name prop is passed through context for form identification.

Label and focus

TextField.Label renders a textbutton; activating it calls CaptureFocus() on the input, so clicking the label focuses the field. When the field is disabled the label drops its Active/Selectable state and does nothing on activation. Description and Message are non-interactive labels that read shared state for their text color.

API reference

TextField.Root

Prop Type Description
value string Controlled value. Pair with onValueChange.
defaultValue string Initial value for uncontrolled usage. Defaults to an empty string.
onValueChange (value: string) => void Called on every text change while the field is editable.
onValueCommit (value: string) => void Called with the final text when editing ends (focus lost). Suppressed when disabled.
disabled boolean Blocks edits, clears Active/Selectable, dims the input, and suppresses commit. Defaults to false.
readOnly boolean Blocks edits while keeping the field selectable. Defaults to false.
required boolean Shared flag exposed for your own validation; not enforced. Defaults to false.
invalid boolean Marks the field invalid and recolors the Message part. Defaults to false.
name string Identifier passed through context for form usage.
children React.ReactNode The field parts.

TextField.Input

Prop Type Description
asChild boolean Merge input behavior onto the single child element instead of rendering the default textbox.
disabled boolean Local disabled state, combined with Root's via OR. Defaults to false.
readOnly boolean Local readOnly state, combined with Root's via OR. Defaults to false.
children React.ReactElement The textbox element to render. Required when asChild is set.

TextField.Label

Prop Type Description
asChild boolean Merge label behavior onto the single child element instead of rendering the default textbutton.
children React.ReactElement The element to render. Required when asChild is set.

TextField.Description

Prop Type Description
asChild boolean Merge the description onto the single child element instead of rendering the default textlabel.
children React.ReactElement The element to render. Required when asChild is set.

TextField.Message

Prop Type Description
asChild boolean Merge the message onto the single child element instead of rendering the default textlabel.
children React.ReactElement The element to render. Required when asChild is set.