CwMultiSelect

Same look and behavior as CwDropdown, but with multi-selection. The bound value is an array of { id, label } entries — easy to send straight to a backend or render as chips. Options can also be grouped by tagging each one with a group key (string or number) and passing a groups array that supplies each group's heading label and render order. A search field appears at the top of the open dropdown by default — typing filters by option label or group label (so typing a group name reveals every option in it). Set dropdownHeight to change the listbox max-height, or pass searchable={false} to hide the filter.

Bound value (Default)

[]

Bound value (Grouped — by site #)

[]
CwMultiSelect example Copy code
<script lang="ts">
	import { CwMultiSelect } from '@cropwatchdevelopment/cwui';

	const options = [
		{ label: 'Apple', value: 'apple' },
		{ label: 'Banana', value: 'banana' },
		{ label: 'Cherry', value: 'cherry' }
	];

	let selected = $state<{ id: string; label: string }[]>([]);
</script>

<CwMultiSelect
	options={options}
	label="Fruit"
	placeholder="Choose one or more…"
	bind:value={selected}
/>

<!-- selected => [{ id: 'apple', label: 'Apple' }, ...] -->
Grouped options example Copy code
<script lang="ts">
	import { CwMultiSelect } from '@cropwatchdevelopment/cwui';

	// Each option carries a `group` key (string or number).
	const options = [
		{ label: 'Apple', value: 'apple', group: 'pome' },
		{ label: 'Pear', value: 'pear', group: 'pome' },
		{ label: 'Cherry', value: 'cherry', group: 'stone' },
		{ label: 'Peach', value: 'peach', group: 'stone' },
		{ label: 'Mango', value: 'mango', group: 'tropical' }
	];

	// Groups define render order and the visible heading label.
	const groups = [
		{ value: 'pome', label: 'Pome Fruits' },
		{ value: 'stone', label: 'Stone Fruits' },
		{ value: 'tropical', label: 'Tropical' }
	];

	let selected = $state<{ id: string; label: string }[]>([]);
</script>

<CwMultiSelect
	{options}
	{groups}
	label="Fruit by category"
	bind:value={selected}
/>

<!-- Group keys can also be numbers, e.g. site IDs.
	 Options whose `group` key isn't in `groups` render ungrouped at the end. -->
Search + dropdownHeight example Copy code
<script lang="ts">
	import { CwMultiSelect } from '@cropwatchdevelopment/cwui';

	let selected = $state<{ id: string; label: string }[]>([]);
</script>

<!-- `dropdownHeight` overrides the listbox max-height (default 15rem).
	 The search field is on by default — typing filters by option label
	 OR group label, so typing a group name reveals every option in it. -->
<CwMultiSelect
	{options}
	{groups}
	label="Sensors"
	placeholder="Pick sensors…"
	dropdownHeight="24rem"
	searchPlaceholder="Filter by sensor or site…"
	bind:value={selected}
/>

<!-- Opt out of the search field with `searchable={false}`. -->
Documentation Upgrade

Start here

CwMultiSelect mirrors CwDropdown but allows multiple options to be selected at once. The bound `value` is an array of `{ id, label }` entries, ready to send to a backend or render as chips elsewhere in your UI.

How to think about it

  1. Build the option list Each option needs a `label` and `value`. Add `disabled` to make an item visible but not selectable.
  2. Bind an array `value` is an array of `{ id, label }`. Use `bind:value` if the parent owns the selection state, or `onchange` if you only need a callback.
  3. Toggle to select / deselect Clicking an option (or pressing Enter/Space while focused in the listbox) toggles its membership in the array. The list stays open so users can pick more than one.
  4. Clear or remove individual chips A "Clear all" action appears in the listbox while items are selected (`clearable` defaults to true). Individual chips can be dismissed by clicking the small × on the trigger.

Props and callbacks

APITypeDetails
options Required
Array<{ label: string; value: string; disabled?: boolean }>Items shown in the listbox.
value

Default: []

Array<{ id: string; label: string }>Selected entries. Bind this when the parent owns state.
label
stringVisible field label.
placeholder

Default: Select...

stringText shown when nothing is selected.
maxVisibleChips

Default: 3

numberHow many chips to render in the trigger before collapsing into "+N more". Ignored when `showAllSelectedItems` is true.
showAllSelectedItems

Default: false

booleanRender every selected chip in the trigger and let it wrap. Overrides `maxVisibleChips`.
clearable

Default: true

booleanShow a "Clear all" action inside the listbox when at least one item is selected.
clearLabel

Default: 'Clear all'

stringLabel used for the "Clear all" action.
required

Default: false

booleanMarks the hidden native input as required for form validation when nothing is selected.
disabled

Default: false

booleanPrevents opening the list, removing chips, or changing the value.
error
stringValidation message rendered below the field.
onchange
(value: Array<{ id: string; label: string }>) => voidCalled whenever the selection array changes.
name / autocomplete
stringOptional native form attributes forwarded to the hidden input. The hidden input value is the comma-joined list of ids.

Copy-paste examples

These snippets intentionally show the full public API surface the live demo relies on.

Controlled multi-select

The bound value is an array of `{ id, label }` entries — easy to render or persist.

Controlled multi-select Copy code
<script lang="ts">
	import { CwMultiSelect } from '@cropwatchdevelopment/cwui';

	const options = [
		{ label: 'Apple', value: 'apple' },
		{ label: 'Banana', value: 'banana' },
		{ label: 'Dragonfruit', value: 'dragonfruit' }
	];

	let selected = $state<{ id: string; label: string }[]>([]);
</script>

<CwMultiSelect
	options={options}
	label="Fruit"
	placeholder="Choose one or more..."
	bind:value={selected}
/>
Pre-selected values + onchange callback

Seed the array with existing selections and listen for changes.

Pre-selected values + onchange callback Copy code
<script lang="ts">
	const siteOptions = [
		{ label: 'North greenhouse', value: 'north' },
		{ label: 'South greenhouse', value: 'south' },
		{ label: 'East greenhouse', value: 'east' }
	];

	let sites = $state([
		{ id: 'north', label: 'North greenhouse' }
	]);

	function handleChange(value: { id: string; label: string }[]) {
		console.log('Selected sites:', value.map((v) => v.id));
	}
</script>

<CwMultiSelect
	options={siteOptions}
	name="sites"
	label="Deployment sites"
	bind:value={sites}
	onchange={handleChange}
/>