Open UI

TextField

A text field that allows a user to enter a plain text value with a keyboard.

import { TextField } from "@opengovsg/oui"export const Example = () => {  return (    <TextField      label="First name"      inputProps={{        placeholder: "John",      }}    />  )}

Usage

Use TextField for single-line text input. Use TextAreaField for multi-line. Use SearchField for search queries. Use NumberField for numeric input.

import { TextField } from "@opengovsg/oui"
<TextField label="Full name" />

Alternatively, install the component as local source via the shadcn CLI:

npx shadcn@latest add https://oui.open.gov.sg/r/text-field.json
pnpm dlx shadcn@latest add https://oui.open.gov.sg/r/text-field.json
npx shadcn@latest add https://oui.open.gov.sg/r/text-field.json
bunx --bun shadcn@latest add https://oui.open.gov.sg/r/text-field.json

The TextField component provides a single-line text input with built-in label, description, and error message support. It is built on React Aria's TextField.

If the component does not have a visible label (by passing a label prop), an aria-label or aria-labelledby prop must be passed instead to identify it to assistive technology.

Examples

With Label and Description

Provide clear instructions to users with labels and descriptions.

import { TextField } from "@opengovsg/oui"export const Example = () => {  return (    <TextField      label="Full name"      description="As shown on your NRIC or FIN."      inputProps={{        placeholder: "Enter your full name",      }}    />  )}

With Placeholder

Use the inputProps.placeholder to show placeholder text.

import { TextField } from "@opengovsg/oui"export const Example = () => {  return (    <TextField      label="Email"      inputProps={{        placeholder: "you@example.com",      }}    />  )}

With Error Message

Combine isInvalid and errorMessage props to show validation errors.

import { TextField } from "@opengovsg/oui"export const Example = () => {  return (    <TextField      label="Email"      isInvalid      defaultValue="invalid-email"      errorMessage="Please enter a valid email address."    />  )}

Sizes

Use the size prop to change the size of the text field. Defaults to "md".

import { TextField } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex flex-col gap-6">      <TextField        size="xs"        label="Extra Small"        inputProps={{ placeholder: "Extra small size" }}      />      <TextField        size="sm"        label="Small"        inputProps={{ placeholder: "Small size" }}      />      <TextField        size="md"        label="Medium (default)"        inputProps={{ placeholder: "Medium size" }}      />    </div>  )}

Controlled

Use the value and onChange props to control the text field value programmatically.

import { useState } from "react"import { TextField } from "@opengovsg/oui"export const Example = () => {  const [value, setValue] = useState("")  return (    <div className="flex flex-col gap-2">      <TextField        label="Your name"        value={value}        onChange={setValue}        inputProps={{ placeholder: "Type here" }}      />      <p className="text-base-content-medium text-sm">        Current value: {value || "(empty)"}      </p>    </div>  )}

Disabled

Use the isDisabled prop to disable the text field.

import { TextField } from "@opengovsg/oui"export const Example = () => {  return <TextField label="Full name" defaultValue="John Doe" isDisabled />}

Read Only

Use the isReadOnly prop to make the text field read-only. The value can be selected but not modified.

import { TextField } from "@opengovsg/oui"export const Example = () => {  return <TextField label="Full name" defaultValue="John Doe" isReadOnly />}

Validation

See the Forms guide for end-to-end patterns including React Hook Form + Zod.

TextField supports React Aria's form validation system:

  • Built-in constraints: Use isRequired, minLength, maxLength, pattern, or type (e.g., "email", "url") for native HTML validation. Errors display after the user commits a value or submits the form.
  • Custom validation: Use the validate prop to provide a function that returns an error message string, or null/true if valid.
  • Realtime validation: Use the isInvalid prop with errorMessage for immediate feedback as the user types.
  • Validation behavior: The validationBehavior prop controls enforcement — "native" (default) prevents form submission when invalid, while "aria" only marks the field via ARIA attributes and allows submission.

Events

TextField supports the following event handlers, inherited from React Aria's TextField:

  • onFocus / onBlur — Called when the input gains or loses focus.
  • onFocusChange — Called when the focus state changes, receiving a boolean.
  • onChange — Called when the text value changes, receiving the new string value.
  • onCopy / onCut / onPaste — Called on clipboard operations.
  • onCompositionStart / onCompositionEnd — Called during IME composition for internationalized text input.
  • onKeyDown / onKeyUp — Called on keyboard events.
  • onSelect — Called when text is selected within the input.

Accessibility

  • Built with a native <input> element for full browser and assistive technology support.
  • Label, description, and error message are automatically linked to the input via aria-labelledby and aria-describedby.
  • Required and invalid states are exposed to assistive technology.
  • Supports keyboard interaction: text entry, clipboard shortcuts, and standard input navigation.
  • If no visible label is provided, an aria-label or aria-labelledby prop must be set.

Slots

  • base: The root container wrapper.
  • label: The label text element.
  • input: The input element.
  • description: The description text below the field.
  • error: The error message text.

Custom Styles

You can customize the TextField component by passing custom Tailwind CSS classes to the component slots via the classNames prop.

import { TextField } from "@opengovsg/oui"export const Example = () => {  return (    <TextField      label="Email address"      description="We'll never share your email."      classNames={{        base: "rounded-lg bg-slate-900 p-4",        label: "text-xs uppercase tracking-widest text-white",        input:          "rounded-full border-cyan-500 bg-slate-800 text-white placeholder:text-slate-400 focus:border-cyan-400",        description: "text-slate-400",      }}      inputProps={{ placeholder: "you@example.com" }}    />  )}

Props

TextField

PropTypeDefaultDescription
labelReact.ReactNode-The label for the text field
descriptionReact.ReactNode-The description text shown below the field
errorMessageReact.ReactNode | ((validation: ValidationResult) => string)-The error message to display when validation fails
valuestring-The current value (controlled)
defaultValuestring-The default value (uncontrolled)
onChange(value: string) => void-Callback fired when the value changes
type"text" | "email" | "password" | "search" | "tel" | "url""text"The type of the input element
size"xs" | "sm" | "md""md"The size of the component
variant"outline" | "unstyled""outline"The visual variant of the input
isDisabledbooleanfalseWhether the field is disabled
isReadOnlybooleanfalseWhether the field is read-only
isRequiredbooleanfalseWhether the field is required
isInvalidbooleanfalseWhether the field should display as invalid
minLengthnumber-The minimum number of characters required
maxLengthnumber-The maximum number of characters allowed
patternstring-A regular expression the value must match
validate(value: string) => ValidationError | true | null | undefined-Custom validation function
autoCompletestring-Hints for browser autocomplete
validationBehavior"native" | "aria""native"Whether to use native HTML form validation or ARIA-based validation
namestring-The name of the input, used when submitting a form
onFocus(e: FocusEvent) => void-Called when the input receives focus
onBlur(e: FocusEvent) => void-Called when the input loses focus
onFocusChange(isFocused: boolean) => void-Called when the focus state changes
inputPropsPartial<InputProps>-Additional props to pass to the input element
classNamesSlotsToClasses<"base" | "label" | "input" | "description" | "error">-Custom CSS classes for component slots

On this page