# Input

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/input/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

A single line of text (text field, text box): names, references, addresses, search terms. Reach for it whenever the answer is short free text; for numbers take a [NumberInput](https://romanhaendler.github.io/umriss-ui/core/numberinput/).

```ts
import { Input } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/input/

Put it in a [FormField](https://romanhaendler.github.io/umriss-ui/core/formfield/): the field brings the label, the hint, the error and the wiring a screen reader needs. Every attribute of the native input passes through.

It fills its place, or takes the width of its value with `chars` - a postcode `chars={5}`; in a row it is sixteen characters wide whatever it holds. How wide and how tall every field is stands on [Sizes](https://romanhaendler.github.io/umriss-ui/core/sizes/).

## Examples

### Text field

Wrap the field in a `FormField` for its label; the field takes every attribute of the native input.

```tsx
import { FormField, Input } from "@umriss-ui/core";

export default function TextField() {
  return (
    <FormField label="Consignee" style={{ maxWidth: 360 }}>
      <Input placeholder="Company or person" autoComplete="organization" />
    </FormField>
  );
}
```

### Sizes

Use `size="sm"` in dense forms and table rows, where the field stands beside small buttons.

```tsx
import { FormField, Grid, Input } from "@umriss-ui/core";

export default function Sizes() {
  return (
    <Grid minItemWidth="220px" gap={4}>
      <FormField label="Tour" hint="Medium, the default.">
        <Input defaultValue="T-01 North" />
      </FormField>
      <FormField label="Tour" hint="Small.">
        <Input size="sm" defaultValue="T-01 North" />
      </FormField>
    </Grid>
  );
}
```

### States

An `error` on the `FormField` marks the field invalid; `disabled` and `readOnly` behave as on the native input.

```tsx
import { FormField, Grid, Input } from "@umriss-ui/core";

export default function States() {
  return (
    <Grid minItemWidth="240px" gap={4}>
      <FormField label="Street" required>
        <Input placeholder="Street and number" autoComplete="street-address" />
      </FormField>
      <FormField label="Postcode" error="A postcode here has five digits.">
        <Input defaultValue="2041" />
      </FormField>
      <FormField label="Depot" hint="Set by the tour, not editable.">
        <Input value="North depot" readOnly />
      </FormField>
      <FormField label="Vehicle" hint="Disabled until a tour is chosen.">
        <Input disabled placeholder="FP 000 X" />
      </FormField>
      <FormField label="Delivery instructions" hint="A long value scrolls inside the field.">
        <Input defaultValue="Holloway Garden Supplies, goods entrance at the rear, ring twice, ask for the warehouse lead on duty" />
      </FormField>
    </Grid>
  );
}
```

### With an error

A `FormField` with an `error` marks the field invalid by itself. Without one, set `invalid` and tie your message to the field with `aria-describedby`. Five digits clear it.

```tsx
import { useId, useState } from "react";
import { Input, Stack, Text } from "@umriss-ui/core";

export default function WithAnError() {
  const [postcode, setPostcode] = useState("2041");
  const messageId = useId();
  const error = /^\d{5}$/.test(postcode) ? undefined : "A postcode here has five digits.";

  return (
    <Stack gap={1} style={{ maxWidth: 360 }}>
      <Input
        aria-label="Postcode"
        value={postcode}
        onChange={(event) => setPostcode(event.target.value)}
        invalid={error !== undefined}
        aria-describedby={error ? messageId : undefined}
        inputMode="numeric"
        autoComplete="postal-code"
      />
      {error && (
        <Text id={messageId} size="xs" style={{ color: "var(--u-color-danger-text)" }}>
          {error}
        </Text>
      )}
    </Stack>
  );
}
```

### Clear the field

Set `clearable` on a controlled field with `onClear`; the cross appears only while there is content.

```tsx
import { useState } from "react";
import { FormField, Grid, Input } from "@umriss-ui/core";

export default function ClearTheField() {
  const [tracking, setTracking] = useState("FP-4471-2026-0317");
  const [search, setSearch] = useState("");

  return (
    <Grid minItemWidth="240px" gap={4}>
      <FormField label="Tracking number">
        <Input
          clearable
          value={tracking}
          onChange={(event) => setTracking(event.target.value)}
          onClear={() => setTracking("")}
        />
      </FormField>
      <FormField label="Search shipments" hint="Still empty, so no cross.">
        <Input
          clearable
          placeholder="Name, street or number"
          value={search}
          onChange={(event) => setSearch(event.target.value)}
          onClear={() => setSearch("")}
        />
      </FormField>
    </Grid>
  );
}
```

### Align reference numbers

Set `numeric` where codes are compared digit by digit; the text stands right-aligned in tabular figures.

```tsx
import { FormField, Input, Stack } from "@umriss-ui/core";

export default function AlignReferenceNumbers() {
  return (
    <Stack gap={3} style={{ maxWidth: 280 }}>
      <FormField label="Shipment">
        <Input numeric defaultValue="100829" />
      </FormField>
      <FormField label="Return shipment">
        <Input numeric defaultValue="1011487" />
      </FormField>
    </Stack>
  );
}
```

### Take a delivery address

Check the fields on submit and hand each message to its `FormField`; until then the fields stay uncontrolled.

```tsx
import { useState } from "react";
import type { FormEvent } from "react";
import { Button, FormField, Grid, Input, Stack, Text } from "@umriss-ui/core";

interface Errors {
  name?: string;
  street?: string;
  postcode?: string;
}

function check(data: FormData): Errors {
  const errors: Errors = {};
  if (String(data.get("name")).trim() === "") errors.name = "Name the consignee.";
  if (String(data.get("street")).trim() === "") errors.street = "Give a street and number.";
  if (!/^\d{5}$/.test(String(data.get("postcode")))) errors.postcode = "A postcode here has five digits.";
  return errors;
}

export default function TakeADeliveryAddress() {
  const [errors, setErrors] = useState<Errors>({});
  const [saved, setSaved] = useState(false);

  const submit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const next = check(new FormData(event.currentTarget));
    setErrors(next);
    setSaved(Object.keys(next).length === 0);
  };

  return (
    <form onSubmit={submit} noValidate style={{ maxWidth: 520 }}>
      <Stack gap={4}>
        <FormField label="Consignee" required error={errors.name}>
          <Input name="name" autoComplete="organization" defaultValue="Oakridge Pharmacy" />
        </FormField>
        <FormField label="Street" required error={errors.street}>
          <Input name="street" autoComplete="street-address" />
        </FormField>
        <Grid minItemWidth="160px" gap={4}>
          <FormField label="Postcode" required error={errors.postcode}>
            <Input name="postcode" numeric autoComplete="postal-code" defaultValue="204" />
          </FormField>
          <FormField label="Town">
            <Input name="town" autoComplete="address-level2" defaultValue="Ferrow" />
          </FormField>
        </Grid>
        <Stack direction="row" gap={3} align="center">
          <Button type="submit" variant="primary" size="sm">
            Save address
          </Button>
          {saved && (
            <Text size="sm" tone="muted">
              Saved to shipment 100829.
            </Text>
          )}
        </Stack>
      </Stack>
    </form>
  );
}
```

## When to use something else

- A figure that is calculated with, with a unit and a range → NumberInput
- Text over several lines → Textarea
- One value out of a known list, found by typing → Combobox

## Keyboard

| Key | Action |
|---|---|
| `Tab` | Moves into the field and on. The cross of a `clearable` field is not a stop: the keyboard clears the field by its own editing keys. |
| `Typing, arrows, Home / End` | The browser's own editing keys; the field adds none. |

## API

### `InputProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `size` | `"sm" \| "md"` | the size of a `ControlSizeProvider` around it, else `"md"` | `sm` for a toolbar and dense forms, `md` otherwise.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/input/#configurator-input), [Sizes](https://romanhaendler.github.io/umriss-ui/core/input/#sizes), [Open it with a shortcut (CommandPalette)](https://romanhaendler.github.io/umriss-ui/core/commandpalette/#shortcut) and 3 more |
| `chars` | `number` | — | The width in characters - room for the value; the field adds its own padding and cross. Given, the field is that wide wherever it stands, and never wider than its place. Without it the field fills its place, and is 20 characters wide where the place asks - in a toolbar or a row.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/input/#configurator-input), [Sized to the value (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#sized-to-the-value), [Narrow places (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#narrow-places) |
| `invalid` | `boolean` | — | Marks the field as invalid. `FormField` sets it itself as soon as it carries an `error` - by hand only necessary without `FormField`.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/input/#configurator-input), [With an error](https://romanhaendler.github.io/umriss-ui/core/input/#with-an-error), [With an error (TreeView)](https://romanhaendler.github.io/umriss-ui/core/treeview/#with-an-error) |
| `numeric` | `boolean` | `false` | Right-aligned tabular figures in Geist Mono, e.g. for key figures.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/input/#configurator-input), [Align reference numbers](https://romanhaendler.github.io/umriss-ui/core/input/#align-reference-numbers), [Take a delivery address](https://romanhaendler.github.io/umriss-ui/core/input/#take-a-delivery-address) and 2 more |
| `clearable` | `boolean` | `false` | Shows a gently appearing cross where there is content, which clears the input. Requires a controlled field (value) and onClear.<br>Shown in: [Clear the field](https://romanhaendler.github.io/umriss-ui/core/input/#clear-the-field), [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still) |
| `ref` | `Ref<HTMLInputElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

#### Events

| Prop | Type | Default | Description |
|---|---|---|---|
| `onClear` | `() => void` | — | Called by the cross. Clearing belongs to the caller: only it knows whether "empty" here is the empty text or no value at all.<br>Shown in: [Clear the field](https://romanhaendler.github.io/umriss-ui/core/input/#clear-the-field), [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still) |

Also takes every attribute of `<input>` – without `size`.

## Known limits

- No input masks or formatting while typing; check the value on leave or on submit.
- No validation of its own: the error is the application's, handed to the `FormField`.

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
