JavaScript Dropdown
@lemonadejs/dropdown · ✓ 41 contract checks · framework-agnostic · zero dependencies
<Dropdown /> — select, autocomplete and picker in one block, built ON
the Modal primitive (the panel) with the datagrid’s fixed-rowheight
virtualization (large option lists stay a window of DOM).
v5 parity, the full nuance set:
- items { value, text, group, image, keywords, synonym, disabled }; strings/numbers normalize to {value,text}; {id,name} too
- groups: sorted together, header rows injected
- single or multiple (Done/Reset controls); value as array or ‘a;b’ string via divisor; allowempty=false pins a selection
- autocomplete: the closed label swaps to a contenteditable search field while open; filters text/group/keywords/synonym; selected items always remain listed; FORCED on by insert/remote/searchbar
- remote: url?q= fetch with 300ms debounce, onbeforesearch veto, results merge behind the selected items; initial url load
- insert: + button adds the typed text (async onbeforeinsert can replace or cancel; oninsert after)
- keyboard: closed Enter/arrows open; open: arrows wrap, Home/End, Enter selects, Space selects (no autocomplete), Escape CANCELS (restores the previous value) — closing any other way COMMITS
- types: default (anchored panel) | picker | searchbar (sheet modes) | inline (no modal, list always visible) | auto (by viewport width at open)
Example
import { html } from 'lemonadejs';
import Dropdown from '@lemonadejs/dropdown';
const cities = [
{ value: 'lon', text: 'London', group: 'Europe' },
{ value: 'par', text: 'Paris', group: 'Europe' },
{ value: 'ber', text: 'Berlin', group: 'Europe' },
{ value: 'nyc', text: 'New York', group: 'Americas' },
{ value: 'sao', text: 'Sao Paulo', group: 'Americas' },
{ value: 'tok', text: 'Tokyo', group: 'Asia' },
{ value: 'sin', text: 'Singapore', group: 'Asia' },
];
const App = (props, { state }) => {
const office = state('lon');
const visits = state(['par', 'tok']);
return html`<div style="display:grid;gap:12px;max-width:320px">
<${Dropdown} data="${cities}" bind="${office}" placeholder="Home office" />
<${Dropdown} data="${cities}" bind="${visits}" multiple autocomplete placeholder="Offices to visit" />
<p>Based in <b>${() => (cities.find((c) => c.value === office.value) || {}).text || 'nowhere'}</b>,
visiting <b>${() => visits.value.length}</b> other offices.</p>
</div>`;
};Installation
npm install @lemonadejs/dropdown
import Dropdown from '@lemonadejs/dropdown';
import '@lemonadejs/dropdown/style.css';
Three deployment forms, one component:
html`<${Dropdown} />` // by value (no registration)
setComponents({ Dropdown }); // then <Dropdown /> by name anywhere
createWebComponent(Dropdown); // <lm-dropdown> in plain HTML/any framework
Props
Every declared prop arrives as a live state — pass a value for a snapshot or a state for a two-way live wire. Attribute strings are coerced to the declared type.
| Prop | Type | Default | Description |
|---|---|---|---|
bind | string | — | Two-way bound value. .set() fires onchange; plain assignment is silent. value: single, array (multiple) or ‘a;b’ string |
data | array | — | DropdownItem[] (or strings/numbers/{id,name}) |
multiple | boolean | false | |
autocomplete | boolean | false | |
remote | boolean | false | search against url?q= instead of locally |
url | string | '' | |
insert | boolean | false | + button adds the typed text |
type | string | '' | ” |
placeholder | string | '' | |
aria-label | string | '' | |
width | number | 0 | |
height | number | 300 | panel viewport height |
rowheight | number | 28 | |
divisor | string | ";" | string-value separator (multiple) |
allowempty | boolean | true | false: the last selection cannot be removed |
disabled | boolean | false |
Events
All event names are lowercase (the platform convention — LJS-305 warns otherwise).
onchange— (value)onopenonclose— (origin)onsearch— (results) after a remote searchonbeforesearch— (query, http) -> false cancelsoninsert— (item)onbeforeinsert— async (item) -> item | falseonload— data ready (incl. initial url load)
API
import { ref } from 'lemonadejs';
const dropdown = ref();
html`<${Dropdown} ref="${dropdown}" />`;
// dropdown.current.open(...) · dropdown.current.close(...) · dropdown.current.toggle(...) · dropdown.current.isClosed(...) · dropdown.current.getValue(...) · dropdown.current.setValue(...) · dropdown.current.getText(...) · dropdown.current.getData(...) · dropdown.current.setData(...) · dropdown.current.add(...) · dropdown.current.reset(...)
open()close()toggle()isClosed()getValue()setValue()getText()getData()setData()add()reset()
Styling
All classes follow the lm-dropdown-* convention; visual variants are data-*
attributes on the root. Override freely — there is no styling engine to fight.
Contract
The machine-readable schema ships with the package:
import contract from '@lemonadejs/dropdown/contract.json';
verify.json carries the conformance proof produced by verify(Dropdown).
Looking for the v5 plugin? See the archived v5 documentation.