The page navigation is complete. You may now navigate the page content as you wish.
Skip to main content

Primitives

Elements used to compose form fields.

We use Form Primitives as the building blocks for the “field” and “group” controls. While we recommend using our pre-defined “field” and “group” controls because they provide built-in accessibility support, you can use the Form Primitives to implement custom layouts or controls as necessary.

More details on how to assemble form components in larger form patterns can be found in the form patterns documentation.

  • The label is the text associated with the form control
  • The helper text is an optional text used to help understand what the field is intended for
  • The error is the message shown in case of failed validation of the field
  • The indicator marks a field as "Required" or "Optional"
  • The legend is the label associated with a fieldset
  • The field is the generic container for control, label, helper text and error messaging
  • The fieldset is the generic container to group multiple fields with label, helper text, and error messaging
  • The character count optionally displays the number of characters entered in a field, the maximum or minimum number of characters allowed, or a custom message communicating the relationship between the count and the maximum or minimum length.
    • A character count belongs only in fields that accept text values (Text Input, Textarea, Masked Input).

How to use this component

The ready-made fields — Text Input, Select, Textarea, Checkbox, Radio, Toggle — already assemble a label, helper text, an error and the required/optional indicator around their control. Reach for the primitives on this page only when you need a field those don't cover.

Most of the primitives are a single element with a single class, so you write them yourself and the design system only supplies the class name. Four of them need code, and those are tags:

| | | | - | - | | <i80-form-field> | wires one control to its label, helper text and error, generating the shared id | | <i80-form-fieldset> | the same for a group of fields, with a legend | | <i80-form-error> | an error block, with its icon and one or more messages | | <i80-form-character-count> | counts what a control holds, and updates as the user types |

The label

A label is a <label class="i80-form-label"> with a for attribute naming the control's id. Giving it an id of label-<control id> matches what the rest of the system generates, but nothing depends on it.

<label class="i80-form-label" for="my-control" id="label-my-control">My label</label>
<input type="text" id="my-control" name="my-control" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular">

When the control must be filled in, follow the text with the Required indicator:

<label class="i80-form-label" for="my-required-control" id="label-my-required-control">My label&nbsp;<div
    class="i80-badge i80-badge--size-small i80-badge--type-filled i80-badge--color-neutral i80-form-indicator"
    aria-hidden="true"><div class="i80-badge__text">Required</div></div></label>
<input type="text" id="my-required-control" name="my-required-control" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular" required>

When it may be left empty, follow it with the Optional indicator:

<label class="i80-form-label" for="my-optional-control" id="label-my-optional-control">My label
  <span class="i80-form-indicator i80-form-indicator--optional">(Optional)</span></label>
<input type="text" id="my-optional-control" name="my-optional-control" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular">

In a field you don't write either by hand: <i80-form-field>, <i80-form-fieldset> and the ready-made fields all render the indicator from a required or optional attribute.

A label can hold more than text. The typographic style applies to the whole label; laying out what is inside it is yours to do.

Accessibility alert

The <label> is tied to the <input>, <select> or <textarea> by its for attribute, which makes it interactive. Never put a link or a button inside it: nested interactive elements cannot be reached with assistive technology.

<label class="i80-form-label" for="my-structured-control" id="label-my-structured-control">
  <span>Some text</span>
  <i80-badge size="small" color="highlight">Some badge</i80-badge>
</label>
<input type="text" id="my-structured-control" name="my-structured-control" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular">

Helper text

Helper text is a <div class="i80-form-helper-text">. Give it an id and name that id in the control's aria-describedby, so the description is read out with the field:

This is some helper text
<label class="i80-form-label" for="helped-control" id="label-helped-control">My label</label>
<div class="i80-form-helper-text" id="helper-text-helped-control">This is some helper text</div>
<input type="text" id="helped-control" name="helped-control" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular"
  aria-describedby="helper-text-helped-control">

It can hold more than text too. The style applies to the container; anything nested inside may need styling of its own.

Accessibility alert

Interactive elements inside a description (linked through aria-describedby) are not announced as interactive by screen readers — only their text is read. Add a screen-reader-only note saying the help text contains a link and needs keyboard exploration.

Some text with a link, or some formatted code or a strong message. This description contains a link; use your keyboard to explore it.
<label class="i80-form-label" for="linked-control" id="label-linked-control">My label</label>
<div class="i80-form-helper-text" id="helper-text-linked-control">
  Some text with a <i80-link-inline href="/components/link/inline">link</i80-link-inline>, or
  <code class="i80-text i80-typography-code-200 i80-font-weight-regular">some formatted code</code>
  or a <strong>strong message</strong>.
  <span class="sr-only">This description contains a link; use your keyboard to explore it.</span>
</div>
<input type="text" id="linked-control" name="linked-control" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular"
  aria-describedby="helper-text-linked-control">

The character count

<i80-form-character-count for="..."> counts the value of the control with that id and says how much room is left. It follows the control as the user types, and announces the change politely. Point the control's aria-describedby at character-count-<control id>.

<label class="i80-form-label" for="count-default" id="label-count-default">Summary</label>
<input type="text" id="count-default" name="summary" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular"
  aria-describedby="character-count-count-default">
<i80-form-character-count for="count-default"></i80-form-character-count>

To guide the user towards an upper limit, set max-length. This does not stop anyone typing past it; to actually limit the value, set the native maxlength attribute on the control as well.

<label class="i80-form-label" for="count-max" id="label-count-max">Summary</label>
<input type="text" id="count-max" name="summary" maxlength="10" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular"
  aria-describedby="character-count-count-max">
<i80-form-character-count for="count-max" max-length="10"></i80-form-character-count>

To guide the user towards a lower limit, set min-length:

<label class="i80-form-label" for="count-min" id="label-count-min">Summary</label>
<input type="text" id="count-min" name="summary" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular"
  aria-describedby="character-count-count-min">
<i80-form-character-count for="count-min" min-length="3"></i80-form-character-count>

When the value has to fall in a range, set both:

<label class="i80-form-label" for="count-range" id="label-count-range">Summary</label>
<input type="text" id="count-range" name="summary" maxlength="10" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular"
  aria-describedby="character-count-count-range">
<i80-form-character-count for="count-range" min-length="3" max-length="10"></i80-form-character-count>

Wording the count yourself

The message attribute replaces the wording. {currentLength}, {minLength}, {maxLength}, {remaining} (max-length minus the current length) and {shortfall} (min-length minus the current length) are filled in:

<label class="i80-form-label" for="count-custom" id="label-count-custom">Summary</label>
<input type="text" id="count-custom" name="summary" maxlength="20" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular"
  aria-describedby="character-count-count-custom">
<i80-form-character-count for="count-custom" max-length="20"
  message="{remaining} characters remaining"></i80-form-character-count>

The error

<i80-form-error for="..."> renders the error block with its icon. The for attribute names the control's id and gives the block an id of error-<control id>, which is what the control's aria-describedby should point at.

This is a simple error message
<label class="i80-form-label" for="failing-control" id="label-failing-control">My label</label>
<input type="text" id="failing-control" name="my-control" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular i80-form-text-input--is-invalid"
  aria-describedby="error-failing-control" aria-invalid="true">
<i80-form-error for="failing-control">This is a simple error message</i80-form-error>

When there is more than one thing wrong, write one <p> per message and each becomes a message in the block — which is what a server rendering a list of validation errors produces anyway:

First error message

Second error message

<label class="i80-form-label" for="failing-control-2" id="label-failing-control-2">My label</label>
<input type="text" id="failing-control-2" name="my-control" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular i80-form-text-input--is-invalid"
  aria-describedby="error-failing-control-2" aria-invalid="true">
<i80-form-error for="failing-control-2">
  <p>First error message</p>
  <p>Second error message</p>
</i80-form-error>

The required and optional indicator

The Required indicator is a small neutral badge, hidden from screen readers because required on the control already says it:

<div class="i80-badge i80-badge--size-small i80-badge--type-filled i80-badge--color-neutral i80-form-indicator"
  aria-hidden="true"><div class="i80-badge__text">Required</div></div>

The Optional indicator is a short note:

(Optional)
<span class="i80-form-indicator i80-form-indicator--optional">(Optional)</span>

The legend

A group of fields is a <fieldset class="i80-form-group"> with a <legend class="i80-form-legend">. <i80-form-fieldset> renders both from a legend attribute:

<i80-form-fieldset legend="My legend">
  <i80-checkbox label="Selection #1" name="selection" value="1"></i80-checkbox>
  <i80-checkbox label="Selection #2" name="selection" value="2"></i80-checkbox>
</i80-form-fieldset>

Add required when the group must be answered:

<i80-form-fieldset legend="My legend" required>
  <i80-checkbox label="Selection #1" name="selection" value="1"></i80-checkbox>
  <i80-checkbox label="Selection #2" name="selection" value="2"></i80-checkbox>
</i80-form-fieldset>

Add optional when it may be skipped:

<i80-form-fieldset legend="My legend" optional>
  <i80-checkbox label="Selection #1" name="selection" value="1"></i80-checkbox>
  <i80-checkbox label="Selection #2" name="selection" value="2"></i80-checkbox>
</i80-form-fieldset>

A legend can hold more than text. For that, write the <fieldset> and the <legend> yourself with their classes; the style applies to the container, and laying out what is inside it is yours to do.

Some text Some badge
<fieldset class="i80-form-group i80-form-group--layout-vertical">
  <legend class="i80-form-legend i80-form-group__legend">
    <span>Some text</span>
    <i80-badge size="small" color="highlight">Some badge</i80-badge>
  </legend>
  <div class="i80-form-group__control-fields-wrapper">
    <i80-checkbox label="Selection #1" name="structured" value="1"></i80-checkbox>
    <i80-checkbox label="Selection #2" name="structured" value="2"></i80-checkbox>
  </div>
</fieldset>

Assembling a field

It's unlikely you need this: use a ready-made field where one fits. If you do need it and something is missing, get in touch.

<i80-form-field> takes the control you write inside it and builds the field around it. It generates one id, puts it on the control, points the label's for at it and links the helper text, the character count and the error through aria-describedby. Depending on the situation you may want only the label, or the label and the helper text, while the error is usually conditional on what the user entered.

Laying out what you put inside the control container is yours to do.

This is the error
<i80-form-field label="This is the label" helper-text="This is the helper text" required>
  <input type="email" name="email" value="jane.doe@email.com" class="i80-form-text-input i80-typography-body-200 i80-font-weight-regular i80-form-text-input--is-invalid">
  <i80-form-error>This is the error</i80-form-error>
</i80-form-field>

Assembling a fieldset

It's unlikely you need this: a Checkbox, Radio or Toggle group already is one. Use <i80-form-fieldset> when the fields in the group are not all of one kind.

<i80-form-fieldset> renders the <fieldset>, its legend, the helper text and the error, and puts the fields you write inside into the group's container. layout="horizontal" puts them in a row. Each field inside picks up the group's contextual class, and the group's helper text and error are linked to every control in it.

<i80-form-fieldset legend="This is the legend" helper-text="This is the helper text"
  error="This is the error" layout="horizontal" required>
  <i80-checkbox label="Selection #1" name="selection" value="1" checked></i80-checkbox>
  <i80-checkbox label="Selection #2" name="selection" value="2"></i80-checkbox>
</i80-form-fieldset>

Attributes

Four of the primitives are <i80-*> elements; the rest are a single element with a single class, which you write yourself. See Web Components for loading i80.js, and the reference for these examples running.

Form field

<i80-form-field> builds the field around the control you write inside it.

Attribute Values Default Notes
label text Rendered as a &lt;label&gt; whose for points at the control.
helper-text text Linked to the control with aria-describedby.
error text One error message; marks the control invalid. For several messages, put an &lt;i80-form-error&gt; inside instead.
required boolean Adds the Required indicator after the label.
optional boolean Adds the Optional indicator after the label.
invalid boolean Marks the control invalid without showing a message.
layout vertical · flag vertical flag puts the control beside the label, the way a checkbox sits.
aria-describedby id list Merged with the ids the element links itself.

The first <input>, <select>, <textarea> or contenteditable element inside is the control: it gets the generated id, and the label, helper text, character count and error are linked to it. Everything else you write inside stays in the control container, laid out by you. An <i80-form-error> or <i80-form-character-count> child is moved out of the control container into its own place.

Form fieldset

<i80-form-fieldset> renders the <fieldset> and its legend around several fields.

Attribute Values Default Notes
legend text Rendered as a &lt;legend&gt;.
helper-text text Linked to every control in the group.
error text One error message, shown after the fields.
required boolean Adds the Required indicator after the legend.
optional boolean Adds the Optional indicator after the legend.
layout vertical · horizontal vertical Whether the fields stack or sit in a row.
name text Given to every radio inside that has no name of its own.

Form error

<i80-form-error> renders the error block with its icon.

Attribute Values Default Notes
for id The control's id; the block's own id becomes error-&lt;that id&gt;, which is what the control's aria-describedby should point at.
message text A single message, for when the tag has no content.

Each <p> or <div> you write inside is one message; text and inline tags are a single message.

Form character count

<i80-form-character-count> counts what a control holds and updates as the user types.

Attribute Values Default Notes
for id The control to count. The element's own id becomes character-count-&lt;that id&gt;. Inside an &lt;i80-form-field&gt; it is set for you.
max-length number The upper limit to guide towards. It does not stop anyone typing past it; set the native maxlength on the control for that.
min-length number The lower limit to guide towards.
message text Your own wording. {currentLength}, {minLength}, {maxLength}, {remaining} and {shortfall} are filled in.
value text Count this instead of the control's value.

Form label

A label is a <label class="i80-form-label"> with for naming the control's id. An id of label-<control id> matches what the elements generate, but nothing depends on it.

Form helper text

Helper text is a <div class="i80-form-helper-text">. Give it an id and name that id in the control's aria-describedby.

Form legend

A legend is a <legend class="i80-form-legend"> inside a <fieldset class="i80-form-group i80-form-group--layout-vertical">, whose fields sit in a <div class="i80-form-group__control-fields-wrapper">. <i80-form-fieldset> renders all of that for you.

Form indicator

The Required indicator is a small neutral badge: <div class="i80-badge i80-badge--size-small i80-badge--type-filled i80-badge--color-neutral i80-form-indicator" aria-hidden="true"> around a <div class="i80-badge__text">Required</div>. It is hidden from screen readers, because required on the control already says it.

The Optional indicator is a <span class="i80-form-indicator i80-form-indicator--optional">(Optional)</span>.

Inside a field or a fieldset you write neither: the required and optional attributes render them.

Contextual classes

A primitive inside a field or a group also carries a class saying where it sits, which is what sets the spacing between the parts. The elements add these themselves; add them when you write a part by hand.

Inside a field Inside a group
i80-form-field__label i80-form-group__legend
i80-form-field__helper-text i80-form-group__helper-text
i80-form-field__control i80-form-group__control-fields-wrapper
i80-form-field__character-count i80-form-group__control-field (on each field)
i80-form-field__error i80-form-group__error

Label

  • We recommend keeping labels clear and concise, about 1-3 words. They should not consist of full sentences.
  • All inputs need a visible label to let users know what the purpose of the input is. This is required for accessibility by WCAG 3.2.2

Helper Text

  • Use helper text to give the user extra details about the data you’re asking them to input, e.g., formatting requirements such as MM-DD-YYYY.

Errors

  • Error messages should provide the user with enough context to guide them in resolving the error.
  • Keep labels and legends short and to the point (ie. "Select one option")
  • Avoid overt politeness; don’t use "please" or "thank you" in your messaging.
  • If an input is errored out, it is visually identifiable with associated error messaging by WCAG 3.3.1.
  • If an input is errored out, the messaging needs to be clear for the user to rectify the issue by WCAG 3.3.3.
  • If the data inputted is related to legal or financial information, the user is given an opportunity to review, confirm or rectify the information before finalizing its submission by WCAG 3.3.4
  • Use roles or properties to help identify statuses so that they are accessible by assistive technologies without receiving focus by WCAG 4.1.3.

Conformance rating

Conditionally conformant

The form primitives aren’t conformant on their own; they become conformant in the field they are part of. Most of them are one element with one class that you write by hand — a label is <label class="i80-form-label"> — so it is your markup that has to be right: the label’s for must be the control’s id, and helper text, a character count and an error must be listed in the control’s aria-describedby.

<i80-form-field> does that wiring for you. It generates one id, puts it on the control you wrote inside it, points the label at it and adds the helper text, the <i80-form-error> and the <i80-form-character-count> to aria-describedby (and sets aria-invalid when there is an error) — the same wiring the ready-made fields get, for a control they don’t cover.

Known issues

The helper text and the error are announced through aria-describedby, and a screen reader reads only their text. A link inside them is not announced as a link, so a keyboard user has no way to reach it from the announcement. Put links in the surrounding page content rather than in a label, helper text or error message.

Applicable WCAG Success Criteria

This section is for reference only, some descriptions have been truncated for brevity. This component intends to conform to the following WCAG Success Criteria:

  • 1.3.1 Info and Relationships (Level A):
    Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.
  • 1.3.2 Meaningful Sequence (Level A):
    When the sequence in which content is presented affects its meaning, a correct reading sequence can be programmatically determined.
  • 1.3.4 Orientation (Level AA):
    Content does not restrict its view and operation to a single display orientation, such as portrait or landscape.
  • 1.4.1 Use of Color (Level A):
    Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.
  • 1.4.10 Reflow (Level AA):
    Content can be presented without loss of information or functionality, and without requiring scrolling in two dimensions.
  • 1.4.11 Non-text Contrast (Level AA):
    The visual presentation of the following have a contrast ratio of at least 3:1 against adjacent color(s): user interface components; graphical objects.
  • 1.4.12 Text Spacing (Level AA):
    No loss of content or functionality occurs by setting all of the following and by changing no other style property: line height set to 1.5; spacing following paragraphs set to at least 2x the font size; letter-spacing set at least 0.12x of the font size, word spacing set to at least 0.16 times the font size.
  • 1.4.3 Minimum Contrast (Level AA):
    The visual presentation of text and images of text has a contrast ratio of at least 4.5:1
  • 1.4.4 Resize Text (Level AA):
    Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality.
  • 2.4.6 Headings and Labels (Level AA):
    Headings and labels describe topic or purpose.
  • 3.3.2 Labels or Instructions (Level A):
    Labels or instructions are provided when content requires user input.
  • 4.1.2 Name, Role, Value (Level A):
    For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies.

Support

If any accessibility issues have been found within this component, let us know by submitting an issue.

0.1.0

Første i80-version (fork af upstream, se core/UPSTREAM.md).


Related