Skip to main content

Input group

An input with addons inside its border: icons, text, buttons.

In Accessible first

See it live​

Start Storybook to see it live: npm run storybook

When to use it​

In Accessible first: A value with a unit or currency, with the label still above the field. Not for separate fields.

  • Use it when: A search with an icon and a clear button, a value with a unit or currency.
  • Not when: Controls that are separate fields (lay them out with field).

Instead:

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.

inline-startalignUsed in Accessible first
Start Storybook to see it live

What it is for

An addon before the text, on the same line: an icon or currency.

✓A search icon or a unit that comes first.
✕Something people press often; put it after the text.

In Accessible first

A currency sign before the amount, with the label still above the field.

inline-endalignUsed in Accessible first
Start Storybook to see it live

What it is for

An addon after the text: a unit, a clear button, a shortcut.

✓Clear, submit or a unit that follows the value.
✕A label that should be outside the field.

In Accessible first

A unit after the value, as a word people can read, with the label still above the field.

block-startalignNot used in Accessible first
Start Storybook to see it live

What it is for

An addon row above the text, inside the field.

✓A toolbar or a label row for a larger input.
✕Single-line fields in a toolbar.

In Accessible first

The label stays above the field, outside it, always visible.

block-endalignNot used in Accessible first
Start Storybook to see it live

What it is for

An addon row below the text, inside the field.

✓A counter or actions under a larger input.
✕Single-line fields in a toolbar.

In Accessible first

Hints and errors sit in the field layout, in the same place on every question.

Why it works this way​

Input group 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 Input group, and what would move if it changed.

What accessibility level do we commit to?people.q2
1The answer
AAA

AAA where it applies; nobody is left out.

2So Input group
  • ✓uses inline-start: A currency sign before the amount, with the label still above the field.
  • ✓uses inline-end: A unit after the value, as a word people can read, with the label still above the field.
  • –does not use block-start: The label stays above the field, outside it, always visible.
  • –does not use block-end: Hints and errors sit in the field layout, in the same place on every question.
It also shapes Button: danger Button: focus Button: disabled Field: vertical Field: horizontal
3If it changed
4
Input group decisions would reopen

and 15 answers that follow from it: A ratio, line heights in proportion, Neutrals and one brand ramp, Nothing, and 12 more.

What is the main task?tasks.q1
1The answer
Complete a form

People must finish the form.

2So Input group
  • ✓uses inline-start: A currency sign before the amount, with the label still above the field.
It also shapes Field: vertical
3If it changed
1
Input group decision would reopen

and 6 answers that follow from it: Completion without errors, A task that fails, One sitting, and 3 more.

What do icons do?iconography.roles
1The answer
Actions and status

Icons support words; they never replace them.

2So Input group
  • ✓uses inline-end: A unit after the value, as a word people can read, with the label still above the field.
It also shapes Button: icon Toggle: outline
3If it changed
1
Input group decision would reopen

No other answer follows from it.

Which kinds of copy does it need?voice.roles
1The answer
Labels, messages and help

Hints and help text carry people through each step.

2So Input group
  • –does not use block-end: Hints and errors sit in the field layout, in the same place on every question.
3If it changed
1
Input group decision would reopen

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: Continue
  • content.action.cancel: Go back
  • content.action.destroy: Delete
  • content.action.retry: Try again
  • content.action.empty-action: Start a new application

See Voice and tone for the whole voice.

Accessibility and keyboard​

Test before you ship:

  • Addon buttons have names; icons that only decorate are hidden from screen readers. undefined

Use it in code​

npx open-ds add input-group

The component reads only component keys and semantic tokens, so it follows the brand and mode on <html>.

Tokens it reads​

Input group reads 37 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 Input group's tokens to change Input group alone.

Changes with theme

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.

PartIts token and the semantic it aliasesValue
Input group
Cornercomponent.input-group.radiusradius.controlvar(--input-group-radius)radius.6:0.375rem
Border
Changes with theme
component.input-group.bordercolor.border.strongvar(--input-group-border)Lightgray.light.500:#595959Darkgray.dark.500:#949494
Shadowcomponent.input-group.shadowelevation.cardvar(--input-group-shadow)shadow.none
Padding leftcomponent.input-group.padding-leftcomponent-spacing-map.x2var(--input-group-padding-left)space.8:0.5rem
Padding rightcomponent.input-group.padding-rightcomponent-spacing-map.x2var(--input-group-padding-right)space.8:0.5rem
Padding, bottomcomponent.input-group.padding-bottomspace.inset.control-xvar(--input-group-padding-bottom)space.24:1.5rem
Padding, topcomponent.input-group.padding-topspace.inset.control-xvar(--input-group-padding-top)space.24:1.5rem
Border 2
Changes with theme
component.input-group.border-2color.border.focusvar(--input-group-border-2)Lightblue.light.600:#0B4FC0Darkblue.dark.600:#8AB4F8
Focus ring
Changes with theme
component.input-group.focus-ringcolor.border.focusvar(--input-group-focus-ring)Lightblue.light.600:#0B4FC0Darkblue.dark.600:#8AB4F8
Border invalid
Changes with theme
when invalid
component.input-group.border-invalidcolor.status.dangervar(--input-group-border-invalid)Lightred.light.600:#C42B2BDarkred.dark.600:#F28B8B
Focus ring invalid
Changes with theme
when invalid
component.input-group.focus-ring-invalidcolor.status.dangervar(--input-group-focus-ring-invalid)Lightred.light.600:#C42B2BDarkred.dark.600:#F28B8B
Gapcomponent.input-group.gapspace.gap.defaultvar(--input-group-gap)space.16:1rem
Padding, top and bottomcomponent.input-group.padding-ycomponent-spacing-map.x2var(--input-group-padding-y)space.8:0.5rem
Text sizecomponent.input-group.font-sizetype.body.font-sizevar(--input-group-font-size)font.size.19:1.1875rem
Weightcomponent.input-group.font-weighttype.label.font-weightvar(--input-group-font-weight)font.weight.700:700
Text
Changes with theme
component.input-group.labelcolor.text.mutedvar(--input-group-label)Lightgray.light.500:#595959Darkgray.dark.500:#949494
Disabled opacity disabled
when disabled
component.input-group.disabled-opacity-disabledopacity.disabledvar(--input-group-disabled-opacity-disabled)opacity.50:0.5
Icon sizecomponent.input-group.icon-sizesize.iconvar(--input-group-icon-size)size.20:1.25rem
Inline
Start padding leftcomponent.input-group.inline.start.padding-leftspace.inset.control-xvar(--input-group-inline-start-padding-left)space.24:1.5rem
End padding rightcomponent.input-group.inline.end.padding-rightspace.inset.control-xvar(--input-group-inline-end-padding-right)space.24:1.5rem
Block
Start padding xcomponent.input-group.block.start.padding-xspace.inset.control-xvar(--input-group-block-start-padding-x)space.24:1.5rem
Start padding topcomponent.input-group.block.start.padding-topspace.inset.control-xvar(--input-group-block-start-padding-top)space.24:1.5rem
Start padding top 2component.input-group.block.start.padding-top-2component-spacing-map.x3var(--input-group-block-start-padding-top-2)space.20:1.25rem
Start padding bottomcomponent.input-group.block.start.padding-bottomspace.inset.control-xvar(--input-group-block-start-padding-bottom)space.24:1.5rem
End padding xcomponent.input-group.block.end.padding-xspace.inset.control-xvar(--input-group-block-end-padding-x)space.24:1.5rem
End padding bottomcomponent.input-group.block.end.padding-bottomspace.inset.control-xvar(--input-group-block-end-padding-bottom)space.24:1.5rem
End padding bottom 2component.input-group.block.end.padding-bottom-2component-spacing-map.x3var(--input-group-block-end-padding-bottom-2)space.20:1.25rem
End padding topcomponent.input-group.block.end.padding-topspace.inset.control-xvar(--input-group-block-end-padding-top)space.24:1.5rem
Xs
Heightcomponent.input-group.xs.heightsize.control.compactvar(--input-group-xs-height)size.48:3rem
Gapcomponent.input-group.xs.gapspace.gap.tightvar(--input-group-xs-gap)space.12:0.75rem
Padding, left and rightcomponent.input-group.xs.padding-xcomponent-spacing-map.x2var(--input-group-xs-padding-x)space.8:0.5rem
Sm
Heightcomponent.input-group.sm.heightsize.control.defaultvar(--input-group-sm-height)size.52:3.25rem
Gapcomponent.input-group.sm.gapcomponent-spacing-map.x2var(--input-group-sm-gap)space.8:0.5rem
Cornercomponent.input-group.sm.radiusradius.controlvar(--input-group-sm-radius)radius.6:0.375rem
Padding, left and rightcomponent.input-group.sm.padding-xcomponent-spacing-map.x3var(--input-group-sm-padding-x)space.20:1.25rem
Icon
Sm sizecomponent.input-group.icon.sm.sizesize.control.defaultvar(--input-group-icon-sm-size)size.52:3.25rem
Control
Padding, top and bottomcomponent.input-group.control.padding-yspace.inset.control-xvar(--input-group-control-padding-y)space.24:1.5rem
From the system
Edge
Changes with theme
color.border.default
straight from the system, through --border
Lightgray.light.200 · #D6D6D6Darkgray.dark.100 · #222222