Stimulus Namespace
Prefix every Stimulus identifier the gem emits so the gem's controllers coexist with your app's controllers
of the same name. Available in v0.47.0+ — verified current against v0.53.0.
The registration mechanism changed in v0.49.0 (registerAll replaced per-controller
application.register calls); this page reflects that.
Why it exists
RapidRails UI is a Stimulus-coupled component library. The gem ships dozens of Stimulus controllers with natural
identifiers like dialog, menu, checkbox, search,
upload. Most non-trivial Rails apps already declare Stimulus controllers of their own. If both sides
register the same identifier, Stimulus picks one and the other silently breaks: components render fine but their
triggers do nothing.
config.stimulus_namespace prefixes the gem's identifiers (e.g. dialog →
rui--dialog) so the two sides stop fighting. The prefix propagates through every emission point:
data-controller, data-action, data-X-target, data-X-Y-value.
Recommended for any app that ships more than a handful of its own Stimulus controllers. If your app has no overlap with the gem's identifiers, the default (no namespace) is simpler and keeps identifiers short.
Enabling the namespace
Easiest path: re-run the install generator with the --stimulus-namespace flag. It bakes the value
into the initializer and rewrites the managed registerAll(...) call in
app/javascript/controllers/index.js so the Ruby and JS sides stay in sync.
rails g rapid_rails_ui:install --stimulus-namespace=rui
Or set it manually in the initializer — then update the registerAll call in index.js yourself (see below):
RapidRailsUI.configure do |config|
config.stimulus_namespace = "rui"
end
If you hand-edit the initializer without re-running the generator, the two sides fall out of sync silently (fixed as a class of bug in v0.49.2 for the generator's own migration path — see JS registration below for the manual fix).
What changes in emitted HTML
<div data-controller="dialog"
data-dialog-open-on-load-value="false">
<button data-action="dialog#open" data-dialog-target="trigger">Open</button>
</div>
<div data-controller="rui--dialog"
data-rui--dialog-open-on-load-value="false">
<button data-action="rui--dialog#open" data-rui--dialog-target="trigger">Open</button>
</div>
What changes in your controllers/index.js
Since v0.49.0, the host's controllers/index.js doesn't list controllers one by one — it imports
guardCollisions and registerAll from rapid_rails_ui/stimulus_guard and
calls both. The install generator manages this block between
// RapidRailsUI Controllers - BEGIN/END markers; don't hand-edit inside
them. If you flipped config.stimulus_namespace by hand instead of re-running the
generator, update prefix: in the block yourself:
// RapidRailsUI Controllers - BEGIN (managed by rapid_rails_ui:install — do not edit by hand)
import { guardCollisions, registerAll } from "rapid_rails_ui/stimulus_guard"
guardCollisions(application)
registerAll(application)
// RapidRailsUI Controllers - END
// RapidRailsUI Controllers - BEGIN (managed by rapid_rails_ui:install — do not edit by hand)
import { guardCollisions, registerAll } from "rapid_rails_ui/stimulus_guard"
guardCollisions(application)
registerAll(application, { prefix: "rui" })
// RapidRailsUI Controllers - END
guardCollisions wraps application.register so a second registration under the same
identifier throws at boot — naming both the controller that won and the one that lost — instead of silently
breaking one component's interactivity. registerAll also accepts only: (register a
subset) and except: (skip ids you're shadowing with your own controller); see
app/javascript/rapid_rails_ui/USAGE.md in the installed gem for the override recipe.
What changes in your own templates
Any custom data-action, data-X-target, or data-X-Y-value attribute you
write that references a gem controller must use the namespaced identifier. The most common case: a trigger button
outside the component's slot.
WRONG — silently does nothing when stimulus_namespace is set:
<button data-action="dialog#open">Open</button>
RIGHT — either form works:
<button data-action="rui--dialog#open">Open</button>
<%# Or, with the helper (recommended — adapts to whatever you configure): %>
<button data-action="<%= stimulus_action(:dialog, "click->#open") %>">Open</button>
Helpers (for portable code)
The gem mixes RapidRailsUI::Helpers::StimulusHelper into BaseComponent. Use these
helpers in your own ERB or components and your code is correct under both default and namespaced configs:
stimulus_id(:dialog)
# => "dialog" (no namespace)
# => "rui--dialog" (namespace = "rui")
stimulus_action(:dialog, "click->#open")
# => "click->dialog#open"
# => "click->rui--dialog#open"
stimulus_attr(:dialog, :target)
# => :"dialog-target"
# => :"rui--dialog-target"
stimulus_attr(:dialog, :open_on_load_value)
# => :"dialog-open-on-load-value"
# => :"rui--dialog-open-on-load-value"
jsbundling vs importmap hosts
Namespacing is identical on both bundlers — same config, same registerAll(application, { prefix:
"rui" }) call. What differs is how controller JS reaches your app, which changes what
you need to do after a bundle update rapid_rails_ui:
| Bundler | Import specifier | After bundle update |
|---|---|---|
| importmap-rails | "rapid_rails_ui/stimulus_guard" | Nothing — controllers resolve straight from the gem via pin_all_from. Next page load picks up JS fixes automatically. |
| jsbundling-rails / esbuild | "../rapid_rails_ui/stimulus_guard" | Run bin/rails rui:update (or re-run the install generator) — esbuild can't resolve gem paths, so controllers are copied locally into app/javascript/controllers/rapid_rails_ui/ and go stale otherwise. |
rui:update (added v0.52.0) is the targeted fix for jsbundling hosts: it re-runs only the
controller-sync step of the install generator (not the initializer, Tailwind, or theme-toggle steps), then
runs yarn build if package.json defines a build script, and prints
exactly which controller files changed.
bin/rails rui:update
This repo runs jsbundling-rails, so rui:update (or
rails g rapid_rails_ui:install) is the step that actually matters here — a plain
bundle update rapid_rails_ui alone only touches the Ruby gem, never the local JS copies.
Why this matters: the v0.52.1 tabs bug
A concrete case study in what breaks when a controller doesn't respect the configured namespace.
rui_tabs shipped in v0.52.0 with its Stimulus controller reading hardcoded
data-rui--tabs-* attribute names. The Ruby side correctly emitted attributes
relative to whatever namespace was configured (data-tabs-* for a host with no namespace set) —
so on any app running the default bare identifiers, the two sides mismatched.
Clicks still worked (they're plain data-action bindings, unaffected), but the active-tab
highlight, deep-linking, and the change event's detail payload were all silently dead — the
exact "renders and clicks but the state never updates" symptom that's easy to blame on your own code.
Fixed in v0.52.1. The controller now derives every attribute name from
this.identifier (Stimulus's own name for the registered controller — "tabs" bare,
"rui--tabs" namespaced) instead of a hardcoded string, so it's correct under any
prefix: automatically. Locked in by a controller-source test so it can't regress silently
again.
The general lesson — if you ever write a custom Stimulus controller that needs to read a
data attribute another RapidRailsUI-adjacent controller wrote (or you're shadowing a gem controller per
the except: pattern above), derive the
attribute name from this.identifier rather than assuming a bare or rui---prefixed
string. It's the only form that's correct regardless of what config.stimulus_namespace is set
to in the host you're dropped into.
Troubleshooting
Component renders but interactions do nothing (no JS console errors).
See the dedicated Troubleshooting page for the full diagnosis ladder.