Field

A labelled form field — label, description, control and error, with the wiring done for you.

import { ... } from '@corndev/ui'

Where requests are sent.

Use an http or https URL.

HTML

No JavaScript required — these classes are the whole component.

<div class="corn-form-field">
  <label class="corn-label" for="secret">Client secret</label>
  <p class="corn-form-description" id="secret-description">Kept in the keychain</p>
  <input class="corn-input" id="secret" type="password" aria-describedby="secret-description" />
  <p class="corn-form-error" id="secret-error">Required</p>
</div>

Classes

Class
.corn-form
.corn-label
.corn-form-field
.corn-form-description
.corn-form-error

Attributes

OnAttributeValues
.corn-form-fielddata-layoutinline
.corn-form-errordata-visiblepresent or absent

Vanilla JS

Optional. createField only adds and removes the classes and attributes above, so the HTML is equivalent. Reach for it when you need to change options at runtime.

import { createField } from '@corndev/ui';
import '@corndev/ui/theme';

const el = document.querySelector('#target');
const instance = createField(el, { /* options */ });

Options

OptionTypeDefaultDescription
errorstringundefinedMessage shown beneath the control. Marks the control invalid while it is set; clear it by passing undefined.

React

Field — from @corndev/ui-react.

Presentational, not bound to useForm: it wires labelling and description, never value or validation state, so it suits a field driven by a form hook and one driven by a parsed document equally. useForm / useField keep working alongside it. It renders exactly one wrapper element, .corn-form-field, which is a flex column carrying min-inline-size: 0 so a field nested in a Fieldset inside a flex column shrinks rather than overflowing.

Props

PropTypeDefaultDescription
labelReact.ReactNodeundefinedNames the control.
descriptionReact.ReactNodeundefinedA hint under the label — what the field wants, before anything is wrong with it.
errorReact.ReactNodeundefinedShown beneath the control, and marks it invalid while set.
requiredbooleanfalseMarks the control required and shows the marker beside the label.
htmlForstringundefinedId of the control, when you are rendering one with an id of its own rather than letting Field assign one.
layout'stacked'|'inline'undefinedWhere the label goes. Left unset, Field asks the control: a Checkbox, Radio or Switch — or a raw <input type="checkbox"> — puts its label beside it, everything else above it. Set it only to overrule that.
classNamestringundefinedMerged with the Corn UI classes.
childrenReact.ReactNoderequiredThe control.

React usage

<Field label="Server URL" description="Where requests are sent." error={error} required>
  <Input value={url} onChange={e => setUrl(e.target.value)} />
</Field>

The control is the child, so any of them works: Input, Select, Textarea, Checkbox, Combobox or NumberInput. Field doesn’t own the value; it owns the part that is easy to get wrong and silent when it is:

  • the control’s id, and the label’s for pointing at it
  • aria-describedby pointing at the description and the error
  • aria-invalid while there is an error
  • required on the control, with a marker beside the label

An unconnected label looks exactly like a connected one, until a screen reader announces the field as unlabelled.

Checkboxes and switches

A checkbox, radio or switch gets its label beside it rather than above it, because the control says which kind it is. Pass layout="inline" or layout="stacked" to choose yourself. Clicking an inline label toggles the control, and the cursor shows it.

With a form

Field is presentational, so it works with useField from Form. Pass the error to Field and the rest to the control:

import { Form, useField, Field, Input, Button } from '@corndev/ui-react';

function EmailField() {
  const { error, ...input } = useField('email');
  return (
    <Field label="Email" error={error} required>
      <Input type="email" {...input} />
    </Field>
  );
}

<Form initialValues={{ email: '' }} validate={v => (v.email ? {} : { email: 'Required' })} onSubmit={save}>
  <EmailField />
  <Button type="submit">Sign in</Button>
</Form>