Field

Arranges and connects a label, optional marker, description, control, and error message. The application owns validation and values.

Installation

bun add @harmo-ui/css @harmo-ui/react

Usage

import { Field, Input } from "@harmo-ui/react";

<Field
  label="Email address"
  optional="Optional"
  description="We'll send your receipt here."
  error={errors.email}
>
  <Input name="email" type="email" autoComplete="email" />
</Field>

Examples

This site is using your theme

Label and control

A column with the label above the control, connected with for and id. required marks a field that must be filled.

<Field label="Full name">
  <Input name="full-name" type="text" autoComplete="name" required />
</Field>

Optional

.hfield-label-row places a marker beside the label. The app supplies the wording, and aria-describedby reads it with the control.

Optional
<Field label="Company" optional="Optional">
  <Input
    name="company"
    type="text"
    autoComplete="organization"
    placeholder="Acme Inc."
  />
</Field>

With a description

A description sits close under the label and is connected with aria-describedby.

We'll send sign-in links to this address.

<Field
  label="Work email"
  description="We'll send sign-in links to this address."
>
  <Input name="work-email" type="email" autoComplete="email" required />
</Field>

With an error

Every part together. After validating, the app sets aria-invalid="true" on the control and the error's text, and connects the error with aria-describedby.

Optional

We'll send your receipt here.

Enter a valid email address or leave this field empty.

<Field
  label="Email address"
  optional="Optional"
  description="We'll send your receipt here."
  error="Enter a valid email address or leave this field empty."
>
  <Input
    name="receipt-email"
    type="email"
    autoComplete="email"
    defaultValue="not-an-email"
  />
</Field>

Validating

The error element is always there, with nothing inside it. Setting its text reveals it. To hide it, the script adds hidden, which closes it, and clears the text once it has gone. Type a code and leave the field to see it; SPRING25 is valid.

const [error, setError] = useState<string>();
const validate = (code: string) =>
  setError(
    ["", "SPRING25"].includes(code.trim().toUpperCase())
      ? undefined
      : "This coupon code is not valid.",
  );

return (
  <Field label="Coupon code" error={error}>
    <Input
      name="coupon"
      type="text"
      onBlur={(event) => validate(event.target.value)}
    />
  </Field>
);

Horizontal

data-orientation="horizontal" puts the control first, for checkboxes, radios, and switches. .hfield-content holds the label and a description.

You can change this at any time.

<Field
  orientation="horizontal"
  label="Send a weekly summary"
  description="You can change this at any time."
>
  <Checkbox name="summary" />
</Field>

API reference

React

<Field> renders these parts around its child, the control, and connects them: the control gets the generated id, and aria-describedby and aria-invalid for the parts there are. Harmo's form controls read these; an id or ARIA attribute you pass a control yourself wins, and descriptions are combined. A control's own id also becomes the label's for when the control is the Field's direct child; for one nested deeper, pass the same id as htmlFor. Other props are the wrapper <div>'s attributes.

PropEffect
labelThe .hlabel: a <label> for the control
htmlForThe control's id, for a control that isn't the Field's direct child; otherwise its own id, or a generated one
optionalThe Optional marker, in your wording, such as "Optional"
descriptionThe .hdescription under the label
errorThe .herror. Pass it to every field you validate, even while it's undefined, so the element is in place when the first error appears; a field without error has none. A message sets aria-invalid
labelAccessoryPlaced in the label row after the Optional marker, such as a Password input's strength
orientationhorizontal, for a checkbox, radio, or switch: the control comes first, and only label and description apply
controlbutton, for a control that's a button rather than a form field, such as a <Button>: the label is a <span> that names it through aria-labelledby
rolegroup or radiogroup makes the Field the group, named by its label, for controls that each have their own

HTML

A <div class="hfield">, with data-orientation="horizontal" for a checkbox, radio, or switch. Its parts are .hlabel, .hfield-label-row, .hfield-optional, .hdescription, .herror, and .hfield-content. Load field.css, plus the control's CSS, such as input.css or checkbox.css.

HookEffect
(default)Stacked: a column with 8px gaps
data-orientation="horizontal"The control first, then the label, or .hfield-content with the label and description

Render the .herror element even when there is no error, with no text or whitespace inside it. Setting its text reveals it. To hide it, add hidden, which animates it closed, then clear the text once that exit has finished, so screen readers don't read a stale message; remove hidden before showing the next error.

A script is only needed for validation. See Field's full contract.

Accessibility

  • Connect the label with for/id, and the description, Optional marker, and error with aria-describedby. Mark required fields with required.
  • Set aria-invalid="true" only when the application has validated the value; required fields aren't marked invalid on load.
  • Descriptions use --harmo-color-text-tertiary (#6e7480), 4.69:1 on white, which clears WCAG AA's 4.5:1 for small text.