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. dialogrui--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.

Recommended: re-run installer
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):

config/initializers/rapid_rails_ui.rb
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

Default (no namespace)
<div data-controller="dialog"
     data-dialog-open-on-load-value="false">
  <button data-action="dialog#open" data-dialog-target="trigger">Open</button>
</div>
With config.stimulus_namespace = "rui"
<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:

Without namespace
// 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
With namespace
// 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:

Available helpers
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.

jsbundling hosts: sync after bundle update
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.