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 <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.
<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:
<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.
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.
<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:
<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.
<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
<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.
<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
<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 <label> 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 <i80-form-error> 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 <legend>. |
|
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-<that id>, 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-<that id>. Inside an <i80-form-field> 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
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).