The Select
component is a powerful and flexible dropdown component. It supports both single and multiple selection modes, provides builtāin filtering capabilities, and renders a hidden native <select>
element to ensure full accessibility. Under the hood, it leverages the Listbox and Popover components to power its interactive behavior.
import { Select } from '@frontile/forms';
A simple single-select dropdown with static string options:
Selected:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Select } from '@frontile/forms';
const options = ['Option 1', 'Option 2', 'Option 3'];
export default class BasicSingleSelect extends Component {
@tracked selectedKeys: string[] = [];
onSelectionChange = (keys: string[]) => {
this.selectedKeys = keys;
};
<template>
<Select
@placeholder='Select an option'
@items={{options}}
@selectedKeys={{this.selectedKeys}}
@onSelectionChange={{this.onSelectionChange}}
/>
<p>Selected: {{this.selectedKeys}}</p>
</template>
}
Configure the Select
component for multiple selection mode. You can supply the options as objects (each with a key
and label
):
Selected:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Select } from '@frontile/forms';
const fruits = [
{ key: 'apple', label: 'Apple' },
{ key: 'banana', label: 'Banana' },
{ key: 'cherry', label: 'Cherry' },
{ key: 'date', label: 'Date' }
];
export default class MultipleSelectExample extends Component {
@tracked selectedKeys: string[] = [];
onSelectionChange = (keys: string[]) => {
this.selectedKeys = keys;
};
<template>
<Select
@selectionMode='multiple'
@placeholder='Select fruits'
@items={{fruits}}
@selectedKeys={{this.selectedKeys}}
@onSelectionChange={{this.onSelectionChange}}
>
<:item as |o|>
<o.Item>
{{o.label}}
</o.Item>
</:item>
</Select>
<p>Selected: {{this.selectedKeys}}</p>
</template>
}
Enable filtering so users can quickly search through the options. Filtering only applies when options are provided via the @items
argument.
Selected:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Select } from '@frontile/forms';
const countries = [
'Argentina',
'Brazil',
'Canada',
'Denmark',
'Egypt',
'France'
];
export default class FilterableSelectExample extends Component {
@tracked selectedKeys: string[] = [];
onSelectionChange = (keys: string[]) => {
this.selectedKeys = keys;
};
<template>
<Select
@isFilterable={{true}}
@placeholder='Select a country'
@items={{countries}}
@selectedKeys={{this.selectedKeys}}
@onSelectionChange={{this.onSelectionChange}}
/>
<p>Selected: {{this.selectedKeys}}</p>
</template>
}
When using the @items
argument, you can pass items as either primitives (strings or numbers) or objects. The Select
component automatically extracts a key and label for each item using a flexible approach:
Key Extraction:
The component first checks for a key
property. If not present, it will look for an id
property. If neither is available, it falls back to the string representation of the item.
Label Extraction:
For the label, the component checks in order for label
, value
, name
, or title
. If none of these properties exist, it uses the string representation of the item.
This supports common object shapes such as:
{ key: 'apple', label: 'Apple' }
{ key: 'apple', value: 'Apple' }
{ id: 'user-1', name: 'John Doe', email: 'john@example.com' }
Customize the display of each option by yielding the item to a block. This is useful when you need to add extra information (such as a description or icon) alongside the label.
Selected:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Select } from '@frontile/forms';
export default class CustomUserSelect extends Component {
@tracked selectedKeys: string[] = [];
onSelectionChange = (keys: string[]) => {
this.selectedKeys = keys;
};
<template>
<Select
@isFilterable={{true}}
@placeholder='Select a user'
@items={{users}}
@selectedKeys={{this.selectedKeys}}
@onSelectionChange={{this.onSelectionChange}}
>
<:item as |o|>
<o.Item>
<div class='flex items-center space-x-4'>
<img
src='{{o.item.avatar}}'
alt='{{o.item.name}}'
class='w-10 h-10 rounded-full'
/>
<div>
<div class='font-medium text-default-900'>{{o.item.name}}</div>
<div class='text-sm text-default-500'>{{o.item.email}}</div>
</div>
</div>
</o.Item>
</:item>
</Select>
<p class='mt-4'>Selected: {{this.selectedKeys}}</p>
</template>
}
const users = [
{
id: 'john-doe',
name: 'John Doe',
email: 'john.doe@example.com',
avatar: 'https://i.pravatar.cc/150?img=1'
},
{
id: 'jane-smith',
name: 'Jane Smith',
email: 'jane.smith@example.com',
avatar: 'https://i.pravatar.cc/150?img=2'
},
{
id: 'alice-johnson',
name: 'Alice Johnson',
email: 'alice.johnson@example.com',
avatar: 'https://i.pravatar.cc/150?img=3'
},
{
id: 'bob-brown',
name: 'Bob Brown',
email: 'bob.brown@example.com',
avatar: 'https://i.pravatar.cc/150?img=4'
},
{
id: 'charlie-davis',
name: 'Charlie Davis',
email: 'charlie.davis@example.com',
avatar: 'https://i.pravatar.cc/150?img=5'
}
];
You can also define options directly inside the component using block syntax (referred to here as declarative items). In this case, filtering is not available. This example also demonstrates the use of the @disabledKeys
and @allowEmpty
options. When @allowEmpty
is enabled, clicking a selected item will toggle its selection state (i.e. deselect it).
Selected:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { array } from '@ember/helper';
import { Select } from '@frontile/forms';
export default class DeclarativeItemsSelect extends Component {
@tracked selectedKeys: string[] = [];
onSelectionChange = (keys: string[]) => {
this.selectedKeys = keys;
};
<template>
<Select
@placeholder='Select...'
@onSelectionChange={{this.onSelectionChange}}
@selectedKeys={{this.selectedKeys}}
@disabledKeys={{array 'item-3' 'item-4'}}
@allowEmpty={{true}}
as |l|
>
<l.Item @key='item-1'>Item 1</l.Item>
<l.Item @key='item-2'>Item 2</l.Item>
<l.Item @key='item-3'>Item 3</l.Item>
<l.Item @key='item-4'>Item 4</l.Item>
<l.Item @key='item-5'>Item 5</l.Item>
</Select>
<p>Selected: {{this.selectedKeys}}</p>
</template>
}
The built-in clear button can be enabled using the @isClearable
flag. This allows users to reset the selection easily.
Selected:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Select } from '@frontile/forms';
const colors = ['Red', 'Green', 'Blue'];
export default class ClearableSelectExample extends Component {
@tracked selectedKeys: string[] = [];
onSelectionChange = (keys: string[]) => {
this.selectedKeys = keys;
};
<template>
<Select
@placeholder='Select a color'
@items={{colors}}
@selectedKeys={{this.selectedKeys}}
@onSelectionChange={{this.onSelectionChange}}
@isClearable={{true}}
/>
<p>Selected: {{this.selectedKeys}}</p>
</template>
}
Display a loading spinner in place of the dropdown icon by enabling the @isLoading
flag. This is useful when data is being fetched asynchronously.
Selected:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Select } from '@frontile/forms';
const sizes = ['Small', 'Medium', 'Large'];
export default class LoadingSelectExample extends Component {
@tracked selectedKeys: string[] = [];
onSelectionChange = (keys: string[]) => {
this.selectedKeys = keys;
};
<template>
<Select
@placeholder='Select a size'
@items={{sizes}}
@selectedKeys={{this.selectedKeys}}
@onSelectionChange={{this.onSelectionChange}}
@isLoading={{true}}
/>
<p>Selected: {{this.selectedKeys}}</p>
</template>
}
This example demonstrates how to use the :startContent
, :endContent
, and :emptyContent
blocks to customize the selectās trigger appearance and the empty state when no results match the filter. Here, the select is filterable, so the empty content is shown when there are no matching options.
Selected:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Select } from '@frontile/forms';
const fruits = ['Apple', 'Banana', 'Cherry', 'Date'];
export default class CustomContentBlocksSelect extends Component {
@tracked selectedKeys: string[] = [];
onSelectionChange = (keys: string[]) => {
this.selectedKeys = keys;
};
<template>
<Select
@isFilterable={{true}}
@placeholder='Search fruits...'
@items={{fruits}}
@selectedKeys={{this.selectedKeys}}
@onSelectionChange={{this.onSelectionChange}}
>
<:startContent>
<span class='mr-2'>š</span>
</:startContent>
<:endContent>
<span class='ml-2'>ā¼</span>
</:endContent>
<:emptyContent>
<div class='p-2 text-center text-gray-500'>
No fruits found.
</div>
</:emptyContent>
</Select>
<p>Selected: {{this.selectedKeys}}</p>
</template>
}
The NativeSelect
component is a lightweight version of the select component that renders a native HTML <select>
element. This is especially useful when you need maximum accessibility or want to rely on the browserās native behavior.
It supports most of the same features as the custom Select
component, such as passing an array of items, managing selection state, and handling selection changes.
By using NativeSelect
directly, you can simplify your markup while still benefiting from Frontileās APIs.
Selected:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { NativeSelect } from '@frontile/forms';
const options = ['Option 1', 'Option 2', 'Option 3'];
export default class NativeSelectExample extends Component {
@tracked selectedKeys: string[] = [];
onSelectionChange = (keys: string[]) => {
this.selectedKeys = keys;
};
<template>
<NativeSelect
@placeholder='Select an option'
@items={{options}}
@selectedKeys={{this.selectedKeys}}
@onSelectionChange={{this.onSelectionChange}}
/>
<p class='mt-4'>Selected: {{this.selectedKeys}}</p>
</template>
}
Element: HTMLDivElement
Name | Type | Default | Description |
---|---|---|---|
allowEmpty
|
boolean
|
- | |
appearance
|
enum
|
'default'
|
The appearance of each item |
backdrop
|
enum
|
- | |
backdropTransition
|
Object
|
- | |
blockScroll
|
boolean
|
true
|
Whether scrolling should be blocked when the select dropdown is open. |
classes
|
SlotsToClasses<'base' | 'input' | 'innerContainer' | 'startContent' | 'endContent' | 'icon' | 'placeholder' | 'listbox' | 'clearButton' | 'emptyContent'>
|
- | Custom classes to style different slots within the select component. |
closeOnEscapeKey
|
boolean
|
true
|
Whether to close when the escape key is pressed |
closeOnItemSelect
|
boolean
|
true
|
Whether the select should close upon selecting an item. |
closeOnOutsideClick
|
boolean
|
true
|
Whether to close when the area outside (the backdrop) is clicked |
description
|
string
|
- | |
didClose
|
function
|
- | |
disabledKeys
|
Array
|
- | |
disableFocusTrap
|
boolean
|
true
|
Whether the focus trap should be disabled when the dropdown is open. |
disableTransitions
|
boolean
|
false
|
Disable css transitions |
endContentPointerEvents
|
enum
|
'none'
|
Controls pointer-events property of endContent. Defauled to `none` to pass the click event to the input. If your content needs to capture events, consider adding `pointer-events-auto` class to that element only. |
errors
|
enum
|
- | |
filter
|
function
|
- | Function to filter the items in the select. The default implementation performs a case-insensitive search. |
flipOptions
|
{ mainAxis?: boolean; crossAxis?: boolean; fallbackPlacements?: Placement[]; fallbackStrategy?: 'bestFit' | 'initialPlacement'; fallbackAxisSideDirection?: 'none' | 'start' | 'end'; ... 5 more ...; boundary?: Boundary; }
|
- | |
focusTrapOptions
|
unknown
|
{ clickOutsideDeactivates: true, allowOutsideClick: true }
|
Focus trap options |
hideEmptyContent
|
boolean
|
false
|
If true, hides the empty content when there are no options available. |
id
|
string
|
- | The unique identifier for the select component. |
inputSize
|
enum
|
- | Defines the input size of the select. |
intent
|
enum
|
- | The intent of each item |
isClearable
|
boolean
|
false
|
Whether to include a clear button in the select component. If enabled, this allows users to clear the selection. This option ignores the `allowEmpty` setting. |
isDisabled
|
boolean
|
- | Whether the select should be disabled, preventing user interaction. |
isFilterable
|
boolean
|
false
|
Allows filtering of the items in the select dropdown. If true, a search input is displayed for filtering. |
isInvalid
|
boolean
|
- | |
isLoading
|
boolean
|
- | If true, the select will show a loading spinner instead of the dropdown icon. |
isRequired
|
boolean
|
- | |
items
|
Array
|
- | |
label
|
string
|
- | |
middleware
|
Array
|
- | |
name
|
string
|
- | The name attribute for the select component, useful for form submissions. |
offsetOptions
|
enum
|
5
|
|
onAction
|
function
|
- | |
onSelectionChange
|
function
|
- | |
placeholder
|
string
|
- | The placeholder text displayed when no option is selected. |
placement
|
enum
|
'bottom-start'
|
Placement of the menu when open |
popoverSize
|
enum
|
'trigger'
|
Defines the size of the popover dropdown. - 'sm': Small - 'md': Medium - 'lg': Large 'trigger': Same size as the trigger |
renderInPlace
|
boolean
|
false
|
Whether to render in place or in the specified/default destination |
selectedKeys
|
Array
|
- | |
selectionMode
|
enum
|
'single'
|
Determines the selection mode of the select component. - 'single': Only one item can be selected at a time. - 'multiple': Allows multiple selections. |
shiftOptions
|
{ mainAxis?: boolean; crossAxis?: boolean; rootBoundary?: RootBoundary; elementContext?: ElementContext; altBoundary?: boolean; padding?: Padding; limiter?: { ...; }; boundary?: Boundary; }
|
- | |
startContentPointerEvents
|
enum
|
'auto'
|
Controls pointer-events property of startContent. If you want to pass the click event to the input, set it to `none`. |
strategy
|
enum
|
'absolute'
|
|
target
|
enum
|
- | The target where to render the portal. There are 3 options: 1) `Element` object, 2) element id, 3) portal target name. For element id, string must be prefixed with `#`. If no value is passee in, we will render to the closest unnamed portal target, parent portal or `document.body`. |
transition
|
Object
|
{name: 'overlay-transition--scale'}
|
The transition to be used in the Modal. |
transitionDuration
|
number
|
200
|
Duration of the animation |
Name | Type | Default | Description |
---|---|---|---|
item
*
|
Array
|
- | |
default
*
|
Array
|
- | |
startContent
*
|
Array
|
- | Content to display at the **beginning** of the select component. This can be an icon, a label, or any custom UI element. Example: A search icon or a custom label. |
endContent
*
|
Array
|
- | Content to display at the **end** of the select component. This can be an icon, a button, or any custom UI element. Example: A clear button or a dropdown arrow. |
emptyContent
*
|
Array
|
- | The content to display when there are no available options. If `hideEmptyContent` argument is true, this content will not be shown. |
Name | Type | Default | Description |
---|
Element: <span class="hljs-built_in">Array</span>
Name | Type | Default | Description |
---|---|---|---|
allowEmpty
|
boolean
|
- | |
classes
|
SlotsToClasses<'base' | 'input' | 'innerContainer' | 'startContent' | 'endContent' | 'icon'>
|
- | |
description
|
string
|
- | |
disabledKeys
|
Array
|
- | |
endContentPointerEvents
|
enum
|
'none'
|
Controls pointer-events property of endContent. Defauled to `none` to pass the click event to the input. If your content needs to capture events, consider adding `pointer-events-auto` class to that element only. |
errors
|
enum
|
- | |
id
|
string
|
- | |
isInvalid
|
boolean
|
- | |
isRequired
|
boolean
|
- | |
items
|
Array
|
- | |
label
|
string
|
- | |
name
|
string
|
- | |
onAction
|
function
|
- | |
onItemsChange
|
function
|
- | |
onSelectionChange
|
function
|
- | |
placeholder
|
string
|
- | Placeholder text used when `allowEmpty` is set to `true`. |
selectedKeys
|
Array
|
- | |
selectionMode
|
enum
|
- | |
size
|
enum
|
- | |
startContentPointerEvents
|
enum
|
'auto'
|
Controls pointer-events property of startContent. If you want to pass the click event to the input, set it to `none`. |
Name | Type | Default | Description |
---|---|---|---|
item
*
|
Array
|
- | |
default
*
|
Array
|
- | |
startContent
*
|
Array
|
- | |
endContent
*
|
Array
|
- |
Element: HTMLOptionElement
Name | Type | Default | Description |
---|---|---|---|
key
*
|
string
|
- | |
manager
*
|
ListManager
|
- | |
textValue
|
string
|
- |
Name | Type | Default | Description |
---|---|---|---|
default
*
|
Array
|
- | |
selectedIcon
*
|
Array
|
- | |
start
*
|
Array
|
- | |
end
*
|
Array
|
- |