# Text Field

> Text input with label, required or optional mark, hint, and inline validation (error or success).

Component · Forms · Pro kit · [Live page](https://ui.twentyfirst.media/pro/components/TextField/)

Text input with label, required or optional mark, hint, and inline validation (error or success).

Props: `label`, `required`, `optional`, `hint`, `error`, `success`, `icon` (leading IconName), `suffix` (unit text), `type`, `value`, `defaultValue`, `onChange`, `onBlur`, `disabled`, `readOnly`, `placeholder`, `autoComplete`, `autoFocus`, `id`, `name`.

Native constraints go straight to the input, so the browser checks them and `Form` reads them: `min`, `max` and `step` (for `type="number"`, dates and times), `minLength`, `maxLength`, `pattern`, and `inputMode` for the touch keyboard.

```tsx
<TextField label="Seats" name="seats" type="number" min={1} max={50} step={1} required />
```

- `name` is the value's key in `Form` and where its validation message shows; `required` marks the label and sets the native attribute.
- Validate on blur, not on every key: show the error once the person leaves the field, then clear it live as they fix it.
- Error text says what is wrong in plain words ("That doesn’t look like an email address"), never "Invalid input".
- Use `success` sparingly, for checks that took effort (a free username, a verified domain).
- Mark the minority: if most fields are required, mark the optional ones instead.
- `FormField` is the same shell without an input, for wrapping custom controls.

## Example

```tsx
import * as React from "react";
import { TextField } from "@twentyfirstmedia/pro";

function App() {
  const e = React.useState("mara@"),
    n = React.useState("Mara Ellis");
  const bad = !/^[^@\s]+@[^@\s]+\.[a-z]{2,}$/i.test(e[0]);
  return (
    <div
      className="tf-panel tf-grid"
      style={
        {
          "--cols": "1fr 1fr",
          gap: 16,
        } as React.CSSProperties
      }
    >
      <TextField
        label="Full name"
        required
        value={n[0]}
        onChange={(x) => {
          n[1](x.target.value);
        }}
        success={n[0].length > 1 ? "Looks good" : null}
      />
      <TextField
        label="Email"
        required
        icon="mail"
        value={e[0]}
        onChange={(x) => {
          e[1](x.target.value);
        }}
        error={bad ? "That doesn’t look like an email address" : null}
      />
      <TextField label="Company" optional placeholder="Acme GmbH" hint="Shown on invoices" />
      <TextField label="Monthly budget" suffix="EUR" defaultValue="1,200" />
      <TextField
        label="Workspace URL"
        defaultValue="halo.app/acme"
        disabled
        hint="Ask an owner to change it"
      />
    </div>
  );
}
export default App;
```

## Props

### TextField

| Prop | Type | Notes |
| --- | --- | --- |
| `type?` | `string` | Input type (`"text"` by default): `"email"`, `"number"`, `"tel"`, `"url"`, `"password"` … |
| `icon?` | `IconName` |  |
| `suffix?` | `ReactNode` |  |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `placeholder?` | `string` |  |
| `disabled?` | `boolean` |  |
| `readOnly?` | `boolean` |  |
| `autoComplete?` | `string` |  |
| `autoFocus?` | `boolean` |  |
| `min?` | `number \| string` | Smallest value for `type="number"`, `"date"` and the like (native `min`). |
| `max?` | `number \| string` | Largest value (native `max`). |
| `step?` | `number \| string` | Step between valid values (native `step`), e.g. `1` for whole numbers or `"any"`. |
| `minLength?` | `number` | Fewest characters (native `minlength`). |
| `maxLength?` | `number` | Most characters (native `maxlength`). |
| `pattern?` | `string` | Pattern the value must match (native `pattern`). |
| `inputMode?` | `"none" \| "text" \| "tel" \| "url" \| "email" \| "numeric" \| "decimal" \| "search"` | Virtual keyboard on touch devices (native `inputmode`), e.g. `"numeric"` or `"decimal"`. |
| `onChange?` | `(e: ChangeEvent<HTMLInputElement>) => void` |  |
| `onBlur?` | `(e: ChangeEvent<HTMLInputElement>) => void` |  |
| `onKeyDown?` | `(e: KeyboardEvent<HTMLInputElement>) => void` |  |
| `action?` | `ReactNode` | An interactive control at the end of the field (an `IconButton`: clear, copy, locate …). Unlike `suffix` it can be clicked, and it stays when the field shows an error or success mark. |
| `reveal?` | `boolean` | For `type="password"`: a button that shows and hides the password (default true). |
| `label?` | `string` |  |
| `hint?` | `string` |  |
| `error?` | `string` |  |
