# Form

> Labels, controls, checks, errors, the form alert and the password field for every form a member fills in.

- Block class: `.hs-form`
- CSS: `components/form.css` (also inside https://brand.honorsociety.org/brand.css)
- JS: `components/password.js`. Load it only on a page that has this component.
- Status: decided
- Decision record: https://brand.honorsociety.org/decisions/components-round-2b.html
- Variants: Field with a hint, Input, select, textarea, error, disabled, Checkbox, radio and fieldset, Form alert, Password field, Password field, sign-in
- HTML page: https://brand.honorsociety.org/components/form/

## What it is

Label 16px/600 ink. Control 40px, 16px ink, 1px slate-500 border (3.7:1), 4px radius. Focus is the shared gold outline and navy ring, and the border turns navy. Field order: label, control, hint, error. Mark the optional fields, not the required ones.

## When to use

- Any form a visitor or member fills in: login, registration, `/member/induction`, the profile edit dialogs.
- A form that failed on submit: put the form alert at the top of the form.
- A new password: the password field with `password.js`.
- A password on a sign-in or account page: the same password field and eye toggle, without the meter and the rules.
- Date parts or a city and ZIP pair on one line: `.hs-fieldset__row` inside a fieldset.

## When not to use

- To show a coupon or promotion code a member copies, use the [Code box](/components/code-box/).
- To filter one list by category, use [Filter chips and tags](/components/chips/).
- To confirm an action that worked, use a [Callout](/components/callout/) with `role="status"`. The form alert is for errors only.
- To open a short form over the page, put the form inside a [Dialog](/components/dialog/).
- For the routes and the help line around a form (“Forgot your password?”, “Need help signing in?”), use [Form link, divider and support note](/components/form-note/).

## Rules

- Keep this order inside `.hs-field`: `.hs-label`, `.hs-control`, `.hs-hint`, `.hs-field-error`. In a `.hs-fieldset` the hint follows `.hs-fieldset__options`.
- Never use a placeholder as the label. Every control keeps a visible `<label for>`. A placeholder may only show an example value: `placeholder="3.75"`.
- Mark the optional fields with `<span class="hs-optional">(optional)</span>` inside the label. Leave the required fields unmarked.
- Keep a hint to one short line. An autofill list or the phone keyboard can cover a long hint.
- Write each error as the fix: “Enter your GPA as a number from 0 to 5, for example 3.75.” Set `aria-invalid="true"` on the control and put the `.hs-field-error` under it.
- After a failed submit, add the form alert (`.hs-callout.hs-callout--danger.hs-form-alert`) at the top, with one link per error to the field `id`.
- Password field: `.hs-field.hs-password` holds `.hs-password__input`, the toggle (shipped `hidden`), `.hs-meter` and `.hs-password__rules`. Each rule is `<li data-rule="length|letter|digit" data-min="N">`; the rules are the site’s `HsUser\PasswordRules`. Load `password.js`.
- Give every toggle `aria-controls` with the input `id`. The script reads it first.
- Toggle: one pattern on every password field, the eye / eye-off icon button inside the field. Put both SVGs in the button, `.hs-password__icon--show` then `.hs-password__icon--hide`; CSS shows one by `aria-pressed`. Never write a text toggle (“Show” / “Hide”).
- Leave the meter and the rules off a password the member types to prove who they are (`autocomplete="current-password"`). They belong to a new password only (`autocomplete="new-password"`).

## Accessibility

- Give every control a `<label for>`, and every group a `fieldset` with a `legend.hs-label`. Link the hint and the error to the control with `aria-describedby`.
- Contrast on white: label ink 13.0:1, hint slate 6.2:1, placeholder slate 6.2:1, error danger 5.4:1. The control border is slate 500 at 3.7:1 (WCAG 1.4.11 asks 3:1). Disabled text is exempt.
- Focus is the shared gold outline and navy ring, and the border turns navy. Control text stays 16px, so iOS does not zoom on focus.
- Checkboxes and radios are native, in navy through `accent-color`: the browser keeps the checked, indeterminate and forced-colours states. The 40px row and the whole label are the tap target.
- The form alert takes `role="alert"` and `tabindex="-1"`. Move focus to it after submit.
- Password: the toggle is a button with `aria-pressed` and `aria-controls`; its `aria-label` flips between “Show password” and “Hide password”. Its hit area is 44px square, and it takes the shared focus style. The icon is slate 6.2:1, navy on hover; both SVGs are `aria-hidden`. Without `password.js` the toggle stays hidden, the field stays a plain password input, and the rules stay visible. The meter length repeats its colour, and a hidden `aria-live` label says “Strength: Fair”. Transitions stop under `prefers-reduced-motion: reduce`.

## Variants

### Field with a hint

The hint is 14px slate, under the control. Link it with `aria-describedby`.

```html
<div class="hs-field">
  <label class="hs-label" for="ph-b">Phone</label>
  <input class="hs-control" id="ph-b" type="tel" autocomplete="tel" aria-describedby="ph-b-hint">
  <p class="hs-hint" id="ph-b-hint">Include the country code, for example +1 for the United States.</p>
</div>
```

### Input, select, textarea, error, disabled

`aria-invalid="true"` gives a 2px danger edge; `.hs-field-error` under the control says what to fix. `--short` 10rem, `--medium` 20rem.

```html
<form class="hs-form" action="#" novalidate>
  <div class="hs-field">
    <label class="hs-label" for="role">Role</label>
    <input class="hs-control" id="role" name="role" type="text" autocomplete="organization-title">
  </div>
  <div class="hs-field">
    <label class="hs-label" for="month">Start month <span class="hs-optional">(optional)</span></label>
    <select class="hs-control hs-control--medium" id="month" name="month">
      <option value="">Month</option>
      <option>September</option>
      <option>October</option>
    </select>
  </div>
  <div class="hs-field">
    <label class="hs-label" for="gpa">GPA</label>
    <input class="hs-control hs-control--short" id="gpa" name="gpa" type="text" inputmode="decimal" value="4.3" aria-invalid="true" aria-describedby="gpa-error">
    <p class="hs-field-error" id="gpa-error">Enter your GPA as a number from 0 to 5, for example 3.75.</p>
  </div>
  <div class="hs-field">
    <label class="hs-label" for="about">About <span class="hs-optional">(optional)</span></label>
    <textarea class="hs-control" id="about" name="about" aria-describedby="about-hint"></textarea>
    <p class="hs-hint" id="about-hint">A few sentences about your goals. Members and schools see this on your profile.</p>
  </div>
  <div class="hs-field">
    <label class="hs-label" for="email">Email</label>
    <input class="hs-control" id="email" type="email" value="avery.example@example.com" disabled>
  </div>
</form>
```

### Checkbox, radio and fieldset

Native 20px box in navy. The row is 40px tall and the label is the tap target. In a fieldset the hint follows the options.

```html
<fieldset class="hs-fieldset" aria-describedby="status-hint">
  <legend class="hs-label">Educational or professional status</legend>
  <div class="hs-fieldset__options">
    <div class="hs-check">
      <input class="hs-check__input" id="st-ug" type="checkbox" name="status" value="undergraduate" checked>
      <label class="hs-check__label" for="st-ug">Undergraduate student</label>
    </div>
    <div class="hs-check">
      <input class="hs-check__input" id="st-gr" type="checkbox" name="status" value="graduate">
      <label class="hs-check__label" for="st-gr">Graduate student</label>
    </div>
    <div class="hs-check">
      <input class="hs-check__input" id="st-pro" type="checkbox" name="status" value="professional">
      <label class="hs-check__label" for="st-pro">Working professional</label>
    </div>
  </div>
  <p class="hs-hint" id="status-hint">Check all that apply.</p>
</fieldset>

<fieldset class="hs-fieldset">
  <legend class="hs-label">Preferred contact</legend>
  <div class="hs-fieldset__options">
    <div class="hs-check">
      <input class="hs-check__input" id="ct-email" type="radio" name="contact" value="email" checked>
      <label class="hs-check__label" for="ct-email">Email</label>
    </div>
    <div class="hs-check">
      <input class="hs-check__input" id="ct-phone" type="radio" name="contact" value="phone">
      <label class="hs-check__label" for="ct-phone">Phone</label>
    </div>
  </div>
</fieldset>
```

### Form alert

A danger callout at the top of a form that failed, with one link per error. It takes `role="alert"` and focus after submit.

```html
<div class="hs-callout hs-callout--danger hs-form-alert" role="alert" tabindex="-1">
  <svg class="hs-callout__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5.5"/><path d="M12 16.4v.1"/></svg>
  <div class="hs-callout__body">
    <p class="hs-callout__title">Fix 2 fields to save your academic record</p>
    <ul class="hs-form-alert__list">
      <li><a href="#gpa">Enter your GPA as a number from 0 to 5, for example 3.75.</a></li>
      <li><a href="#school">Enter your school.</a></li>
    </ul>
  </div>
</div>
```

### Password field

Type in it. The strength meter is one brand bar: a third danger, two thirds warning, full success; a hidden live label says the word. The rules tick when met. Needs `password.js`.

```html
<div class="hs-field hs-password">
  <label class="hs-label" for="new-pass">Password</label>
  <div class="hs-password__input">
    <input class="hs-control" id="new-pass" name="pass" type="password" autocomplete="new-password" aria-describedby="new-pass-rules">
    <button class="hs-password__toggle" type="button" aria-label="Show password" aria-pressed="false" aria-controls="new-pass" hidden>
      <svg class="hs-password__icon hs-password__icon--show" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg><svg class="hs-password__icon hs-password__icon--hide" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10.6 5.1A10.4 10.4 0 0 1 12 5c6.4 0 10 7 10 7a17.6 17.6 0 0 1-2.6 3.5M6.6 6.6C3.7 8.5 2 12 2 12s3.6 7 10 7a9.9 9.9 0 0 0 5.4-1.6"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/><path d="M3 3l18 18"/></svg>
    </button>
  </div>
  <div class="hs-meter">
    <span class="hs-meter__fill"></span>
    <span class="hs-meter__label hs-visually-hidden" aria-live="polite"></span>
  </div>
  <ul class="hs-password__rules" id="new-pass-rules">
    <li class="hs-password__rule" data-rule="length" data-min="8">8+ characters</li>
    <li class="hs-password__rule" data-rule="letter" data-min="1">1 letter</li>
    <li class="hs-password__rule" data-rule="digit" data-min="1">1 number</li>
  </ul>
</div>
```

### Password field, sign-in

A password the member already has: the eye toggle, no meter, no rules. Without `password.js` the toggle stays hidden and the field works as a plain password input. Decided in [round 4](/decisions/components-round-4.html): one toggle pattern on every password field.

```html
<div class="hs-field hs-password">
  <label class="hs-label" for="signin-pass">Password</label>
  <div class="hs-password__input">
    <input class="hs-control" id="signin-pass" name="pass" type="password" autocomplete="current-password">
    <button class="hs-password__toggle" type="button" aria-label="Show password" aria-pressed="false" aria-controls="signin-pass" hidden>
      <svg class="hs-password__icon hs-password__icon--show" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg><svg class="hs-password__icon hs-password__icon--hide" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10.6 5.1A10.4 10.4 0 0 1 12 5c6.4 0 10 7 10 7a17.6 17.6 0 0 1-2.6 3.5M6.6 6.6C3.7 8.5 2 12 2 12s3.6 7 10 7a9.9 9.9 0 0 0 5.4-1.6"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/><path d="M3 3l18 18"/></svg>
    </button>
  </div>
</div>
```
