Guide
Remote data
Point load at your endpoint. The widget debounces the typing, cancels the request that is no longer
wanted, caches what came back, and pages as the list is scrolled.
createSelect(input, {
name: 'customer',
minChars: 2,
debounce: 200,
load: async (query, { signal, cursor }) => {
const response = await fetch(`/api/customers?q=${encodeURIComponent(query)}&page=${cursor ?? 1}`, { signal });
const body = await response.json();
return {
options: body.data.map((row) => ({ value: String(row.id), label: row.name, description: row.email })),
cursor: body.next_page,
hasMore: Boolean(body.next_page),
};
},
});
Return either a plain array of options or { options, cursor, hasMore }.
What happens for you
- Debounce. One request per pause, not one per keystroke.
- Abort. The
signalis aborted when a newer query starts. ThrowingAbortErroris expected and never shown as an error. - Cache. Repeating a query — including going back to an earlier one — is answered from memory.
- Paging. When
hasMoreis true, reaching the end of the list callsloadagain with yourcursor. - States. The widget carries
dxs--loadingwhile a request is in flight and shows yourstrings.errorwhen one fails, so the list never lies about what it knows.
Keeping the chosen option
A remote list only holds what the last query returned, so pass the already-chosen option in when you
render the field — either as the <select>'s existing <option>, or:
createSelect(input, { options: [current], value: current.value, load });
Laravel
Route::get('/api/customers', function (Request $request) {
return Customer::query()
->when($request->string('q')->toString(), fn ($q, $term) => $q->where('name', 'like', "%{$term}%"))
->paginate(30, ['id', 'name', 'email']);
});
The paginator's next_page_url is a cursor; hand it back as cursor and the widget pages through it.
Updated 12 Sep 2026