browsable
Rails-aware browser-compatibility auditing for your frontend code.
browsable audits a Rails application's CSS, HTML, ERB, and JavaScript and
reports which browsers can actually render and run it — then compares that
against the project's declared allow_browser policy.
The name is a play on Rails 8's allow_browser controller API. Instead of
declaring which browsers you allow, browsable tells you which browsers your
code is actually browsable by.
This is the core gem of the
browsablemonorepo. See alsobrowsable-lsp(editor diagnostics) andbrowsable.nvim(Neovim plugin).
Philosophy
- The gem owns no parsing or compat-data logic. It shells out to mature tools that already do this well — Herb for ERB, stylelint for CSS, eslint-plugin-compat for JavaScript. The gem's value is the Rails-aware glue.
- No
package.json, nonode_modulesin your Rails repo. The external tools live globally on your machine.browsable doctordetects them and guides installation. - It reports, it doesn't decide. It tells you what your code requires and what your config permits. You decide what to do.
- Configuration is optional.
browsableruns with zero config.
Installation
Add it to your Rails app's Gemfile:
group :development, :test do
gem "browsable"
end
Then bundle install. Or install it standalone:
gem install browsable
System dependencies — the doctor workflow
browsable shells out to stylelint and eslint (and the node runtime they
need). Those are not gem dependencies — they live globally on your machine.
Check what you have:
bundle exec browsable doctor
doctor prints, for each missing tool, the exact command to install it. Let it
do the work for you:
bundle exec browsable doctor --fix # installs missing tools via brew / npm
ERB and HTML analysis needs nothing extra — the herb gem is a dependency and
runs in-process.
Quick start
bundle exec browsable audit
That's it. With no configuration, browsable:
- reads
ApplicationController'sallow_browserpolicy to learn your target, - discovers your stylesheets, views, JavaScript, and importmap pins,
- audits each against that target, and
- prints a report grouped by file.
CLI reference
| Command | Purpose |
|---|---|
browsable / browsable audit [PATH] |
Full project audit |
browsable doctor |
Check system dependencies |
browsable doctor --fix |
Install missing dependencies (opt-in) |
browsable check FILE [FILE...] |
Audit specific files (used by editors) |
browsable target [PATH] |
Show the inferred browser-support target |
browsable init |
Generate a .browsable.yml (non-Rails projects) |
browsable version |
Print the version |
Flags
| Flag | Effect |
|---|---|
--target QUERY |
Override the inferred browserslist query |
--json |
Emit findings as JSON (shortcut for --format json) |
| `--format human\ | json\ |
--no-build |
Scan only what is on disk (browsable never builds assets itself) |
--include GLOB |
Add a path glob to the audit (repeatable) |
--exclude GLOB |
Exclude a path glob (repeatable) |
| `--fail-on warning\ | error` |
--config PATH |
Override the config file location |
The --json output is the universal interface: the LSP server (and any future
MCP server) consume exactly that structure. The human and GitHub formatters are
just alternate presentations of the same data.
Rails generator
rails g browsable:install
This writes a fully-commented config/browsable.yml — every option present,
commented out, set to its default. It is a self-documenting reference: uncomment
a line to override it. Flags: --minimal, --target QUERY, --force.
Non-Rails projects use browsable init, which writes .browsable.yml instead.
Configuration
browsable needs no config file. When one is present it is discovered in this
order:
- the path passed to
--config config/browsable.yml(preferred in Rails apps).browsable.ymlin the working directory
Resolution precedence (highest wins): CLI flags → config file → inferred Rails
config → gem defaults. See the generated config/browsable.yml for the full,
commented option reference.
How it works — the inference chain
ApplicationController.allow_browser → Target (browserslist query) → Analyzers
:modern chrome 120, safari 17.2 │
▼
config/importmap.rb ─┐ CSS → stylelint
app/assets/** ─┼─→ Sources ─→ files by kind ───────── ERB → Herb + BCD
app/views/** ─┤ HTML → Herb + BCD
app/javascript/** ─┘ JS → eslint
│
▼
Report → Formatter
browsable translates between Rails-land and browserslist-land: it reads
allow_browser :modern, expands it to concrete browser versions, configures
stylelint/eslint with that target, and runs Herb against the bundled MDN
browser-compat-data snapshot for ERB/HTML.
Partial allow_browser policies
If your allow_browser policy is a hash that pins only some browsers — say
versions: { safari: 16.4, firefox: 121 } — Rails leaves every browser you
don't list allowed at any version (it only blocks a browser it was given a
minimum, or false, for). browsable audits exactly the browsers you pinned and
prints a note naming the rest. To audit against more, set an explicit target:
in config/browsable.yml. The same note-and-fall-back-to-defaults behaviour
applies when browsable cannot resolve your policy statically.
Where defaults comes from
When there is no allow_browser policy at all, browsable audits against the
browserslist defaults query —
the "reasonable broad support" baseline the wider frontend ecosystem uses. It is
resolved live from caniuse data when the browserslist CLI is installed
(npm install -g browserslist); otherwise browsable uses a small built-in
approximation and says so in a note. Either way these versions are not a
Rails concept — Rails blocks nothing unless you call allow_browser — and they
are not derived from stylelint or eslint. For a precise, stable target, set
target: in config/browsable.yml.
Suggested allow_browser fix
When an audit finds errors that are purely a version conflict — your code needs
a browser version newer than your policy permits — browsable prints a ready-to-paste
allow_browser line that raises only the offending browsers to the minimum
those features require, leaving every other browser untouched:
Suggested allow_browser policy
allow_browser versions: { chrome: 120, edge: 120, firefox: 125, safari: 17.2, opera: 106 }
firefox: 121 → 125
It is a suggestion, not an instruction: tightening the policy is one fix, changing
the code (a fallback, a @supports rule) is another. browsable reports; you
decide. The suggestion is derived from HTML/ERB findings, which carry exact
version data; it also appears in --json (suggested_policy) and as a GitHub
Actions notice.
Per-controller and per-action policies
Rails lets any controller override allow_browser, and scope the override to
certain actions with only:/except:. browsable scans every file under
app/controllers/ (including concerns/) and lists each allow_browser call
it finds — its versions and any action scope — under Browser policies in the
report.
The audit itself runs against a single target (ApplicationController's policy,
or your config/browsable.yml). browsable does not try to map each frontend
asset to the exact endpoints — and policies — that serve it. CSS and importmap
JavaScript are global assets, included via layout helpers on nearly every page,
so they have no single owning controller action; a per-asset policy graph would
be guesswork. Instead, browsable shows you the whole policy landscape: if a
controller serves shared assets to a broader range of browsers than
ApplicationController, audit against that policy explicitly with --target or
config/browsable.yml. Per-action auditing of app/views/<controller>/
templates against their controller's policy is a planned refinement.
Rake tasks
Inside a Rails app, the railtie registers:
rake browsable:audit— auditapp/assets/builds/as it standsrake browsable:audit:fresh— runassets:precompilefirst, then auditrake browsable:doctor— run the dependency check
browsable never precompiles assets on its own. In CI, compose the pipeline
explicitly: bundle exec rails assets:precompile && bundle exec browsable audit.
Contributing
This gem lives in the browsable/ subdirectory of the
monorepo. To work on it:
cd browsable
bundle install
bundle exec rspec
Refresh the bundled compat data with ruby bin/update-bcd-snapshot.
License
MIT — see the LICENSE at the monorepo root.