HotwireCombobox is pretty damn slick
In a stroke of good fortune, this week's big, overriding to-do item was to figure out how to write a hotwire-friendly "combo box" (one of those drop-down / select boxes for the web that you can type into and filter the options). Then I happened to scan this week's Ruby Weekly and found somebody beat me to the punch!
It's by Jose Farias and he calls it HotwireCombobox. The documentation page contains plenty of demos, so go play with it!
The best part (and my favorite thing about moving to import maps for JavaScript in Rails 7) is that the front-end assets live with the gem, which means there's no risk of version drift causing the backend and front-end to fall out of sync with each other.
In fact, set up was so minimal, I'm going to share the entire changeset of what
it took to convert my app's
f.collection_select
boxes over to f.combobox
.
Install
Add the gem to your Gemfile
and bundle
it:
gem "hotwire_combobox"
Search your app for its stylesheet_link_tag
(probably in a layout's <head>
tag) and then just paste combobox_style_tag
in above them. Mine now looks
like:
<%= combobox_style_tag %>
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
Add to_combobox_display
to your models
I hate adding view-specific cruft to my Active Record models, but in the spirit
of wanting to get this to work as quickly as possible, I threw this in my
Equipment
model, so that the title
attribute is what's presented in the
combobox's list:
class Equipment < ApplicationRecord
#…
def to_combobox_display
title
end
end
Swap out the select
inputs
Next, I replaced each collection_select
, like this:
<%= f.collection_select :equipment_id, Equipment.all.order(:title), :id, :title, include_blank: true %>
With a f.combobox
, like this:
<%= f.combobox :equipment_id, Equipment.all.order(:title), include_blank: true %>
(As of March 29, 2024, that include_blank: true
isn't working on its
own, so I patched it
in my custom
TailwindFormBuilder).
Customize appearance
The hardest part was getting the combobox to look like my other custom form
inputs. Since I'm using Tailwind, I targeted the
classes and variables the gem exposes and overrode what I needed to inside
@layer base {}
.
If you don't use Tailwind, this will look like nonsense:
/* hotwirecombobox */
.hw-combobox__main__wrapper {
@apply mt-1 rounded-md shadow-sm max-w-screen-md;
}
.hw-combobox__main__wrapper input:focus {
@apply ring-0;
}
.hw-combobox__dialog__input {
@apply rounded-lg focus:ring-0 focus:border-accent ;
}
:root {
--hw-focus-color: rgb(var(--accent));
--hw-border-width--thick: 1px;
--hw-combobox-width: 100%;
--hw-combobox-width--multiple: 100%;
}
That's it!
Note what I didn't include in any of the snippets above? No import map configuration. No registering Stimulus controllers in JavaScript. The gem handles it all on its own.
These sorts of UIs have always been a massive distraction in my experience, so having a component that's easy to drop in and plays nicely with your app's existing frontend is simply tremendous.
Thank you, Jose! 💜