GitHub

Select

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

import { Select } from '@frontile/forms';

Basic Single Select

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>
}

Multiple Select

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>
}

Filterable Select

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>
}

Item Object Format

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' }

Custom Option Rendering

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'
  }
];

Declarative Items with Named Blocks

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>
}

Clearable Select

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>
}

Loading Select

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>
}

Select with Custom Input Content and Empty State

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>
}

Using NativeSelect

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>
}

API

Select

Element: HTMLDivElement

Arguments

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

Blocks

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.

Arguments

Name Type Default Description

NativeSelect

Element: <span class="hljs-built_in">Array</span>

Arguments

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`.

Blocks

Name Type Default Description
item * Array -
default * Array -
startContent * Array -
endContent * Array -

NativeSelectItem

Element: HTMLOptionElement

Arguments

Name Type Default Description
key * string -
manager * ListManager -
textValue string -

Blocks

Name Type Default Description
default * Array -
selectedIcon * Array -
start * Array -
end * Array -
Released under MIT License - Created by Josemar Luedke