FormField

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.

Examples

Label and hint

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>
  );
}

Show an error

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>
  );
}

Mark a field as required

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>
  );
}

Check an invoice on submit

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>
  );
}

Keyboard

The keys of Input, NumberInput, Checkbox, Select, DatePicker and Button apply here.

API

FormFieldProps

NameTypeDefaultDescription
labelrequiredReactNode—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

hintReactNode—Help text below the field. Replaced by error.

Shown in: Configurator, Label and hint, Show an error and 33 more

errorReactNode—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

requiredbooleanfalseSets 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

fieldIdstring—Optional: a fixed id for the input element (instead of a generated one).

Shown in: Sum up a form's errors (Alert)

refRef<HTMLDivElement>—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

Also takes every attribute of <div>.

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