CwListBox

Selectable list with left badge, label, and right-aligned count/icon. Matches the sidebar pattern. Use the arrow keys, Home and End to move between items.

Sidebar lists

Groups
Locations
Navigation

Last event: none yet

Disabled items & empty list

Status
Favourites

No favourites yet. Star a location to pin it here.

CwListBox example Copy code
<script lang="ts">
	import { CwListBox } from '$lib/index.js';
	import type { CwListBoxItem } from '$lib/index.js';

	const groups: CwListBoxItem<string>[] = [
		{ value: 'all', label: 'All groups', badge: 'ALL', badgeTone: 'secondary', endText: '196' },
		{ value: 'ungrouped', label: 'Ungrouped', badge: 'UNG', badgeTone: 'secondary', endText: '91' },
		{ value: 'seagaia', label: 'Seagaia', badge: 'SEA', badgeTone: 'success', endText: '41' },
		{ value: 'sa', label: 'SA', badge: 'SA', badgeTone: 'info', endText: '11' },
		{ value: 'kokokara', label: 'Kokokara', badge: 'KOK', badgeTone: 'warning', endText: '2' },
		{ value: 'toyota', label: 'Toyota', badge: 'TOY', badgeTone: 'danger', endText: '2' },
		{ value: 'tk-ebisu', label: 'TK-Ebisu', badge: 'TK-', badgeTone: 'primary', endText: '49' }
	];

	const locations: CwListBoxItem<string>[] = [
		{ value: 'all-loc', label: 'All locations', endText: '196' },
		{ value: 'miyazaki', label: 'Miyazaki House', endText: '7' },
		{ value: 'npo', label: 'NPO法人 東米良創生会', endText: '1' },
		{ value: 'pine1', label: 'パインテラス 1 階', endText: '26' },
		{ value: 'pine-b1', label: 'パインテラス地下 1 階', endText: '6' },
		{ value: 'sa-farm', label: 'SA西都農場', endText: '3' },
		{ value: 'kawagoe', label: '川越農産', endText: '3' },
		{ value: 'takama', label: '高間ハウス', endText: '3' },
		{ value: 'toyotama', label: 'とよたま 2 階', endText: '9' }
	];

	const navItems: CwListBoxItem<string>[] = [
		{ value: 'home', label: 'Home', endText: '↗' },
		{ value: 'locations', label: 'Locations', endText: '↗' },
		{ value: 'rules', label: 'Rules', endText: '↗' },
		{ value: 'reports', label: 'Reports', endText: '↗' },
		{ value: 'gateways', label: 'Gateways', endText: '↗' }
	];

	let selectedGroup = $state<string | null>('all');
	let selectedLocation = $state<string | null>('all-loc');
	let selectedNav = $state<string | null>('home');
</script>

<div class="demo-columns">
	<div class="demo-sidebar">
		<CwListBox
			heading="Groups"
			items={groups}
			bind:value={selectedGroup}
		/>

		<CwListBox
			heading="Locations"
			items={locations}
			bind:value={selectedLocation}
		/>
	</div>

	<div class="demo-sidebar">
		<CwListBox
			heading="Navigation"
			items={navItems}
			bind:value={selectedNav}
		/>

		<div class="demo-sidebar__footer">
			<p>Selected group: <strong>{selectedGroup}</strong></p>
			<p>Selected location: <strong>{selectedLocation}</strong></p>
		</div>
	</div>
</div>
API

Usage and API reference

CwListBox is an always-visible, single-select list with optional badges and trailing text. It is a proper ARIA listbox: one tab stop, arrow keys to move, Enter or Space to select.

How to think about it

  1. Name the list A visible `heading` also labels the listbox. Without one, pass `ariaLabel` so screen readers announce what the options are.
  2. Bind the value Use `bind:value` for the selected item value, or listen with `onselect` to receive the whole item.
  3. Plan for empty data Set `emptyText` so an empty list explains itself instead of rendering nothing.

Props and callbacks

APITypeDetails
items Required
CwListBoxItem<T>[]`{ value, label, badge?, badgeTone?, endText?, disabled? }` entries.
value

Default: null

T | nullBindable selected value.
heading
stringVisible heading that also labels the list.
ariaLabel

Default: 'Options'

stringAccessible name when there is no heading.
emptyText
stringMessage shown when `items` is empty.
onselect
(item: CwListBoxItem<T>) => voidCalled when an item is selected.
itemIcon / itemEnd
Snippet<[CwListBoxItem<T>]>Custom content on the left or right of each row.

Copy-paste examples

Each snippet is a complete, working example you can paste into a Svelte 5 component.

Region picker
Region picker Copy code
<script lang="ts">
	import { CwListBox } from '@cropwatchdevelopment/cwui';

	let region = $state<string | null>('as923');
</script>

<CwListBox
	heading="Frequency plan"
	bind:value={region}
	items={[
		{ value: 'as923', label: 'Asia 923 MHz', badge: 'AS', endText: '12 gateways' },
		{ value: 'eu868', label: 'Europe 868 MHz', badge: 'EU', endText: '4 gateways' }
	]}
	emptyText="No regions configured"
/>