Simple Form
poetry-simple_form is the migration bridge for existing Simple Form
apps: one initializer re-maps the input types onto Poetry fields, and
every f.input call in your views keeps working.
How it works
The bridge re-maps Simple Form's input types onto classes that render
whole Poetry fields through Poetry::Ui::FormBuilder,
bound to the same object and template. Label, hint, error, and aria
wiring derive from your model on the same code path the
native builder uses, so a bridged form
and a rewritten one render identical fields. Simple Form contributes
only type resolution; its wrapper tree is bypassed by design, because
a flat wrapper stack cannot express the field's label, hint, error,
and aria as one unit.
Your existing simple_form.* i18n keys (labels, hints,
placeholders) keep working: Poetry's builder reads them as a
fallback chain.
Install
Add the gem and run the install generator. It writes one initializer, and that initializer is the whole integration.
gem "poetry-simple_form"
bin/rails g poetry:simple_form:install
# config/initializers/poetry_simple_form.rb
Poetry::SimpleForm.activate!
Delete the file and stock Simple Form rendering is back instantly.
What restyles
Existing views need no edits. Each resolved input type renders the
matching Poetry control, wrapped in a field with
aria-required (never the native attribute), maxlength,
min, max, and step derived from your validations, and errors wired
via aria-describedby.
<%= simple_form_for @user do |f| %>
<%= f.input :email %>
<%= f.input :bio %>
<%= f.input :admin, as: :switch %>
<%= f.input :role, collection: %w[admin member] %>
<%= f.association :company %>
<% end %>
| Simple Form type | Renders as |
|---|---|
string / email / url / tel / citext / uuid | the input field, native type preserved |
search | the search field |
password | a password input; the value never round-trips into the markup |
sensitive | the sensitive input, masked with a reveal toggle |
text / hstore / json / jsonb | the textarea field |
boolean | the horizontal checkbox field |
switch | the switch setting row |
integer / decimal / float | the number field, min/max/step re-derived from numericality validations |
range / slider | the slider |
date / time / datetime | the date, time, and date-time fields; the composite is one datetime-local control |
date_picker / calendar | the date picker popover and the inline calendar |
file | the file input; input_html: { variant: :dropzone } for the drop zone |
otp | the one-time-code input |
tag_group | the tag group |
select / grouped_select / time_zone | the select |
native_select | the styled native select, zero JavaScript |
combobox / autocomplete | the combobox and the autocomplete |
radio_buttons / check_boxes | the radio group and the checkbox group with its select-all |
rich_text_area / hidden / country | left to Simple Form on purpose: Poetry ships no rich-text editor, a hidden field has nothing to render, and the country list belongs to the country_select gem |
The table is Poetry::SimpleForm::COVERAGE, and a parity
test in the gem fails when a new Poetry form control has no Simple
Form route. The types Simple Form never had are reached by name
through as:.
<%= f.input :code, as: :otp, poetry: { length: 4 } %>
<%= f.input :api_key, as: :sensitive %>
<%= f.input :volume, as: :slider, poetry: { min: 0, max: 11 } %>
<%= f.input :starts_on, as: :date_picker %>
<%= f.input :tags, as: :tag_group %>
<%= f.input :city, as: :autocomplete, collection: cities %>
<%= f.input :plan, as: :combobox, collection: plans, label_method: :title %>
f.association works unchanged: Simple Form fetches the
records, the bridge renders them as Poetry pickers, and
label_method: / value_method: (symbols or
callables) are honored before Simple Form's detection chain.
required: true or false overrides the
presence inference, on aria-required only. Poetry-only
options ride a poetry: hash, merged last so it wins over
anything Simple Form derived. String label:,
hint:, and placeholder: options pass
through; the rest of input_html: lands on the control
minus its class and id, since Poetry components own their classes.
The end state
The bridge exists so views can migrate at their own pace. New forms,
and old ones as you touch them, move to Poetry's own builder, where
f.input works the same and the rest of the
form builder guide applies.
<%= form_with(model: @user, builder: Poetry::Ui::FormBuilder) do |f| %>
<%= f.input :email %>
<%= f.submit %>
<% end %>
Both paths derive the same
field from the same model,
so a half-migrated app has one look throughout and the bridge can be
removed the day the last simple_form_for goes.