Overview
Bootstrap 6 is a great framework that requires many lines of HTML for a simple component such as a dialog:
<button type="button" class="btn-solid theme-primary" data-bs-toggle="dialog" data-bs-target="#exampleDialog">
Open dialog
</button>
<dialog class="dialog" id="exampleDialog">
<div class="dialog-header">
<h1 class="dialog-title">Dialog title</h1>
<button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
</div>
<div class="dialog-body">
<p>This is a native dialog element. It uses the browser’s built-in modal behavior for accessibility and focus management.</p>
</div>
<div class="dialog-footer">
<button type="button" class="btn-solid theme-secondary" data-bs-dismiss="dialog">Close</button>
<button type="button" class="btn-solid theme-primary">Save changes</button>
</div>
</dialog>
With Bootstrap helpers, the code above can be rendered in just three lines:
<%= dialog 'What is in the hat?', title: 'A gumball, mostly' do %>
And whatever the wand last turned into one.
<% end %>
How to install
Bh is a Rails engine. It brings Bootstrap 6 with it, so nothing else goes in the Gemfile.
# Gemfile
gem 'bh', '~> 6.7.0'
Rails 8.1 and Ruby 3.2 are the minimum.
The form Bootstrap docs
One builder dresses every field, so a view says what it wants and never a class.
class ApplicationController < ActionController::Base
default_form_builder Bh::FormBuilder
end
<%= form.fieldset 'The word' do %>
<%= form.label :word, 'Six letters of it' %>
<%= form.pin_field :pin %>
<%= form.submit 'Say it' %>
<%= form.button 'Whisper it', type: :button %>
<% end %>
What it draws
The word
Combobox Bootstrap docs
A long <select> becomes a searchable menu — and stays a select, so the form submits exactly as before.
<%= form.combobox :shape, shapes, { prompt: 'Pick a shape' }, multiple: true %>
What it draws
The 6-digit code Bootstrap docs
The six slots a one-time code is typed into, drawn from one real field — so a phone can offer the code straight from the text that brought it.
<%= form.pin_field :pin_confirmation, required: true %>
What it draws
Phone numbers
A number that formats itself as it is typed and stores as ten bare digits, so no page has to agree on how to write one.
<%= form.phone_field :phone %>
What it draws
Dialogs Bootstrap docs
A link and the dialog it opens, with the backdrop, the focus trap and Esc that the browser's own element brings. Nothing is scripted, and no page invents an id.
<%= dialog 'What is in the hat?', title: 'A gumball, mostly' do %>
And whatever the wand last turned into one.
<% end %>
Toasts Bootstrap docs
The flash, as toasts at the bottom right. They stand before any script runs and leave on their own.
<%= toasts %>
What it draws
Chat
A thread of bubbles with a field under it, drawn from anything that can say who spoke, when, and whether it arrived.
<%= chat_with url: questions_path, hints: Chat::HINTS, messages: messages %>
What it draws
the Earl · 5 minutes ago
you · 4 minutes ago ✓
the Earl · just now · one wave
The palettes
Nine of them, linked one at a time after the stylesheet. Each repaints Bootstrap's own families, and every one of them is legible in both light and dark.
-
bootstrap -
dawn -
dracula -
gruvbox -
monokai -
nord -
one_dark -
solarized -
tokyo_night
Swapping one for another
The scheme controller does it, and remembers what the reader picked.
<button type='button' data-controller='scheme' data-action='scheme#rotate'
data-scheme-themes-value='["bootstrap","nord","dracula"]'
data-scheme-path-value='/bh/theme' data-scheme-storage-value='scheme'>
Another palette
</button>
API reference →
Trying it out
The gem carries a dummy app that draws every helper on one page. From the root of a clone:
bin/setup
rails s
localhost:3000 is every component at once, and ?flash=1 adds the toasts.