Components · Forms & selection
Multi-select
A control for picking several options at once: chosen values become removable pills inside a wrapping trigger, with a typeahead input and a checkable dropdown list. The trigger grows to fit its tags; the open list reuses the popover surface.
Preview
The trigger with tags and the open list, across the three themes.
Light
Design close
Research close
check Design
Engineering
Marketing
Dark
Design close
Research close
check Design
Engineering
Marketing
Accessibility
Design close
Research close
check Design
Engineering
Marketing
Spec
Values and token references straight from the Zetta spec.
multi-select-trigger
- backgroundColor
- {colors.surface-card}
- borderColor
- {colors.border-strong}
- borderWidth
- 1px
- borderRadius
- {rounded.base}
- minHeight
- 32px
- paddingX
- 14px
- paddingY
- 6px
- display
- flex
- flexWrap
- wrap
- alignItems
- center
- gap
- 6px
- cursor
- text
multi-select-trigger-hover
- borderColor
- {colors.border-strong}
- boxShadow
- {shadows.sm}
multi-select-trigger-focus
- borderColor
- {colors.border-focus}
- outline
- 2px solid {colors.border-focus}
- outlineOffset
- 2px
multi-select-trigger-invalid
- borderColor
- {colors.danger}
multi-select-trigger-invalid-focus
- borderColor
- {colors.danger}
- outline
- 2px solid {colors.danger}
- outlineOffset
- 2px
multi-select-trigger-disabled
- backgroundColor
- {colors.disabled-bg}
- borderColor
- {colors.disabled-border}
- cursor
- not-allowed
multi-select-tag
- backgroundColor
- {colors.surface-secondary}
- textColor
- {colors.ink}
- borderRadius
- {rounded.full}
- fontFamily
- Geist
- fontSize
- 12px
- fontWeight
- 400
- lineHeight
- 1.5
- paddingX
- 10px
- paddingY
- 2px
- display
- flex
- alignItems
- center
- gap
- 4px
multi-select-tag-remove
- iconSize
- 12px
- iconColor
- {colors.muted}
- cursor
- pointer
- borderRadius
- {rounded.full}
- padding
- 2px
multi-select-tag-remove-hover
- iconColor
- {colors.ink}
- backgroundColor
- {colors.surface-pressed}
multi-select-input
- fontFamily
- Geist
- fontSize
- 14px
- fontWeight
- 400
- textColor
- {colors.ink}
- placeholderColor
- {colors.placeholder}
- placeholderStyle
- italic
- flex
- 1
- minWidth
- 80px
- height
- 20px
- backgroundColor
- transparent
multi-select-content
- backgroundColor
- {colors.popover}
- textColor
- {colors.popover-foreground}
- borderColor
- {colors.hairline}
- borderWidth
- 1px
- borderRadius
- {rounded.base}
- padding
- 6px
- shadow
- {shadows.md}
multi-select-item
- textColor
- {colors.ink}
- fontFamily
- Geist
- fontSize
- 14px
- fontWeight
- 400
- borderRadius
- {rounded.base}
- paddingX
- 12px
- paddingY
- 10px
- cursor
- pointer
- display
- flex
- alignItems
- center
- gap
- 8px
multi-select-item-hover
- backgroundColor
- {colors.accent}
multi-select-item-selected
- textColor
- {colors.brand}
- fontWeight
- 500
- checkIconColor
- {colors.brand}
- checkIconSize
- 16px
multi-select-item-disabled
- textColor
- {colors.disabled-text}
- cursor
- not-allowed
multi-select-empty
- textColor
- {colors.muted}
- fontFamily
- Geist
- fontSize
- 14px
- textAlign
- center
- paddingX
- 12px
- paddingY
- 16px
Build with the skill
No package to install — hand this to your AI to generate the multi-select in your stack.
Implement the Zetta "Multi-select" component in this project's stack using the zetta-design-md skill.
- Apply the `multi-select` spec from the skill (variants: default, invalid, disabled).
- Use this project's own component conventions and framework idioms.
- Reference the Zetta design tokens (CSS variables) for every color, radius, and shadow — never hardcode values.
- Implement all states (hover, focus, active, disabled, and invalid where applicable) with a visible 2px focus ring.
- Honor the Zetta guardrails (brand never shifts hue, shadows for overlays only, etc.) and verify in Light, Dark, and Accessibility. Anatomy & rules
- Wrapping trigger (min-height 32px), pill tags on
surface-secondarywith a full radius and a removable icon, and a flexible typeahead input. The list marks selected items with abrandcheck. - Tags wrap to new rows as they accumulate; the trigger never truncates the selection silently.
Accessibility
- Each tag's remove control needs an accessible name and must be keyboard operable (Backspace from the input removes the last tag). The list follows the multi-select listbox pattern with
aria-multiselectable.