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.
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
| 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. |
InputProps| Name | Type | Default | Description |
|---|---|---|---|
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 |
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. Shown in: Configurator, Sized to the value (Sizes), Narrow places (Sizes) |
invalid | boolean | — | 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) |
numeric | boolean | false | Right-aligned tabular figures in Geist Mono, e.g. for key figures. Shown in: Configurator, Align reference numbers, 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. Shown in: Clear the field, A row that holds still (Sizes) |
ref | Ref<HTMLInputElement> | — | Receives the component's root element. from RefPropShown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more |
| Name | 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. Shown in: Clear the field, A row that holds still (Sizes) |
Also takes every attribute of <input> – without size.
FormField.What umriss deliberately does not build, and why: ADR-0032.