# Herb and ReActionView

Rails 8.2 compiles HTML templates through Herb. What Poetry guarantees under it, under ReActionView and with slots, how poetry:check holds your own components to the same bar, and where reactive templates and Poetry components meet today.

Rails 8.2 compiles HTML templates through Herb under its framework defaults (`config.action_view.erb_implementation = :herb`), and Action View depends on the `herb` gem, so `poetry:install` adds no line for it there. Poetry guarantees, each as a gate in its own pipeline: every template the gems ship compiles with every validator Herb ships, each one fatal, and the compiled Ruby checked; the unit suite and the behaviour tier pass on Rails main in a host that compiles through Herb; they pass three more times under ReActionView with nothing rescued (as the engine, with slots in server mode, with slots in client mode); a component renders the same markup, with the slot markers added when slots are on. Versions: Herb 0.11, ReActionView 0.6. Reactive templates are experimental upstream.

Your components: Rails checks views, Poetry checks components. `bin/rails herb:check` walks the view paths and never reaches a template under `app/components`; `bin/rails poetry:check` compiles those the same way (`herb-compile`, an error when the app renders through Herb, a warning until then). Build elements through `element_tag(root_tag, **root_attributes)` and hand `tag.attributes` its hash as an argument, never a splat: with slots on, Action View's own tag helpers are resolved ahead of the render, a splat's attributes are dropped and a tag name held in a variable stops the template compiling (`element-tag` warning). Keep `format` off a component's options, styles and slots: Rails reads that name off what it renders as the template's format, and from 8.2 every render in a view asks (`renderable-reader`, a warning before 8.2 and an error from it; a class that defines `format` itself and reads the value under another name is fine).

ReActionView settings: `intercept_erb`, `external_template_mode` (`:fallback`, `:skip`, `:compile`) and `slots` all leave Poetry components rendering the same; `slots` compiles templates from gems too. `debug_mode` wraps what your own templates output in a `display: contents` span, which breaks sibling selectors such as `peer-*` in a component of yours; Poetry's templates are not wrapped. In a test that compares markup under slots, `Poetry::Core::SlotMarkers.strip(html)` takes the markers off.

Reactive templates with Poetry components: a Poetry component is rendered by the server, so the browser cannot build one and cannot change what a component's block reads. A form with `data-herb-into` takes Poetry components (`poetry_input`, `poetry_button`, in a plain form or `form_with`); declare at least one state in its template; keep what a new row shows at once, and any branch that follows a state, in plain markup; keep partials out of `form_with`. Any `data-herb-*` action goes through `data:` on a Poetry helper; read the state beside the component, not inside its block. A change the server may refuse is not put back by a reactive template: use `poetry_optimistic_form`, which answers a refusal with a refresh. Poetry's controllers do not call `useState`; yours can.

Open upstream on 2026-09-29: herb 2737, 2738, 2708 and 1111; reactionview 185, 179 and 180; view_component 2717.
