# Field

The frame around one form control: its label with an optional required mark, and help text or an error message under the control.

Block: `smbk-field`. Status: candidate. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `layout` | `stacked`, `side-by-side` | `stacked` | Stacked puts the label above the control. Side by side puts it on the left of a small control, for settings; in a narrow space the field stacks. |
| `error` | on / off | off | The value is invalid. The label and the message under the control turn the danger color, the message leads with an icon, and the control draws its danger border and icon. |
| `disabled` | on / off | off | The control is disabled. The label, its mark and the help text take the disabled color. |
| `readonly` | on / off | off | The control can be read and copied, not changed. It keeps its place in the tab order. |

Parts: `smbk-field__label`, `smbk-field__description`, `smbk-field__warning`

## Markup

```html
<div class="smbk-field smbk-field--layout-stacked">
  <label
    class="smbk-label smbk-label--tone-subtle smbk-label--type-default smbk-field__label"
    id="f-configure-label"
    for="f-configure"
  >
    <span class="smbk-label__text">Email address</span>
    <span class="smbk-label__required" aria-hidden="true">*</span>
  </label>
  <span class="smbk-input smbk-input--size-md">
    <input
      class="smbk-input__control"
      id="f-configure"
      placeholder="maya.chen@example.com"
      aria-labelledby="f-configure-label"
      aria-describedby="f-configure-description"
      required=""
    >
  </span>
  <p
    id="f-configure-description"
    class="smbk-field-message smbk-field-message--type-help smbk-field__description"
  >
    <span class="smbk-field-message__text">We send the receipt here.</span>
  </p>
</div>
```

## React

```tsx
import { Field } from './design-system/components/field/field.js';
import { Input } from './design-system/components/input/input.js';

<Field label="Email address" required layout="stacked" description="We send the receipt here.">
  <Input id="f-configure" size="md" placeholder="maya.chen@example.com" />
</Field>
```

## Stylesheet

```css
/* smbk-field — Field. Figma "Field" 4800:63897 (State: Default, Error, Disabled, Read-only × Layout: Stacked, Side by side).
 * A column: the label component in its subtle tone with the optional required
 * mark, then the control Figma's slot holds (an input, a select or a text
 * area, each drawing its own well), then the field message: a line of help
 * text, or the error message in its place. The control is not wrapped: it is
 * the field's own child, so its width and states are its own. In error, the
 * label and the message take the danger text color, as Figma binds them, the
 * message leads with an icon, and the control draws its danger border and
 * icon. Disabled, the label, its mark and the help text take the disabled
 * text color. Side by side, the label sits on the left of the control. */

.smbk-field {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-sm);
  width: 100%;
  min-width: 0;
}

/* The label hugs its words, so only they take a press, as Figma's label hugs. */
.smbk-field__label {
  align-self: flex-start;
}

/* The line under the control is the field message, whose own stylesheet draws it. The field's description class
   only turns its words to the danger color in an error and to the disabled color when disabled. */
.smbk-field--error .smbk-field__label,
.smbk-field--error .smbk-field__description {
  color: var(--smbk-color-feedback-danger-text-on-subtle);
}

.smbk-field--disabled .smbk-field__label,
.smbk-field--disabled .smbk-label__required,
.smbk-field--disabled .smbk-field__description {
  color: var(--smbk-color-text-disabled);
}

/* The warning's place, kept on the page while there is no warning so a screen reader hears one arrive, takes
   no room and no gap until it has words. */
.smbk-field__warning:empty {
  position: absolute;
}

/* ---- side by side: the label in a column on the left, as tall as a small control so its words sit
   on the control's middle; the control on the right at Figma's width, its message under it. The order
   in the page stays label, control, message: only the layout moves. ---- */

.smbk-field--layout-side-by-side {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, var(--smbk-size-field-control-width));
  align-items: start;
  column-gap: var(--smbk-space-stack-md);
  row-gap: var(--smbk-space-stack-xs);
}

.smbk-field--layout-side-by-side > * {
  grid-column: 2;
}

.smbk-field--layout-side-by-side > .smbk-field__label {
  grid-column: 1;
  grid-row: 1 / span 2;
  justify-self: start;
  min-height: var(--smbk-size-control-sm);
}

/* A narrow space has no room for two columns: the field stacks. */
@container style(--smbk-layout: narrow) {
  .smbk-field--layout-side-by-side {
    display: flex;
    gap: var(--smbk-space-stack-sm);
  }

  .smbk-field--layout-side-by-side > .smbk-field__label {
    min-height: 0;
  }
}
```

## Tokens it reads

```json
{
  "space": {
    "stack": {
      "sm": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Gap between things stacked one above another, small."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Gap between things stacked one above another, medium."
      },
      "xs": {
        "$type": "dimension",
        "$value": "{{space.4}}",
        "$description": "Gap between things stacked one above another, extra small."
      }
    }
  },
  "color": {
    "feedback": {
      "danger": {
        "text-on-subtle": {
          "$type": "color",
          "$value": "{{color.red.700}}",
          "$description": "Danger messages: text on the pale fill."
        }
      }
    },
    "text": {
      "disabled": {
        "$type": "color",
        "$value": "{{color.grey.500}}",
        "$description": "Text in unavailable controls."
      }
    }
  },
  "size": {
    "field": {
      "control": {
        "width": {
          "$type": "dimension",
          "$value": "200px",
          "$description": "Width of the control in a side-by-side field, the label taking the rest of the row. A fixed frame width in Figma with no variable behind it; it should become one."
        }
      }
    },
    "control": {
      "sm": {
        "$type": "dimension",
        "$value": "{{size.32}}",
        "$description": "Height of a small control: 32 comfortable, 28 compact. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      }
    }
  }
}
```

## Purpose

The frame that turns a bare control into a form question: a label above it that stays while the reader types, an optional mark saying it must be filled, and a line under it with help or, when something is wrong, the error message. It holds one control, as Figma's slot does: an input, a select or a text area. It does not draw the control and never decides when a value is wrong; the form does.

## Anatomy

- The block, a column spaced by the small stack step. Side by side, two columns: the label on the left, as tall as a small control so its words sit on the control's middle; the control on the right at a fixed width, with its line under it.
- The label part, the label component in its subtle tone, with its required mark when the field is required.
- The control, the field's own child, unwrapped: an input, a select, a text area or a search field. Its width and states are its own.
- The description part, a line under the control: a field message of the help type, carrying the field's class beside its own. In error it is a field message of the error type, a circled-cross icon and the error message, in the help text's place, with the same class.
- The warning part, optional: a field message of the warning type under the help text or the error, for something to notice that is not an error, such as Caps Lock being on in a password field.

## When to use

- Every form control that the reader fills in: a name, an email, a role, a message.
- A single control on its own, such as a field inside a popover.

## When not to use

- A group of checkboxes or radios under one question. Use the [field group](field-group.md), a fieldset with its legend.
- A search box at the top of a list, named by what is around it. Use the [search field](search-field.md) with an `aria-label`.

## Behavior

The field passes its state to the control: it gives the control its id unless it has one, links the line under it to it, marks it required, makes it disabled or read-only, and, in error, turns on the control's danger border and icon. The system's controls read all of this from the field around them, so a control inside a site's own component, such as a wrapper that adds a site's behaviour, is still named, described and marked by the field, though the wrapper passes nothing on. In error, the label and the message take the danger text color, as Figma binds them, and the message leads with its icon. Given an error message, the line shows the message in place of the help text; given only that the field is in error, it shows the help text as the message. Nothing in the field moves. Disabled, the label, its mark and the help text dim with the control. Read-only, only the control changes: it can be read and copied, not changed.

A warning is a line of its own under the help text or the error, with the warning icon; it does not mark the field invalid and does not stop the form. While the page passes `null` for it, the field keeps an empty place for it that takes no room, and the warning fills that place when it comes.

The id the field makes can change when the page changes, so give the control its own id when the page needs one that stays the same, such as for tests or a page rendered ahead of time.

The field fills the width of its container; the form decides how wide it is.

Two layouts. Stacked puts the label above the control. Side by side puts the label on the left of the control, for a column of settings: give the control its small size, since the label lines up with a small control's middle. The page keeps its order, label, control, line, so a screen reader and the Tab key meet them as before; only the layout moves. In a narrow space a side-by-side field stacks.

The select has no help line of its own. Put a select inside a field to give it help text.

## Accessibility

### What it does for you

- The label is a real label element tied to the control, so it names the control and a press on it reaches the control. It also names the control through `aria-labelledby`, which is what names a select's open list; a control given its own `aria-label` or `aria-labelledby` keeps it.
- The line under the control describes it through `aria-describedby`, so help and error messages are read with the control. The control's own `aria-describedby` is kept as well.
- An error marks the control invalid (`aria-invalid`) as well as turning it red. The message is an alert, so a screen reader reads it out as it appears, and the icons in the control and before the message make the error a shape as well as a color.
- A warning is a status: a screen reader reads it when it appears, once it has finished what it is saying, and reads it again with the control while it shows. It is announced only when its place was already on the page.
- Read-only keeps the control in the tab order, so its value can still be reached and read; disabled takes it out.
- The required mark is decorative; the control itself is marked required (the native attribute, or `aria-required` on the select).

### What you must do

- Always give a message with an error. Given `error` with no message, the field shows its help text as the message, which may not say what is wrong. The message should say what to change.
- Pass `null` for the warning while there is none, rather than leaving the prop out, so its place is on the page before the words arrive.
- Put any limit the reader must meet in the help text, so it is known before the first try. The help text comes before any error and is replaced by the message only when one is given.

### How to test

- With a screen reader, focus the control: it is read by its label, then its help or error, and as required where it is.
- Press the label with a pointer: focus moves to the control.
- Cause an error: the message is read as it appears and the control is read as invalid.
- With the colors removed, the error icons and the message still say something is wrong.
- Tab to a read-only control: it takes focus. A disabled one does not.
- With text at 200% and the page 320 pixels wide, the label, control and line stay whole.
- In a forced-color mode, the control, its icons and the message are still visible.

## Usage rules

### Do

- Name the value in the label ("Email address"), not the instruction ("Enter your email").
- Write an error message that says how to put it right.
- Keep the help text short: one line in most widths.

### Don't

- Use the placeholder as the only label.
- Show an error before the reader has had a chance to answer.
- Put two controls in one field.
