Composition
You build a component by nesting helpers, the way shadcn/ui nests React components. The view ends up shaped like the HTML it renders.
Helpers exist only for what is installed
Each component installs its own helper module:
button writes app/helpers/ui/button_helper.rb,
which defines Ui::ButtonHelper#ui_button. Rails automatically includes
everything under app/helpers/, so an installed helper is available in
every view with no include.
The helper of a component that is not installed does not exist, and calling it
raises NoMethodError when the page renders. Check
installed[].helpers before writing the view.
A leftover ui_helper.rb at the root?
app/helpers/ui_helper.rb, which defines
helpers for components you may not have. status --json flags it as
helpers.legacyHelperPresent, and you can delete it.
Subcomponents are nested helpers
These components don't use ViewComponent slots, and they don't take their content as arguments. You put them together by nesting calls.
bin/shadwire info <name> lists the helper for each part. Look it up
rather than guessing: the helper is ui_card_content, not
ui_card_body.
Use the whole composition
Don't put everything into one part. The header, title and description each render their own markup, and putting all of it in the content loses that.
Items go inside their group
The same goes for ui_tabs_trigger inside
ui_tabs_list, ui_breadcrumb_item inside
ui_breadcrumb_list, and ui_pagination_item inside
ui_pagination_content.
Overlays always need a title
dialog, alert-dialog,
sheet and drawer need their title component so
screen readers can announce them, even when the design has no visible title.
Interactive components need Stimulus
29 of the 57 components ship a controller.
bin/shadwire info <name> tells you which through
requiresStimulus, and status.stack says whether the
app has importmap and stimulus-rails.
The controllers install into app/javascript/controllers/ and register
themselves through controllers/index.js's
eagerLoadControllersFrom("controllers", application). If your app
registers controllers explicitly, register the new ones there too.
Rendering the class directly
The helper only wraps the class, so these two render the same thing:
In views, use the helper. Use the class when you need the component object itself, or
inside another ViewComponent. ViewComponents don't get helpers automatically, so include
the module to call ui_button from a component's template: