@polarityio/pi-select
v1.0.4
Published
Feature-rich select/dropdown with single and multi-select, async search, and custom rendering
Downloads
561
Readme
Polarity Integration Component Library
Select Component
A feature-rich select/dropdown component supporting single and multi-select modes, synchronous and asynchronous search, custom option rendering, and intelligent dropdown positioning via Floating UI.
Installation
npm install @polarityio/pi-selectPeer Dependencies
- lit ^3.0.0
Usage
import '@polarityio/pi-select';Basic Single Select
import { html } from 'lit';
html`
<pi-select placeholder="Choose a fruit" .options=${['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']}></pi-select>
`;Multi-Select
import { html } from 'lit';
html`
<pi-select
multiple
placeholder="Select tags"
.options=${['Bug', 'Feature', 'Enhancement', 'Documentation']}
.selected=${['Bug', 'Feature']}
></pi-select>
`;Async Search
import { html } from 'lit';
html`
<pi-select
placeholder="Search users..."
search-placeholder="Type a name..."
search-message="Type to search for users"
loading-message="Searching..."
.search=${async (term) => {
const res = await fetch(`/api/users?q=${term}`);
return res.json();
}}
label-field="name"
search-field="name"
></pi-select>
`;Custom Option Rendering
import { html } from 'lit';
const select = document.querySelector('pi-select');
select.options = [
{ name: 'Alice', role: 'Admin', avatar: '/alice.png' },
{ name: 'Bob', role: 'Editor', avatar: '/bob.png' }
];
select.labelField = 'name';
select.searchField = 'name';
select.renderOption = (option) => html`
<div style="display: flex; align-items: center; gap: 8px;">
<img src="${option.avatar}" width="24" height="24" style="border-radius: 50%;" />
<div>
<div>${option.name}</div>
<small style="opacity: 0.7;">${option.role}</small>
</div>
</div>
`;Handling Events
const select = document.querySelector('pi-select');
select.addEventListener('pi-change', (e) => {
console.log('Selected:', e.detail.selected);
console.log('Previous:', e.detail.previous);
});
select.addEventListener('pi-search', (e) => {
console.log('Search term:', e.detail.term);
console.log('Results:', e.detail.results);
});
select.addEventListener('pi-open', () => console.log('Dropdown opened'));
select.addEventListener('pi-close', () => console.log('Dropdown closed'));API Reference
Properties
| Property | Type | Default | Description |
| -------------------- | ----------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
| options | T[] | [] | Array of option items to display in the dropdown |
| selected | T \| T[] \| undefined | undefined | Currently selected option(s). Array for multiple mode. |
| multiple | boolean | false | Enable multi-select mode |
| placeholder | string | '' | Placeholder text when no option is selected |
| disabled | boolean | false | Disable the entire select component |
| searchEnabled | boolean | true | Enable search/filter functionality |
| searchField | string \| undefined | undefined | Object property name to search within (for object options) |
| labelField | string \| undefined | undefined | Object property name to display as the option label |
| allowClear | boolean | false | Show a clear button to reset the selection |
| closeOnSelect | boolean \| undefined | undefined | Close dropdown after selection. Defaults to true in single mode, false in multiple mode. |
| searchPlaceholder | string | '' | Placeholder text for the search input |
| noMatchesMessage | string | 'No results found' | Message displayed when no options match the search |
| searchMessage | string | 'Type to search' | Message shown in async search mode before the first search |
| loadingMessage | string | 'Loading...' | Message displayed while loading async search results |
| highlightOnHover | boolean | true | Highlight options on mouse hover |
| matchTriggerWidth | boolean | true | Make dropdown width match the trigger width |
| triggerClass | string | '' | Additional CSS class(es) for the trigger element |
| dropdownClass | string | '' | Additional CSS class(es) for the dropdown element |
| triggerTabindex | number | 0 | Tab index for keyboard navigation |
| searchDebounce | number | 150 | Debounce delay in milliseconds for search filtering |
| loading | boolean | false | Indicates async search is in progress |
| search | (term: string) => Promise<T[]> | undefined | Async search function. When provided, enables async search mode. |
| matcher | (option: T, term: string) => number | undefined | Custom matcher function for filtering. Return -1 for no match, 0+ for match position. |
| renderOption | (option: T) => TemplateResult \| string | undefined | Custom render callback for option display in the dropdown |
| renderSelectedItem | (option: T) => TemplateResult \| string | undefined | Custom render callback for selected item display in the trigger |
Events
| Event Name | Detail | Description |
| ----------- | ---------------------------------------------------------------------- | -------------------------------------------------------- |
| pi-change | { selected: T \| T[] \| undefined, previous: T \| T[] \| undefined } | Fired when the selection changes |
| pi-open | {} | Fired when the dropdown opens |
| pi-close | {} | Fired when the dropdown closes |
| pi-search | { term: string, results: T[] } | Fired on search input with the term and filtered results |
| pi-focus | {} | Fired when the select gains focus |
| pi-blur | {} | Fired when the select loses focus |
Slots
| Slot Name | Description |
| ---------------- | --------------------------------------------------------------------- |
| before-options | Content displayed before the options list in the dropdown |
| after-options | Content displayed after the options list in the dropdown |
| loading | Custom loading message (replaces loadingMessage when provided) |
| no-matches | Custom no-matches message (replaces noMatchesMessage when provided) |
CSS Custom Properties
| Property | Default | Description |
| ---------------------------------------- | ----------------------------------------------- | -------------------------------------------------- |
| --pi-select-dropdown-z-index | 1000 | Dropdown z-index |
| --pi-select-line-height | 1.75em | Select line height |
| --pi-select-trigger-padding | 4px 16px 4px 8px | Trigger padding |
| --pi-select-border-color | var(--pi-color-border-element) | Border color |
| --pi-select-border-radius | var(--pi-size-radius-base) | Border radius |
| --pi-select-background | var(--pi-color-background-container-base) | Background color |
| --pi-select-text-color | var(--pi-color-font-primary) | Text color |
| --pi-select-disabled-bg | var(--pi-color-background-container-disabled) | Disabled background |
| --pi-select-placeholder-color | var(--pi-color-font-disabled) | Placeholder color |
| --pi-select-loading-icon-color | var(--pi-color-font-disabled) | Loading icon color |
| --pi-select-dropdown-border | 1px solid var(--pi-select-border-color) | Dropdown border |
| --pi-select-dropdown-box-shadow | var(--pi-shadow-sm) | Dropdown shadow |
| --pi-select-dropdown-margin | 2px 0 0 | Dropdown margin from trigger |
| --pi-select-option-padding | 4px 8px | Option padding |
| --pi-select-search-input-border-radius | 3px | Search input border radius |
| --pi-select-search-field-border | 1px solid var(--pi-select-border-color) | Search field border |
| --pi-select-visible-options | 7 | Maximum number of visible options before scrolling |
| --pi-select-highlight-bg | var(--pi-color-background-primary) | Highlighted option background |
| --pi-select-highlight-color | var(--pi-color-font-inverse) | Highlighted option text color |
| --pi-select-selected-bg | var(--pi-color-background-hover) | Selected option background |
| --pi-select-disabled-color | var(--pi-color-font-disabled) | Disabled option text color |
| --pi-select-tag-bg | var(--pi-color-background-secondary) | Multi-select tag background |
| --pi-select-tag-color | var(--pi-color-font-primary) | Multi-select tag text color |
| --pi-select-tag-border | 1px solid var(--pi-color-border-container) | Multi-select tag border |
| --pi-select-tag-border-radius | 3px | Multi-select tag border radius |
| --pi-select-tag-padding | 0 4px | Multi-select tag padding |
CSS Parts
| Part Name | Description |
| -------------------- | ----------------------------------------- |
| trigger | The select trigger/button area |
| selected-item | Selected item text (single mode) |
| placeholder | Placeholder text |
| clear-btn | Clear button |
| loading-icon | Loading spinner icon |
| status-icon | Dropdown arrow icon |
| tags-area | Area containing tags (multiple mode) |
| tag | Individual tag (multiple mode) |
| tag-label | Tag label text (multiple mode) |
| tag-remove | Tag remove button (multiple mode) |
| search-input | Search input field |
| icons | Icons container (multiple mode) |
| dropdown | Dropdown container |
| search | Search input container (single mode) |
| options-list | Options list container (role="listbox") |
| option | Individual option |
| option-highlighted | Highlighted option (keyboard/hover) |
| option-selected | Selected option |
| option-disabled | Disabled option |
| no-matches | No-matches message element |
| loading | Loading message element |
Public Methods
| Method | Description |
| ---------- | ------------------------------------------------------ |
| open() | Opens the dropdown. No-op if already open or disabled. |
| close() | Closes the dropdown and returns focus to the trigger. |
| toggle() | Toggles the dropdown between open and closed states. |
Keyboard Navigation
| Key | Behavior |
| ----------------- | ------------------------------------------------------ |
| ArrowDown | Open dropdown or move highlight to the next option |
| ArrowUp | Open dropdown or move highlight to the previous option |
| Home | Move highlight to the first option |
| End | Move highlight to the last option |
| Enter / Space | Select highlighted option or open dropdown |
| Escape | Close dropdown |
| Tab | Close dropdown and move focus |
| Backspace | Remove last selected tag (multiple mode, empty search) |
| (type-ahead) | Jump to matching option when dropdown is closed |
Theming
Customize the appearance using CSS custom properties. This component uses design tokens from @polarityio/pi-shared for consistent theming across the component library.
pi-select {
--pi-select-border-radius: 8px;
--pi-select-visible-options: 10;
--pi-select-highlight-bg: #3b82f6;
}
pi-select::part(trigger) {
min-height: 40px;
}
pi-select::part(tag) {
border-radius: 12px;
}License
MIT
