Input

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.

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

Put it in a 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.

Examples

Text field

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

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.

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.

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.

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.

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.

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.

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

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

NameTypeDefaultDescription
size"sm" | "md"the size of a ControlSizeProvider around it, else "md"sm for a toolbar and dense forms, md otherwise.

Shown in: Configurator, Sizes, Open it with a shortcut (CommandPalette) and 3 more

charsnumber—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.

Shown in: Configurator, Sized to the value (Sizes), Narrow places (Sizes)

invalidboolean—Marks the field as invalid. FormField sets it itself as soon as it carries an error - by hand only necessary without FormField.

Shown in: Configurator, With an error, With an error (TreeView)

numericbooleanfalseRight-aligned tabular figures in Geist Mono, e.g. for key figures.

Shown in: Configurator, Align reference numbers, Take a delivery address and 2 more

clearablebooleanfalseShows a gently appearing cross where there is content, which clears the input. Requires a controlled field (value) and onClear.

Shown in: Clear the field, A row that holds still (Sizes)

refRef<HTMLInputElement>—Receives the component's root element. from RefProp

Shown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more

Events

NameTypeDefaultDescription
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.

Shown in: Clear the field, A row that holds still (Sizes)

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

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/core

Getting started

Customising

Layout

Typography

Actions

Forms

Choosing

Dates and times

Feedback

Overlays

Navigation

Data display

API index