Hacktron Design System

Select

A dropdown for picking one or more values from a list.

<template>  <Select :items="[{ value: 'a', label: 'Apple' }]" model-value="a" placeholder="Fruits" /></template>

Props

Name Type Default
items({ type: 'label'; label: string } | { value: string; label: string; description?: string; icon?: string; sublabel?: string; disabled?: boolean })[]-
placeholderstring-
iconstring-
multiplebooleanfalse
size'md' | 'sm' | 'xs''md'
errorbooleanfalse
selectedLabelstring-
triggerLabelstring-
loadingbooleanfalse
disabledbooleanfalse
triggerSurfacestring-
modelValuestring | string[]-

Slots

Name Description
leadingContent before the trigger's value, e.g. a status dot. The spinner replaces it while `loading`.
defaultSingle mode: replaces the trigger's value text, inside the value span (takes precedence over `triggerLabel`), e.g. to color it or show a value missing from `items`.
item-leadingScoped `{ item }`. Replaces an option row's leading icon (e.g. a status dot). Not rendered for section-header entries.
item-labelScoped `{ item }`. Replaces an option row's label (e.g. a status badge that carries its own text). Not rendered for section-header entries.

Guidelines

Do

  • Use this when the user must pick from a list that's too long to show all at once, or when screen space is limited.
  • Use multiple when more than one option can be selected - the trigger keeps showing placeholder as a fixed label, with a small count badge appended once anything's selected.
  • When that placeholder is an instruction ("Select repositories"), pass selected-label with the field's name ("Repositories"): once anything is selected the trigger reads the name beside the count instead of the instruction.
  • Use the #item-leading="{ item }" slot to replace an option's leading icon with custom content, such as a status dot. Without the slot, each item's own icon renders as usual. #item-label="{ item }" replaces the label the same way, for a badge that carries its own text.
  • Group related options with a { type: 'label', label: '...' } section header entry - but only once there are 2+ groups to tell apart.
  • Inside a UFormField, a validation error puts the field on the error surface by itself. Pass error for a state the form doesn't know; the trigger takes Input's red wash and border either way.
  • Use #leading for content before the trigger's value (a status dot) and loading while a picked value saves: the spinner replaces the leading content and the trigger is disabled until it settles.
  • Fill the default slot to render the trigger's value yourself, e.g. a label for a value that isn't a pickable option (a finding's "Closed" status).
  • Write the placeholder by where the picker sits: in a filter or toolbar with no visible label, the bare field name ("Repository"); in a form under a label, "Select a/an x" ("Select a channel"). Once something is picked it shows the value, or with multiple the field name and the count (selected-label).
  • For a toolbar picker with no visible label, pass trigger-label with the field's name: the trigger reads "Scan type: All" once something is picked, and the placeholder alone while nothing is. Keep an aria-label too.
  • Always pass a placeholder when the picker can start empty: there's no built-in default.
  • Don't use this for a short, fully-visible list of mutually-exclusive options - use RadioGroup instead.
  • Don't use this for independent on/off choices - use Checkbox.
  • Don't use single mode when the user needs to return to "nothing selected" - a single-mode Select can only swap its value, never clear it. Use Combobox (picking the selected option again clears it) or add an explicit "Any" option.
  • Don't add a section header to a single, ungrouped list - a lone { type: 'label' } entry has nothing to distinguish it from, so it just adds noise above the options.
  • Don't pass :ui - it would replace the whole design-system look (trigger, panel and option rows), so the type-check rejects it. Select and Combobox triggers share one resting surface with the secondary Button, and every picker shares one option-row style. Don't restyle the trigger through class either: the one sanctioned exception is trigger-surface, for a picker that renders as a badge (the severity picker).
  • Don't use an example name ("e.g. acme/web"), an ellipsis, or a generic "Select something" as the placeholder: an example reads as already picked, and a generic one doesn't say what to pick.

Examples

Variants

Multiple select

Selecting more than one item at once, shown as a count.

<template>  <Select    v-model="selected"    :items="[      { value: 'apple', label: 'Apple' },      { value: 'banana', label: 'Banana' },      { value: 'blueberry', label: 'Blueberry' },      { value: 'grapes', label: 'Grapes' },      { value: 'pineapple', label: 'Pineapple' },    ]"    multiple    placeholder="Fruits"  /></template>

Sizes

Sizes

The three trigger height steps - xs, sm, and md.

<template>  <Select v-model="selected" :items="items" size="xs" placeholder="Fruits" />  <Select v-model="selected" :items="items" size="sm" placeholder="Fruits" />  <Select v-model="selected" :items="items" size="md" placeholder="Fruits" /></template>

With icons and slots

With icon

A leading icon on the trigger for extra context.

<template>  <Select    v-model="selected"    :items="[      { value: 'open', label: 'Open' },      { value: 'true_positive', label: 'Triaged' },      { value: 'resolved', label: 'Resolved' },      { value: 'accepted_risk', label: 'Accepted Risk' },      { value: 'false_positive', label: 'False Positive' },    ]"    icon="i-lucide-circle-dot"    placeholder="Status"  /></template>

Composition

Multiple sections

Items grouped under more than one section header.

<template>  <Select    v-model="selected"    :items="[      { type: 'label', label: 'Fruits' },      { value: 'apple', label: 'Apple' },      { value: 'banana', label: 'Banana' },      { type: 'label', label: 'Vegetables' },      { value: 'carrot', label: 'Carrot' },      { value: 'potato', label: 'Potato' },    ]"    placeholder="Ingredients"  /></template>

With sublabel

A monospace value shown inline next to each item's own label.

<template>  <Select    v-model="selected"    :items="[      { value: 'apple', label: 'Apple', sublabel: 'AAPL' },      { value: 'banana', label: 'Banana', sublabel: 'BNNA' },      { value: 'blueberry', label: 'Blueberry', sublabel: 'BLBY' },    ]"    placeholder="Fruits"  /></template>