Field
A label, control, hint and error laid out together, the same way every time.
In Accessible first
See it live
npm run storybookWhen to use it
In Accessible first: Label, hint and error in the same place on every question.
- Use it when: Every form control that needs a label, a hint or an error.
- Not when: A control that is not part of a form, such as a search box in a toolbar.
Instead:
- Label: The label on its own.
Variants
What each variant is for in any design system, then what Accessible first decided and why. Each sample is the live story with that variant set.
verticalorientationUsed in Accessible firstWhat it is for
The label above the control.
In Accessible first
Every question: the label above the field, always visible.
horizontalorientationNot used in Accessible firstWhat it is for
The label beside the control.
In Accessible first
Labels stay above the field, visible while typing and in the same place on any screen.
responsiveorientationNot used in Accessible firstWhat it is for
The label above on narrow screens and beside on wide ones.
In Accessible first
One layout on every device, so people meet the same form every time.
How Accessible first uses it
Why it works this way
Field looks the way it does because of 4 answers about this product. Each card is one answer, in three steps: the answer, what it decided for Field, and what would move if it changed.
people.q21The answer
AAA where it applies; nobody is left out.
2So Field
- ✓uses vertical: Every question: the label above the field, always visible.
- –does not use horizontal: Labels stay above the field, visible while typing and in the same place on any screen.
Button: danger Button: focus Button: disabled Input group: inline-start Input group: inline-end Input group: block-start Input group: block-end3If it changed
and 15 answers that follow from it: A ratio, line heights in proportion, Neutrals and one brand ramp, Nothing, and 12 more.
tasks.q11The answer
People must finish the form.
2So Field
- ✓uses vertical: Every question: the label above the field, always visible.
Input group: inline-start3If it changed
and 6 answers that follow from it: Completion without errors, A task that fails, One sitting, and 3 more.
human.q21The answer
Nobody remembers the interface from last time.
2So Field
- –does not use responsive: One layout on every device, so people meet the same form every time.
Button: secondary Button: ghost Button: disabled Toggle: default3If it changed
and 1 answer that follow from it: Actions and status.
scenarios.q11The answer
Public services are used on any device, with any assistive technology.
2So Field
- –does not use responsive: One layout on every device, so people meet the same form every time.
3If it changed
No other answer follows from it.
Words
- Use common words. Explain any term you must use.
- One idea per sentence. Put the action first.
- Never blame the person.
- Use: Continue, Change, Start, You. Avoid: Invalid, Utilise, Submit, Error code.
The labels components use, from the voice tokens:
content.action.commit: Continuecontent.action.cancel: Go backcontent.action.destroy: Deletecontent.action.retry: Try againcontent.action.empty-action: Start a new application
See Voice and tone for the whole voice.
Accessibility and keyboard
Test before you ship:
- The label, hint and error are linked to the control, so they are read with it. undefined
Use it in code
npx open-ds add field
The component reads only component keys and semantic tokens, so it follows the brand and mode on <html>.
Tokens it reads
Field reads 30 tokens of its own, each an alias of a semantic token. Each row is one part: its own token, the semantic it aliases, and the value it ends at. Change a semantic and every component that reads it follows; override one of Field's tokens to change Field alone.
Accessible first decides what changes when you switch theme. Here a switch changes colors only, so the marked rows differ between light and dark; everything else holds steady.
| Part | Its token and the semantic it aliases | Value |
|---|---|---|
| Set | ||
| Gap | component.field.set.gapspace.gap.sectionvar(--field-set-gap) | space.48:3rem |
| Gap 2 | component.field.set.gap-2component-spacing-map.x3var(--field-set-gap-2) | space.20:1.25rem |
| Legend | ||
| Weight | component.field.legend.font-weighttype.label.font-weightvar(--field-legend-font-weight) | font.weight.700:700 |
| Text size | component.field.legend.font-sizetype.body.font-sizevar(--field-legend-font-size) | font.size.19:1.1875rem |
| Group | ||
| Gap | component.field.group.gapcomponent-spacing-map.x8var(--field-group-gap) | space.56:3.5rem |
| Gap 2 | component.field.group.gap-2component-spacing-map.x3var(--field-group-gap-2) | space.20:1.25rem |
| Gap 3 | component.field.group.gap-3space.gap.groupvar(--field-group-gap-3) | space.32:2rem |
| Field | ||
| Gap | component.field.gapcomponent-spacing-map.x3var(--field-gap) | space.20:1.25rem |
| Label invalid Changes with theme when invalid | component.field.label-invalidcolor.status.dangervar(--field-label-invalid) | Lightred.light.600:#C42B2BDarkred.dark.600:#F28B8B |
| Border checked Changes with theme when on | component.field.border-checkedcolor.action.primaryvar(--field-border-checked) | Lightblue.light.600:#0B4FC0Darkblue.dark.600:#8AB4F8 |
| Background checked Changes with theme when on | component.field.background-checkedcolor.action.primaryvar(--field-background-checked) | Lightblue.light.600:#0B4FC0Darkblue.dark.600:#8AB4F8 |
| Fill Changes with theme | component.field.backgroundcolor.surface.canvasvar(--field-background) | Lightgray.light.50:#F7F7F7Darkgray.dark.0:#0A0A0A |
| Padding, left and right | component.field.padding-xcomponent-spacing-map.x2var(--field-padding-x) | space.8:0.5rem |
| Gap 2 | component.field.gap-2space.gap.tightvar(--field-gap-2) | space.12:0.75rem |
| Content | ||
| Gap | component.field.content.gapcomponent-spacing-map.x2var(--field-content-gap) | space.8:0.5rem |
| Text | ||
| Gap | component.field.label.gapspace.gap.defaultvar(--field-label-gap) | space.16:1rem |
| Disabled opacity disabled when disabled | component.field.label.disabled-opacity-disabledopacity.disabledvar(--field-label-disabled-opacity-disabled) | opacity.50:0.5 |
| Corner | component.field.label.radiusradius.controlvar(--field-label-radius) | radius.6:0.375rem |
| Padding | component.field.label.paddingcomponent-spacing-map.x4var(--field-label-padding) | space.28:1.75rem |
| Text size | component.field.label.font-sizetype.body.font-sizevar(--field-label-font-size) | font.size.19:1.1875rem |
| Weight | component.field.label.font-weighttype.label.font-weightvar(--field-label-font-weight) | font.weight.700:700 |
| Color Changes with theme | component.field.labelcolor.text.mutedvar(--field-label) | Lightgray.light.500:#595959Darkgray.dark.500:#949494 |
| Description | ||
| Text size | component.field.description.font-sizetype.body.font-sizevar(--field-description-font-size) | font.size.19:1.1875rem |
| Weight | component.field.description.font-weighttype.body.font-weightvar(--field-description-font-weight) | font.weight.400:400 |
| Text Changes with theme | component.field.description.labelcolor.text.mutedvar(--field-description-label) | Lightgray.light.500:#595959Darkgray.dark.500:#949494 |
| Label 2 Changes with theme | component.field.description.label-2color.action.primaryvar(--field-description-label-2) | Lightblue.light.600:#0B4FC0Darkblue.dark.600:#8AB4F8 |
| Separator | ||
| Text size | component.field.separator.font-sizetype.body.font-sizevar(--field-separator-font-size) | font.size.19:1.1875rem |
| Error | ||
| Text size | component.field.error.font-sizetype.body.font-sizevar(--field-error-font-size) | font.size.19:1.1875rem |
| Weight | component.field.error.font-weighttype.body.font-weightvar(--field-error-font-weight) | font.weight.400:400 |
| Text Changes with theme | component.field.error.labelcolor.status.dangervar(--field-error-label) | Lightred.light.600:#C42B2BDarkred.dark.600:#F28B8B |
| From the system | ||
| Edge Changes with theme | color.border.default straight from the system, through --border | Lightgray.light.200 · #D6D6D6Darkgray.dark.100 · #222222 |