The frame around a field: its label, a hint below it and the error that replaces the hint. Wrap every field in one, so the question, the help and the problem are read out together.
import { FormField, useFormField } from "@umriss-ui/core";
Every field of Forms, Choosing and Dates and times reads its id, description, invalid and required state from the surrounding FormField; an error marks the field invalid by itself. A control of your own gets the same through useFormField.
required sets the mark and aria-required; it checks nothing. When and how a value is checked stays with the application.
Give every field a label; a hint below it says what the field expects and is read out with it.
import { FormField, Input } from "@umriss-ui/core";
export default function LabelAndHint() {
return (
<FormField label="Cost centre" hint="As on the budget sheet, for example CC-2100." style={{ maxWidth: 360 }}>
<Input placeholder="CC-0000" />
</FormField>
);
}
Pass error once a value fails your check; it replaces the hint and marks the field invalid.
import { FormField, Grid, Input } from "@umriss-ui/core";
export default function ShowAnError() {
return (
<Grid minItemWidth="240px" gap={4}>
<FormField label="Cost centre" hint="As on the budget sheet, for example CC-2100.">
<Input defaultValue="CC-2100" />
</FormField>
<FormField
label="Cost centre"
hint="As on the budget sheet, for example CC-2100."
error="CC-2700 is not on the budget sheet."
>
<Input defaultValue="CC-2700" />
</FormField>
<FormField
label="Reason for the overspend, as the budget owner will read it in the monthly review"
error="Give a reason of at least ten characters, so the review can follow the decision without asking again."
>
<Input defaultValue="Events" />
</FormField>
</Grid>
);
}
Set required for the mark on the label and aria-required on the field; checking the value stays yours.
import { Checkbox, FormField, Grid, Input } from "@umriss-ui/core";
export default function MarkAFieldAsRequired() {
return (
<Grid minItemWidth="240px" gap={4}>
<FormField label="Invoice number" required>
<Input placeholder="INV-0000" />
</FormField>
<FormField label="Purchase order" hint="Optional.">
<Input placeholder="PO-0000" />
</FormField>
<FormField label="Approval" required>
<Checkbox label="I checked the amounts against the delivery note" />
</FormField>
</Grid>
);
}
Hold the values, check them all on submit, and give each FormField its own message; the fields follow.
import { useState } from "react";
import type { FormEvent } from "react";
import { Button, DatePicker, FormField, Grid, Input, NumberInput, Select, Stack, Text } from "@umriss-ui/core";
interface Invoice {
number: string;
centre: string;
amount: number | null;
due: Date | null;
}
type Errors = Partial<Record<keyof Invoice, string>>;
function check(invoice: Invoice): Errors {
const errors: Errors = {};
if (!/^INV-\d{4}$/.test(invoice.number)) errors.number = "An invoice number reads INV- and four digits.";
if (invoice.centre === "") errors.centre = "Choose the cost centre the invoice is booked on.";
if (invoice.amount === null || invoice.amount <= 0) errors.amount = "Give the net amount.";
if (invoice.due === null) errors.due = "Give the due date from the invoice.";
return errors;
}
export default function CheckAnInvoiceOnSubmit() {
const [invoice, setInvoice] = useState<Invoice>({ number: "INV-31", centre: "", amount: 4860, due: null });
const [errors, setErrors] = useState<Errors>({});
const [booked, setBooked] = useState(false);
const set = (patch: Partial<Invoice>) => setInvoice((previous) => ({ ...previous, ...patch }));
const submit = (event: FormEvent) => {
event.preventDefault();
const next = check(invoice);
setErrors(next);
setBooked(Object.keys(next).length === 0);
};
return (
<form onSubmit={submit} noValidate style={{ maxWidth: 560 }}>
<Stack gap={4}>
<Grid minItemWidth="220px" gap={4}>
<FormField label="Invoice number" required error={errors.number}>
<Input numeric value={invoice.number} onChange={(event) => set({ number: event.target.value })} />
</FormField>
<FormField label="Cost centre" required error={errors.centre}>
<Select value={invoice.centre} onChange={(event) => set({ centre: event.target.value })}>
<option value="" disabled>
Choose a cost centre
</option>
<option value="CC-1200">CC-1200 Marketing</option>
<option value="CC-2200">CC-2200 Design</option>
<option value="CC-4300">CC-4300 IT</option>
</Select>
</FormField>
<FormField label="Net amount" required error={errors.amount}>
<NumberInput value={invoice.amount} onChange={(amount) => set({ amount })} min={0} decimals={2} suffix="€" />
</FormField>
<FormField label="Due" required error={errors.due}>
<DatePicker value={invoice.due} onChange={(due) => set({ due })} clearable />
</FormField>
</Grid>
<Stack direction="row" gap={3} align="center">
<Button type="submit" variant="primary" size="sm">
Book the invoice
</Button>
{booked && (
<Text size="sm" tone="muted">
{invoice.number} booked on {invoice.centre}.
</Text>
)}
</Stack>
</Stack>
</form>
);
}
The keys of Input, NumberInput, Checkbox, Select, DatePicker and Button apply here.
FormFieldProps| Name | Type | Default | Description |
|---|---|---|---|
labelrequired | ReactNode | — | The label of the field. It is mandatory: a field without a name is a
field without a question for a screen reader. Shown in: Configurator, Label and hint, Show an error and 77 more |
hint | ReactNode | — | Help text below the field. Replaced by error.Shown in: Configurator, Label and hint, Show an error and 33 more |
error | ReactNode | — | Error message; it marks the field as invalid at the same time. Shown in: Configurator, Show an error, Check an invoice on submit and 22 more |
required | boolean | false | Sets the required mark on the label and aria-required on the field -
on Input, Select, Textarea, NumberInput, Combobox, RadioGroup and
Checkbox. The triggers of the pickers and of the MultiSelect are
buttons, on which aria-required is not a permitted attribute; there
the mark carries it alone. It validates nothing - validation belongs to
the caller (ADR-0060).Shown in: Configurator, Mark a field as required, Check an invoice on submit and 9 more |
fieldId | string | — | Optional: a fixed id for the input element (instead of a generated
one). Shown in: Sum up a form's errors (Alert) |
ref | Ref<HTMLDivElement> | — | 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 |
Also takes every attribute of <div>.
error (ADR-0060). No form state and no submit handling either.FormField; a group of fields that share one label is a fieldset of your own.What umriss deliberately does not build, and why: ADR-0032.