Lazy-loaded, paginated, server-side filtered <wa-combobox> options for
Rails 8 + Hotwire + Web Awesome — no extra gems required.
- On connect, the Stimulus controller fetches page 1 via Turbo Stream →
turbo_stream.updatereplaces the combobox with the first N options plus a<turbo-frame loading="lazy">sentinel at the bottom. - When the user scrolls to the sentinel, Turbo auto-loads the next page →
remove(old sentinel)+append(new options + new sentinel). - When the user types,
keyup->combobox#searchfires (debounced), re-fetches withq=param, resetting to page 1. - Filter param changes re-fetch page 1, replacing all content including stale sentinels.
Note:
keyupis used instead ofinputbecausewa-comboboxhandles keyboard input inside its shadow DOM — the nativeinputevent does not bubble out to the light DOM, butkeyupdoes.
| File | Location |
|---|---|
combobox_page.rb |
app/models/combobox_page.rb |
combobox_pagination.rb |
app/controllers/concerns/combobox_pagination.rb |
_combobox_page.turbo_stream.erb |
app/views/shared/_combobox_page.turbo_stream.erb |
combobox_controller.js |
app/javascript/controllers/combobox_controller.js |
class TagsController < ApplicationController
include ComboboxPagination
def index
collection = Label.all_top_tags # returns { "ambient" => 12, "jazz" => 8, ... }
collection = filter_combobox_collection(collection, params[:q])
@combobox_page = paginate_for_combobox(
collection.sort_by { |_k, v| -v },
target: params[:target] || "tags",
selected: Array.wrap(params[:selected]).compact_blank.map(&:to_s)
)
@combobox_page.next_page_path = tags_path(combobox_next_page_params(@combobox_page))
respond_to do |format|
format.turbo_stream { render layout: false }
end
end
end<%= render @combobox_page %><wa-combobox
name="tags[]"
id="tags"
multiple
placeholder="Choose tags"
data-controller="combobox"
data-action="change->faceted-search#perform:prevent keyup->combobox#search"
data-combobox-async-src-value="<%= tags_path %>">
</wa-combobox>resources :tags, only: :indexThe concern expects a collection of [key, count] pairs — i.e. a Hash or
array of 2-element arrays. The filter_combobox_collection helper does a
case-insensitive substring match on the key.
If your collection has a different shape, filter and sort it before passing to
paginate_for_combobox, and update _combobox_page.turbo_stream.erb to render
your option labels accordingly.