ViewComponent DevTools
Inspect Rails ViewComponent render trees, component state, and render timings directly in your browser's DevTools.
Note
ViewComponent DevTools is in early development and should only be used in trusted development environments.
Features
- Explore nested ViewComponent render trees and render timings.
- Inspect public component properties and instance variables with conservative normalization and credential redaction.
- Pick a rendered component from the page and reveal it in the tree without adding wrapper elements to your markup.
- Search component names with plain text or regular expressions.
- Navigate the tree by keyboard and move through a component's ancestors.
- Ignore exact component names or wildcard namespaces such as
Primer::*. - Target Chromium with Manifest V3, with experimental Firefox packaging.
Packages
This repository contains two packages:
| Package | Description |
|---|---|
packages/view_component_devtools |
A Rails gem that records the native render.view_component instrumentation tree and exposes a bounded, conservatively redacted JSON payload. |
packages/browser-extension |
A WXT + React browser extension that adds a View Components panel to DevTools and renders the component tree and details. |
Requirements
- Ruby 3.2+
- Rails 7.1+
- ViewComponent 3.0-4.x
- Node 20+
- pnpm 10+
The project was developed with Ruby 3.4.1 and Node 22.12.0.
Installation
Add the local or published gem to the development bundle only:
# Gemfile
gem "view_component_devtools", group: :developmentExclude development dependencies from production installs, for example with
BUNDLE_WITHOUT=development:test. This is the only way to ensure the gem's code
is not installed or loaded in production.
Enable capture explicitly when the gem is available:
# config/initializers/view_component_devtools.rb
if defined?(ViewComponentDevtools)
ViewComponentDevtools.configure do |config|
config.enabled = true
end
endInclude the concern in the shared component base. Native events are captured without it, but component properties and page selection require it:
class ApplicationComponent < ViewComponent::Base
include ViewComponentDevtools::Component if defined?(ViewComponentDevtools::Component)
endFinally, add the payload helper after the application body so all components
rendered by yield have completed:
<body>
<%= yield %>
<%= view_component_devtools_payload if respond_to?(:view_component_devtools_payload) %>
</body>Build the Chromium extension from this repository:
pnpm setup
pnpm buildThe extension is written to
packages/browser-extension/.output/chrome-mv3. Load that directory from
chrome://extensions with Developer mode and Load unpacked. Close and
reopen DevTools after installing or reloading the extension.
Usage
Open your Rails application, then open Chrome DevTools and select the View Components tab. It may be under the ยป overflow menu. The toolbar popup only shows usage instructions; the inspector itself lives in DevTools.
Click the element-picker button in the panel toolbar, hover a rendered component, and click to reveal it in the component tree. The picker uses invisible HTML comments emitted by the component concern, so it does not add wrapper elements or affect page layout.
Search component names with plain text or /regular expressions/; use Enter
and Shift+Enter to move between matches. The component tree supports arrow-key
navigation, and the details pane links back through the selected component's
ancestors. Settings can ignore exact component names or wildcard namespace
rules such as Primer::*. Use Refresh after a server render that does not
trigger a top-level navigation.
Captured properties
Property discovery is local to each concrete component class. Every public
instance method declared directly on that class with no parameters is captured,
including attr, attr_reader, and the reader half of attr_accessor.
Inherited and included methods are excluded, as are methods with required,
optional, rest, or keyword parameters. A method that raises is displayed as an
error marker without interrupting the component render. Every instance variable
present before rendering is also captured under its @-prefixed name, including
variables without public readers.
class AvatarComponent < ApplicationComponent
attr_reader :user, :size
def initialize(user:, size: 32)
@user = user
@size = size
end
def size_label
"#{size}px"
end
enduser is represented as [unsupported User] rather than serialized. Prefer
small scalar readers such as user_id or user_name when useful.
Configuration
| Setting | Default | Purpose |
|---|---|---|
enabled |
false |
Explicitly enables development capture. |
max_depth |
8 |
Maximum nested property depth. |
max_collection_size |
50 |
Maximum array/hash entries. |
max_string_length |
2_000 |
Maximum characters per string. |
redact_keys |
credential patterns | Additional strings or regular expressions to redact. |
See SECURITY.md before changing capture or redaction behavior.
Architecture
sequenceDiagram
participant VC as ViewComponent
participant AS as ActiveSupport::Notifications
participant Gem as DevTools gem
participant Page as Rails layout
participant Panel as Browser DevTools panel
VC->>AS: render.view_component start/finish
Gem->>AS: evented subscription
Gem->>Gem: request-local tree + safe property normalization
Page->>Gem: view_component_devtools_payload
Gem-->>Page: application/json script element
Panel->>Page: inspectedWindow.eval(read textContent)
Page-->>Panel: JSON string
The evented ActiveSupport subscriber preserves nested render order without
patching ViewComponent. An optional concern wraps the public render_in method
only to associate a component instance with its native event. It captures values
from direct public methods and current instance variables. State uses
ActiveSupport::IsolatedExecutionState, is created by Rack middleware per
request, and is cleared when the response body closes or immediately when the
application raises.
The extension uses WXT so the same source can target Chromium and Firefox. The MVP is validated against Manifest V3 Chromium output; Firefox packaging is available as an experimental build target.
Development
Install dependencies, run the complete test suite, and build the extension:
pnpm setup
pnpm test
pnpm buildTo produce an experimental Firefox build, run:
pnpm build:firefoxDemo
The executable fixture uses the gem's real subscriber, request state, normalizer, concern, and payload helper:
BUNDLE_GEMFILE=packages/view_component_devtools/Gemfile bundle exec \
ruby examples/generate_demo.rb > tmp/view_component_devtools_demo.html
ruby -run -e httpd tmp -p 4000Visit http://localhost:4000/view_component_devtools_demo.html, open DevTools,
and select View Components. The tree contains Demo::PageComponent with a
nested Demo::CardComponent; the card token is redacted. This fixture proves
the same page contract used by Rails without requiring a generated Rails
application in the repository.
Limitations and roadmap
- Components captured only through native instrumentation appear in the tree, but are not selectable on the page unless they include the component concern.
- Streaming responses must keep their Rack body open until iteration finishes; this is already how compliant Rack servers behave, but dedicated streaming coverage is future work.
- The panel displays one payload per inspected document and does not yet retain render history or Turbo frame snapshots.
- Next steps are DOM/source highlighting, Turbo navigation integration, richer slot metadata, Firefox validation/signing, and a generated Rails demo application.
Security
ViewComponent DevTools can expose application internals and is intentionally disabled by default. Keep it out of production bundles and read SECURITY.md for the complete capture, redaction, and reporting guidance.
Contributing
Issues and pull requests are welcome. Run pnpm test before submitting a
change. Changes to capture or redaction behavior should include focused gem
tests and account for the guarantees documented in SECURITY.md.
License
ViewComponent DevTools is available under the MIT License.
