Skip to main content

Input group

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

In Dense data

See it live​

Start Storybook to see it live: npm run storybook

When to use it​

In Dense data: Search with an icon and a clear button.

  • 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 Dense data decided and why. Each sample is the live story with that variant set.

inline-startalignUsed in Dense data
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 Dense data

The search icon before the text.

inline-endalignUsed in Dense data
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 Dense data

The clear button after the text, for searches people repeat all day.

block-startalignNot used in Dense data
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 Dense data

Inputs stay one line high in toolbars and filter bars.

block-endalignNot used in Dense data
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 Dense data

Inputs stay one line high in toolbars and filter bars.

Why it works this way​

Input group looks the way it does because of 3 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 do icons do?iconography.roles
1The answer
Actions and status

Icons mark row actions and status.

2So Input group
  • ✓uses inline-start: The search icon before the text.
It also shapes Button: icon Empty: icon
3If it changed
1
Input group decision would reopen

No other answer follows from it.

How often do people repeat actions?tasks.q2
1The answer
Constantly

The same actions repeat many times an hour.

2So Input group
  • ✓uses inline-end: The clear button after the text, for searches people repeat all day.
It also shapes Button: ghost Sidebar: default Alert: default Toast: success Toggle: default
3If it changed
2
Input group decisions would reopen

and 11 answers that follow from it: Time on task, All day, Three, and 8 more.

How much is on one screen?content.q2
1The answer
A lot

Experts want many records on one screen.

2So Input group
  • –does not use block-start: Inputs stay one line high in toolbars and filter bars.
  • –does not use block-end: Inputs stay one line high in toolbars and filter bars.
It also shapes Button: default Button: compact Button: large Field: horizontal Sidebar: lg Toggle: default Toggle: sm Toggle: lg
3If it changed
2
Input group decisions would reopen

and 14 answers that follow from it: Quiet, Only the steps the screens need, Small, and 11 more.

Words​

  • Lead with the object, then the state: "Alert 2041 contained."
  • Use numbers, not words for numbers.
  • One verb per button. No "please", no exclamation marks.
  • Use: Investigate, Filter, Assign, Escalated, Resolved. Avoid: Oops, Awesome, Please, Simply.

The labels components use, from the voice tokens:

  • content.action.commit: Save
  • content.action.cancel: Cancel
  • content.action.destroy: Delete
  • content.action.retry: Retry
  • content.action.empty-action: Change filters

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

Dense data 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.2:0.125rem
Border
Changes with theme
component.input-group.bordercolor.border.strongvar(--input-group-border)Lightslate.light.500:#6B7585Darkslate.dark.400:#969FAD
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.12:0.75rem
Padding, topcomponent.input-group.padding-topspace.inset.control-xvar(--input-group-padding-top)space.12:0.75rem
Border 2
Changes with theme
component.input-group.border-2color.border.focusvar(--input-group-border-2)Lightviolet.light.600:#4B3FD9Darkviolet.dark.600:#A9A3F6
Focus ring
Changes with theme
component.input-group.focus-ringcolor.border.focusvar(--input-group-focus-ring)Lightviolet.light.600:#4B3FD9Darkviolet.dark.600:#A9A3F6
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.8:0.5rem
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.14:0.875rem
Weightcomponent.input-group.font-weighttype.label.font-weightvar(--input-group-font-weight)font.weight.500:500
Text
Changes with theme
component.input-group.labelcolor.text.mutedvar(--input-group-label)Lightslate.light.600:#535D6DDarkslate.dark.400:#969FAD
Disabled opacity disabled
when disabled
component.input-group.disabled-opacity-disabledopacity.disabledvar(--input-group-disabled-opacity-disabled)opacity.40:0.4
Icon sizecomponent.input-group.icon-sizesize.iconvar(--input-group-icon-size)size.16:1rem
Inline
Start padding leftcomponent.input-group.inline.start.padding-leftspace.inset.control-xvar(--input-group-inline-start-padding-left)space.12:0.75rem
End padding rightcomponent.input-group.inline.end.padding-rightspace.inset.control-xvar(--input-group-inline-end-padding-right)space.12:0.75rem
Block
Start padding xcomponent.input-group.block.start.padding-xspace.inset.control-xvar(--input-group-block-start-padding-x)space.12:0.75rem
Start padding topcomponent.input-group.block.start.padding-topspace.inset.control-xvar(--input-group-block-start-padding-top)space.12:0.75rem
Start padding top 2component.input-group.block.start.padding-top-2component-spacing-map.x3var(--input-group-block-start-padding-top-2)space.12:0.75rem
Start padding bottomcomponent.input-group.block.start.padding-bottomspace.inset.control-xvar(--input-group-block-start-padding-bottom)space.12:0.75rem
End padding xcomponent.input-group.block.end.padding-xspace.inset.control-xvar(--input-group-block-end-padding-x)space.12:0.75rem
End padding bottomcomponent.input-group.block.end.padding-bottomspace.inset.control-xvar(--input-group-block-end-padding-bottom)space.12:0.75rem
End padding bottom 2component.input-group.block.end.padding-bottom-2component-spacing-map.x3var(--input-group-block-end-padding-bottom-2)space.12:0.75rem
End padding topcomponent.input-group.block.end.padding-topspace.inset.control-xvar(--input-group-block-end-padding-top)space.12:0.75rem
Xs
Heightcomponent.input-group.xs.heightsize.control.compactvar(--input-group-xs-height)size.24:1.5rem
Gapcomponent.input-group.xs.gapspace.gap.tightvar(--input-group-xs-gap)space.4:0.25rem
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.32:2rem
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.2:0.125rem
Padding, left and rightcomponent.input-group.sm.padding-xcomponent-spacing-map.x3var(--input-group-sm-padding-x)space.12:0.75rem
Icon
Sm sizecomponent.input-group.icon.sm.sizesize.control.defaultvar(--input-group-icon-sm-size)size.32:2rem
Control
Padding, top and bottomcomponent.input-group.control.padding-yspace.inset.control-xvar(--input-group-control-padding-y)space.12:0.75rem
From the system
Edge
Changes with theme
color.border.default
straight from the system, through --border
Lightslate.light.200 · #DDE2EADarkslate.dark.100 · #262D38