Components · Forms & selection
Multi-select
Combobox variant that supports selecting multiple values displayed as inline tags. Split from zetta-form-v1_4.md (removed in v1.5) in v1.4.
Preview
Design
Research
Add team…
Design
Engineering
Marketing
Research
Sales
Support
No teams match.
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Multi-select" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `multi-select` spec and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `multi-select` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
┌──────────────────────────────────────────┐
│ [Design ×] [Frontend ×] [type to search…]│ ← trigger (grows vertically)
└──────────────────────────────────────────┘
↓ open
┌──────────────────────────────────────────┐
│ ✓ Design ← selected (primary) │
│ Backend │ ← item
│ Frontend ✓ ← selected │
│ (No results) ← multi-select-empty │
└──────────────────────────────────────────┘
| State | Trigger border | Notes |
|---|---|---|
| Default (empty) | {colors.border-strong} |
Placeholder shown |
| Default (has values) | {colors.border-strong} |
Tags shown inline |
| Hover | {colors.border-strong} + shadow |
— |
| Focus / Open | {colors.border-focus} + glow |
Dropdown visible |
| Invalid | {colors.danger} |
Error state |
| Disabled | {colors.disabled-border} |
not-allowed, tags not removable |
When to use
- Selecting multiple values from a known list (tags, categories, assignees, skills).
- When selected items need to be visible and individually removable.
When not to use
Do
- Show a "No results" empty state when the search yields nothing.
- Support Backspace to remove the last tag when the input is empty.
- Limit
maxSelectedwhen there is a meaningful constraint.
Don't
- Don't allow duplicate selections.
- Don't allow free-text entry unless explicitly a "creatable" multi-select.
- Placeholder: "Select options…" or context-specific — "Add tags", "Assign members".
- Tag remove button
aria-label: "Remove [value]". - Empty state: "No options found." or "No results for '[query]'."
- Selected item in dropdown: show
checkicon prefix in{colors.primary}.
Keyboard
| Key | Behavior |
|---|---|
Tab |
Focus trigger |
Type |
Filter options in dropdown |
Down Arrow |
Move focus to first dropdown item |
Up / Down Arrow |
Navigate dropdown items |
Enter / Space |
Select / deselect focused item |
Escape |
Close dropdown |
Backspace (empty input) |
Remove last selected tag |
Accessibility
- ARIA role:
role="combobox"on trigger,aria-multiselectable="true"on listbox. aria-selected="true"on selected options in the listbox.- Each tag remove button:
aria-label="Remove [value]". aria-live="polite"region to announce additions and removals: "Design added. 3 items selected."aria-invalid="true"+aria-describedbyin error state.
Spec
Values and token references resolved 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
- {elevation.shadow.raised}
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
- {elevation.shadow.overlay}
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.primary}
- fontWeight
- 500
- checkIconColor
- {colors.primary}
- 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