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/reactnpm install @harmo-ui/css @harmo-ui/reactpnpm add @harmo-ui/css @harmo-ui/reactyarn add @harmo-ui/css @harmo-ui/reactbunx harmo-ui add fieldnpx harmo-ui add fieldpnpm dlx harmo-ui add fieldyarn dlx harmo-ui add fieldUsage
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
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><div class="hfield">
<label class="hlabel" for="full-name">Full name</label>
<input class="hinput" id="full-name" name="full-name" type="text" autocomplete="name" required />
</div>Optional
.hfield-label-row places a marker beside the label. The app supplies the wording, and aria-describedby reads it with the control.
<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.
We'll send your receipt here.
Enter a valid email address or leave this field empty.
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.
<div class="hfield">
<label class="hlabel" for="coupon">Coupon code</label>
<input class="hinput" id="coupon" name="coupon" type="text" aria-describedby="coupon-error" />
<p class="herror" id="coupon-error"></p>
</div>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><div class="hfield" data-orientation="horizontal">
<input class="hcheckbox" type="checkbox" id="summary" name="summary" aria-describedby="summary-description" />
<div class="hfield-content">
<label class="hlabel" for="summary">Send a weekly summary</label>
<p class="hdescription" id="summary-description">You can change this at any time.</p>
</div>
</div>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.
| Prop | Effect |
|---|---|
label | The .hlabel: a <label> for the control |
htmlFor | The control's id, for a control that isn't the Field's direct child; otherwise its own id, or a generated one |
optional | The Optional marker, in your wording, such as "Optional" |
description | The .hdescription under the label |
error | The .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 |
labelAccessory | Placed in the label row after the Optional marker, such as a Password input's strength |
orientation | horizontal, for a checkbox, radio, or switch: the control comes first, and only label and description apply |
control | button, 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 |
role | group 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.
| Hook | Effect |
|---|---|
| (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 witharia-describedby. Mark required fields withrequired. - 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.