EOS UI
Components

Select

A compass inspired, framework agnostic select with automatic four side placement and virtualized options.

Introduction

Select keeps its compact pill trigger in the form layout. When opened, its compass panel floats beside the trigger and automatically chooses the side with the most room. The pointer stays fixed on the selected row while the option list scrolls and snaps that row to the center.

The component is a native Web Component and can be used from React, Vue, Angular, or plain HTML. Its built-in positioning logic handles side selection, boundary collision, and position updates as the page scrolls or resizes. The component virtualizes long option lists and mounts only the visible rows plus a small overscan.

Interactive example

This example contains 10,000 options. Use the placement controls to preview four compass layouts. The compact trigger stays centered in the preview; the panel is constrained by the page viewport and can extend beyond the demo card. Restore returns placement to automatic.

Four side placement

Actual ยท closed
Selected: Italy

Basic usage

<eos-select id="country" aria-label="Country" placeholder="Choose a country"></eos-select>
import '@eosjs/ui/select'; // Register only Select

const select = document.querySelector('#country');
select.options = [
  { value: 'fr', label: 'France', prefix: '๐Ÿ‡ซ๐Ÿ‡ท' },
  { value: 'de', label: 'Germany', prefix: '๐Ÿ‡ฉ๐Ÿ‡ช' },
  { value: 'it', label: 'Italy', prefix: '๐Ÿ‡ฎ๐Ÿ‡น' },
  { value: 'jp', label: 'Japan', prefix: '๐Ÿ‡ฏ๐Ÿ‡ต' },
];
select.value = 'it';
select.addEventListener('change', (event) => {
  const { value, option } = event.detail;
  console.log(value, option.label);
});

API

Properties and attributes

NameTypeDefaultDescription
optionsreadonly EosSelectOption[][]Options with value, label, optional prefix and disabled. Set as a JavaScript property.
valuestring''Selected option value.
openbooleanfalseWhether the floating panel is open.
disabledbooleanfalseDisables the trigger and selection.
placeholderstringSelect an optionText shown before a value is selected.
full-widthbooleanfalseMakes the trigger fill its containing block.
placementauto | top | right | bottom | leftautoPreferred floating panel side. auto chooses the side with the most available space.
boundaryElement | Element[] | RectViewportOptional collision boundary. Set it as a JavaScript property; multiple boundaries are intersected.
aria-labelstringSelect an optionAccessible name for the combobox.
aria-labelledbystringโ€”ID reference for an external visible label.

Events

EventDetailDescription
change{ value: string, option: EosSelectOption }Fires when a different option is committed. It bubbles and is composed across the shadow boundary.

Keyboard interaction

  • Enter or Space opens the panel; while open, it commits the active option.
  • Up/down placements use ArrowLeft / ArrowRight; left/right placements use ArrowUp / ArrowDown. Both keep the active option centered in the compass.
  • Home and End move to the first or last enabled option.
  • Escape closes the panel and returns focus to the trigger; Tab closes it while continuing normal focus movement.
  • Typing characters searches option labels by prefix.

Large option lists

Options are stored as data, while only the visible viewport rows and three rows of overscan are rendered. The active or selected row snaps to the center after scrolling. Keep option values unique and provide stable labels. For datasets that change, replace the options property with the updated array.

Placement and themes

The panel uses built-in positioning logic for automatic placement, preferred placement with fallback, viewport shifting, available-size constraints, live updates, and custom collision boundaries. Left/right placements use a vertical compass arc; top/bottom placements use a horizontal arc with a matching tick axis and virtual scroll direction. EOS keeps the pointer and selection capsule fixed at the compass center. The placement property requests a side; the component can fall back when there is insufficient room. Its default auto chooses the side with the most available space. The boundary property constrains collision handling to an element or rectangle. The trigger remains in normal layout, so opening the list does not push form content. Light and dark surfaces use EOS semantic theme tokens. Reduced-motion preferences shorten the transitions.

Framework integration

Register the element once, then assign options as a property. In React, attach a native event listener in an effect; in Vue, use a template ref or listen to the composed change event. The component itself has no framework runtime dependency.

import { useEffect, useRef } from 'react';
import '@eosjs/ui/select';

export function CountryField() {
  const ref = useRef<HTMLElement>(null);

  useEffect(() => {
    const select = ref.current;
    if (!select) return;
    select.options = [{ value: 'jp', label: 'Japan' }];
    const onChange = (event: Event) => {
      console.log((event as CustomEvent).detail.value);
    };
    select.addEventListener('change', onChange);
    return () => select.removeEventListener('change', onChange);
  }, []);

  return <eos-select ref={ref} aria-label="Country" />;
}

On this page