UI
poetry-ui is the component catalog: 88 accessible, themeable components rendered server-side from Ruby helpers, with nine complete themes, eight page blocks, a form builder, testing helpers, and a full agent surface.
What it is
Where poetry-core is the
framework, poetry-ui is the shadcn-parity catalog built on it:
every component from Button
and Dialog through
Combobox,
Data table, and
Sidebar, each one a
ViewComponent wired to Stimulus behavior and rendered through a
poetry_* helper (140 of them, counting the group,
item, and wrapper helpers). The full gallery fills the components
section of the sidebar, and the registry that indexes all of it
ships in the gem at config/component_registry.yml.
Install and first components
One generator wires the whole surface: the vendored CSS set, the Tailwind imports, the safelist, Stimulus registration, the engine mount, and the agent files. Every step is idempotent, and re-running it after a gem bump is the upgrade path. The installation guide has the details.
bundle add poetry-core poetry-ui poetry-lucide
bin/rails g poetry:install --theme default
Simple components are one helper call with the visible text as the
content block; composite components yield a builder whose
with_* slots mirror the component's anatomy:
<%= poetry_button { "Save" } %>
<%= poetry_button(variant: :outline) { "Cancel" } %>
<%= poetry_dropdown_menu do |menu| %>
<% menu.with_trigger(variant: :outline) { "Open" } %>
<% menu.with_item(shortcut: "⌘S") { "Settings" } %>
<% menu.with_separator %>
<% menu.with_item(variant: :destructive) { "Log out" } %>
<% end %>
Every helper accepts class:, data:, and
aria: passthroughs, merged over the component's own
attributes with your classes winning conflicts. Variant and size
vocabularies are closed sets recorded in the registry, so
bin/rails poetry:check flags a typo with a
did-you-mean suggestion before it ships.
The form builder handoff
Inside a model-bound form you stop calling components directly and
hand off to Poetry::Ui::FormBuilder: the builder
derives label, value, error, and required state from the object,
infers each field's component from the attribute's type, and wires
the validation attributes for you. as: overrides the
inference, and association reflects the model's
associations into the right control.
<%= form_with(model: @post, builder: Poetry::Ui::FormBuilder) do |f| %>
<%= f.input(:title) %>
<%= f.input(:body) %>
<%= f.input(:published, hint: "Visible to everyone.") %>
<%= f.association(:category) %>
<%= f.submit %>
<% end %>
The forms guide walks the whole surface, including type inference and error rendering.
Nine themes ship in the gem
A theme is not a paid add-on or a separate package: the gem carries nine complete visual themes (default, vega, nova, mira, rhea, maia, luma, lyra, and sera), and the installer copies your choice into the app. Components reference stable class names, the theme file paints them, so switching restyles all 88 components and every block at once:
bin/rails g poetry:install --theme sera # switch; everything else sticks
The theming guide covers the token layer underneath, dark mode, and how to derive a theme of your own; there is a live theme switcher in the docs header to compare them on any page.
Own the code when you want to
By default components are gem-owned and upgrade with
bundle update. When you need to change one,
poetry:add copies its source into
app/components, where Rails autoload precedence
shadows the gem; the copy is yours, dependencies come along
recursively, and nothing ever overwrites your edits. Addresses
beyond the bare names reach any compatible registry, including
the one this site serves:
bin/rails g poetry:add button # copy the gem's source into app/components
bin/rails g poetry:add @acme/fancy-chart # install from a configured registry
bin/rails g poetry:diff # read-only drift report for your copies
Every copy records the gem version it came from in
config/poetry_components.yml, so after an upgrade the
poetry:diff report shows exactly where your copies
stand against what the gems now ship.
Blocks and recipes
Above the components sit eight blocks, full page sections composed
from the catalog and installed as editable starting points:
app-shell,
data-index,
page-header,
section-card,
destructive-panel,
stepper,
action-bar, and
top-nav.
Recipes go one step further:
multi-file slices like a working data-index screen with its
controller and system test, plus the scaffold template set that
makes rails g scaffold itself produce Poetry-composed
views.
The agent surface
poetry-ui treats coding agents as first-class users. The gem
serves /poetry/llms.txt and
/poetry/llms-full.txt from its engine, ships the
poetry MCP server with ten tools projected from the
committed registry, installs the poetry,
poetry-design, and poetry-component
skills plus an AGENTS.md section, and provides the
check as the executable verdict an agent finishes with:
bundle exec poetry-agent # the MCP server (poetry-agent gem) - reads the registry, no app boot
bin/rails poetry:check # template verification - run it last
The agent guide maps the whole surface, the MCP guide documents the ten tools, and the editors guide wires it into your editor with one generator.
Testing helpers
The gem ships interaction testers for its composite components:
include Poetry::Ui::Testing in a system test and
drive a Select, Combobox, Dropdown menu, or Dialog through its
real keyboard and pointer contract instead of hand-rolled click
sequences. Assertions read the public attribute contract, the
same one the components guarantee across upgrades:
require "poetry/ui/testing"
class PlanSettingsTest < ApplicationSystemTestCase
include Poetry::Ui::Testing
test "picking a plan from the keyboard" do
visit settings_path
plan = poetry_select("#plan")
plan.select_option("Pro", via: :keyboard)
assert_equal "pro", plan.value
end
end
The testing guide covers the full ladder, from the boot-free check through wiring tests to browser runs, and the accessibility guide adds the manual verification pass.