active_admin_prism

Prism is a plug-and-play ActiveAdmin theme: a collapsible left sidebar with grouped navigation, card-style panels and tables, and reskinned Formtastic forms — no Sass compiler, no JS bundler, no manual asset manifest edits.

→ See INTEGRATION.md for the full integration guide (configuration reference, feature-by-feature usage, switching/disabling the theme, troubleshooting). This README is a quick start.

Live demo

A live demo dashboard is running at prism-demo.onrender.com/admin:

Username: admin@example.com
Password: password

The demo is hosted on Render's free tier, so the first request after a period of inactivity can take up to a minute while the instance spins up.

Requirements

  • ActiveAdmin >= 3.0, < 4
  • Rails >= 7.0
  • Ruby >= 3.1

Install

Add to your Gemfile:

gem "active_admin_prism"
bundle install
rails g active_admin_prism:install

That's it. The generator:

  • adds one line — ActiveAdminPrism.enable! — to your config/initializers/active_admin.rb. The theme's CSS and JS are registered automatically by the gem's Rails engine; there's no Sass/webpack build step required, regardless of whether your app uses Sprockets or Propshaft.
  • if you have app/assets/stylesheets/active_admin.scss (generated by rails g active_admin:assets, i.e. you're on the Sprockets/Sass path, not Webpacker/importmap), adds one @import line there too — see below for what it does.

If you'd rather do either by hand, skip the generator and just add ActiveAdminPrism.enable! anywhere in your config/initializers/active_admin.rb (after the ActiveAdmin.setup block is fine), and @import "active_admin_prism/variable_overrides"; before @import "active_admin/mixins"; in your active_admin.scss.

What it changes, and how

Prism follows a "light touch" philosophy: it changes as little of ActiveAdmin's internals as possible, so it keeps working across ActiveAdmin upgrades.

  • SidebarActiveAdminPrism.enable! swaps ActiveAdmin's header slot (normally the top nav bar) for ActiveAdmin::Views::PrismSidebar, using ActiveAdmin's own supported view-registration mechanism (ActiveAdmin::ViewFactory.register). It renders your existing menu do |m| ... end blocks from app/admin/*.rb — nested groups, priority, if: — completely unchanged; only how that menu is drawn is different. No other page structure (title bar, content, footer) is touched.
  • Panels, tables, buttons, forms — pure CSS reskin of ActiveAdmin's existing markup (.panel, .index_table, Formtastic's li.input.*, etc.). No Ruby view overrides here at all.
  • Icons — a small curated set of inline SVG icons (no icon font, no build step). Available anywhere in your admin views as prism_icon(:cart).
  • Confirm dialogs — every data-confirm link (row-level View/Edit/Delete, and anything else using Rails UJS's data-confirm) is routed through ActiveAdmin's own styled jQuery UI dialog — the same one it already uses for Batch Actions — instead of the native, unstylable browser confirm(). This overrides $.rails.allowAction, jquery-ujs's own documented extension point for custom confirm dialogs; Batch Actions links are left untouched since they already run their own confirm flow. No-ops gracefully if your app doesn't use jquery-rails/jquery-ujs.

Configuration

Every feature is individually toggleable via ActiveAdminPrism::Configuration — including a sidebar flag that lets you keep ActiveAdmin's stock top nav while still getting everything else, and a full off switch for reverting to stock ActiveAdmin without uninstalling the gem. All default to on:

ActiveAdminPrism.configure do |config|
  config.sidebar = true                 # swap AA's top nav for the Prism sidebar
  config.colorize_action_icons = true   # color-coded View/Edit/Delete icons
  config.styled_confirms = true         # route data-confirm through the styled dialog
  config.collapsible_filters = true     # "Filters" panel collapses to an icon until clicked
  config.sidebar_footer = true          # "Powered by Active Admin" moves into the sidebar
  config.flash_dismissible = true       # dismiss (x) button on flash messages
  config.flash_auto_dismiss = true      # auto-hide flash messages
  config.flash_auto_dismiss_seconds = 5
  config.flash_transition_ms = 320      # fade/slide duration when a flash is dismissed
  config. = true              # centered card + brand mark on the sign-in page
  config. = nil               # nil = Prism's built-in mark; or a String/Proc/Symbol
  config. = nil           # nil = falls back to AA's own config.site_title
  config. = "Sign in to your admin dashboard"
  config.language_switcher = true       # "Languages" dropdown near the top of the sidebar
  config.languages = [                  # defaults to these 3 — replace freely
    { label: "English", locale: :en },
    { label: "Español", locale: :es },
    { label: "Français", locale: :fr }
  ]
end

ActiveAdminPrism.enable!

See INTEGRATION.md for the full reference, and INTEGRATION.md for switching the whole theme on/off (including a full revert to stock ActiveAdmin, gem still installed).

Menu items are still defined the normal ActiveAdmin way. Two extra, optional conventions:

# app/admin/dashboard.rb / any resource file
menu label: "Orders", priority: 2, html_options: { icon: :box }

menu label: -> { "Messages #{(:span, unread_count, class: 'nav-badge')}".html_safe },
     html_options: { icon: :message }

icon: (inside html_options: — ActiveAdmin's own MenuItem already passes this hash through untouched, so no new DSL is introduced) accepts any symbol from the bundled set: :dashboard, :users, :cart, :box, :receipt, :credit_card, :message, :settings, :home, :list, :folder, :bell, :search, :menu, :logout, :check, :x, :chevron_down, :eye, :pencil, :trash, :filter, :globe.

Language switcher

A "Languages" dropdown renders near the top of the sidebar (below the brand, above the Pages nav) — no admin.build_menu :utility_navigation code needed. Ships with 3 languages; replace the list freely:

ActiveAdminPrism.configure do |config|
  config.languages = [
    { label: "English", locale: :en },
    { label: "Deutsch", locale: :de },
    { label: "日本語", locale: :ja }
  ]
end

By default, each option links to the current page with ?locale=xx appended (the same convention you'd use by hand with url_for(locale: ...)) — actually switching I18n.locale based on that param is your own app's responsibility (typically a before_action), same as it would be if you'd wired the menu up yourself. Give any entry its own url: (String/Proc/Symbol) to hit something else instead, e.g. a remote endpoint that sets the locale server-side:

{ label: "日本語", locale: :ja,
  url: -> { "https://example.com/set_locale?locale=ja&return_to=#{request.path}" } }

Turn the whole thing off with config.language_switcher = false.

Toggle switches

Boolean fields render as regular (reskinned) checkboxes by default. Opt in to a switch-style toggle per field:

form do |f|
  f.input :active, as: :prism_toggle
end

This only changes the wrapping markup — submitted params are identical to as: :boolean.

For index columns and show attributes_table rows, use the matching read-only prism_toggle_tag helper in place of ActiveAdmin's default status_tag "Yes"/"No" pill:

index do
  column :active do |product|
    prism_toggle_tag product.active
  end
end

show do
  attributes_table do
    row :active do |product|
      prism_toggle_tag product.active
    end
  end
end

It's an explicit opt-in per column/row (like everything else in Prism) — ActiveAdmin's default boolean rendering is untouched everywhere you don't call it.

Sign-in page

Every Devise auth page (sign in, sign up, forgot/reset password, resend confirmation, resend unlock — whichever of these your app actually uses) gets a centered card, an animated brand mark, a full-width submit button, and a row of pill-shaped links instead of ActiveAdmin's default gradient header box — no setup needed. The icon and app name are shared across all of them; the tagline underneath is sign-in's own configurable line, while every other page just names its own action ("Sign up", "Forgot your password?", etc.):

ActiveAdminPrism.configure do |config|
  config. = -> { image_tag "my_logo.svg", height: 40 }
  config. = "My Company Admin"
  config. = "Internal operations console"
end

login_logo/login_app_name/login_tagline each accept a String, Proc (instance_exec'd in the view — image_tag/prism_icon are both available), or Symbol (a method name) — the same convention ActiveAdmin itself uses for config.footer. Turn the whole thing off with config.login_page = false to keep ActiveAdmin's original plain login box. See INTEGRATION.md for the Gemfile-ordering note behind how this view override works.

Error pages (404/500)

rails g active_admin_prism:error_pages

Copies Prism-styled public/404.html and public/500.html into your app. This one is a separate, explicit step (not part of install, and not config-driven) because static exception pages are read straight off disk by Rails — there's no engine hook to override them automatically, and a host's existing public/404.html/500.html are usually already customized, so silently touching them on every install run would be surprising. The generator prompts before overwriting either file. Once copied, they're plain static HTML/CSS with no Ruby behind them — edit them directly for further changes.

Customizing colors

The shipped prism.css is compiled from Sass variables (see scss-src/_variables.scss in this gem's source) such as $prism-color-primary, $prism-bg-page, $prism-sidebar-width. To use your own palette, fork the SCSS source, adjust the variables, and run bin/build-css to produce your own prism.css — see CONTRIBUTING below.

ActiveAdmin's own default colors

Prism reskins ActiveAdmin's existing markup with CSS, but a few things ActiveAdmin renders itself — the jQuery UI batch-actions confirm dialog, the dropdown menu popup, any custom page you write with panel/section-background — are built from ActiveAdmin's own Sass variables (active_admin/mixins/_variables.scss and _gradients.scss), not from anything a precompiled CSS file can reach into.

Rather than chase every individual AA component that might leak its default gray through, the gem ships variable_overrides.scss — Prism's values for every one of those variables. Since they're all declared !default in the activeadmin gem, @import-ing this file before @import "active_admin/mixins" in your active_admin.scss means everything ActiveAdmin generates from that point on inherits Prism's palette as its baseline instead of gray:

// app/assets/stylesheets/active_admin.scss
@import "active_admin_prism/variable_overrides";
@import "active_admin/mixins";
@import "active_admin/base";

The install generator adds this line for you automatically if this file exists in your app. Every variable in variable_overrides.scss is itself !default, so you can customize further — set your own value (including $sidebar-width, which it widens from AA's default 270px to 320px to fit a "select + input" filter row comfortably) before that @import and it wins.

This only works inside your own app's Sass compilation (where active_admin/mixins is actually @import-ed) — it's a one-time addition to your app, not something a precompiled CSS file can do on your behalf.

Development

bin/build-css        # compiles + minifies scss-src/*.scss -> app/assets/stylesheets/active_admin_prism/prism.css
bin/build-js         # minifies js-src/prism.js -> app/assets/javascripts/active_admin_prism/prism.js
bundle install
bundle exec rspec    # or: rake

Never hand-edit the committed prism.css/prism.js — edit scss-src/*.scss/js-src/prism.js and rebuild. Both committed assets are shipped pre-minified (not just for Sprockets hosts, who'd normally re-minify on their own assets:precompile anyway, but for Propshaft hosts too, since Propshaft has no built-in minification step at all) — all the documentation comments explaining the theme's various CSS-specificity fixes etc. live in the scss-src/js-src sources, not the compiled output.

The test suite boots a minimal Rails + ActiveAdmin + Devise app (spec/dummy) and exercises the gem against real HTTP requests — no mocking of ActiveAdmin/Arbre internals. See spec/rails_helper.rb for how it's wired up.

License

MIT