PureAdmin is a Phoenix LiveView component library that wraps the Pure Admin CSS framework into function components. It serves as a drop-in replacement for Phoenix's generated CoreComponents.
Prerequisites
Create a new Phoenix project without Tailwind — Pure Admin provides its own CSS framework:
mix phx.new my_app --no-tailwind
If you have an existing project that uses Tailwind, remove the Tailwind dependency and its configuration before adding Pure Admin, as the two CSS frameworks will conflict.
Installation
Add keen_pure_admin to your mix.exs:
def deps do
[
{:keen_pure_admin, "~> 1.0"}
]
endThen fetch dependencies:
mix deps.get
Setup
1. Replace CoreComponents import
In your MyAppWeb module (lib/my_app_web.ex), find the html_helpers function and replace the CoreComponents import:
- import MyAppWeb.CoreComponents
+ use PureAdmin.ComponentsThis replaces button/1, input/1, simple_form/1, modal/1, table/1, list/1, label/1, flash/1, and flash_group/1. Functions not replaced:
header/1— use@page_titlein<.navbar_title>(layout renders it, LiveView sets it)icon/1— use Font Awesome directly:<i class="fa-solid fa-user"></i>translate_error/1—PureAdmin.Components.Form.translate_error/1ships a plain%{key}-interpolating default. For Gettext, setconfig :keen_pure_admin, :error_formatter, {MyAppWeb.CoreComponents, :translate_error}and errors flow through your existing pipeline.show/1,hide/1— usePhoenix.LiveView.JSdirectly
Also clean up these generated files that use Tailwind classes or CoreComponents functions:
- Delete
lib/my_app_web/components/core_components.ex— no longer needed - Delete
priv/static/assets/default.css— Phoenix default styles that conflict with Pure Admin - Replace
lib/my_app_web/controllers/page_html/home.html.heex— the generated page uses Tailwind classes andLayouts.flash_groupwhich no longer exists
2. Replace the generated layouts
Phoenix generates a layouts.ex with inline app/1 and flash_group/1 functions that conflict with PureAdmin. Replace the entire file:
# lib/my_app_web/components/layouts.ex
defmodule MyAppWeb.Layouts do
use MyAppWeb, :html
embed_templates "layouts/*"
endThen create lib/my_app_web/components/layouts/app.html.heex with a PureAdmin layout:
<.layout>
<.navbar>
<:start>
<.navbar_burger />
<.navbar_brand />
</:start>
<:center>
<.navbar_title>
<h2>{assigns[:page_title] || "Home"}</h2>
</.navbar_title>
</:center>
</.navbar>
<.layout_inner>
<.sidebar>
<.sidebar_item label="Home" icon="fa-solid fa-house" href="/" />
</.sidebar>
<.layout_content>
<.main>
<.flash_group flash={@flash} />
{@inner_content}
</.main>
<.footer />
</.layout_content>
</.layout_inner>
</.layout>Add the app layout to your router's browser pipeline (Phoenix 1.8 doesn't set this by default — the generated code used an inline app/1 function instead):
# lib/my_app_web/router.ex
pipeline :browser do
# ... existing plugs ...
plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
plug :put_layout, html: {MyAppWeb.Layouts, :app} # <-- add this line
# ...
end3. Configure your app (optional)
# config/config.exs
config :keen_pure_admin,
app_name: "My App",
app_version: "1.0.0",
copyright: "© 2026 My Company",
font_class: "pa-font-responsive"The navbar_brand and footer components read from this config automatically. Add the font class to <html> in your root layout:
<html lang="en" {PureAdmin.Config.root_html_attrs()}>4. Install a theme and update the root layout
Declare the themes your app ships with in a pureadmin.json at your project root:
{
"themesDir": "priv/static/themes",
"themes": {
"audi": {}
}
}Then resolve and download them with the PureAdmin CLI:
npx @keenmate/pureadmin themes install
This generates pureadmin.lock.json (commit it — same convention as package-lock.json) and extracts each theme to priv/static/themes/<id>/. CI / Docker builds should run npx @keenmate/pureadmin themes ci instead, which reproduces the lockfile exactly and fails fast if the two files drift. Add priv/static/themes/ to .gitignore — themes are downloaded artifacts.
Add themes to your static paths so Phoenix serves the files:
# lib/my_app_web.ex
def static_paths, do: ~w(assets fonts images themes favicon.ico robots.txt)Then replace the CSS links in lib/my_app_web/components/layouts/root.html.heex. Remove the Phoenix-generated default.css and add the theme CSS, Font Awesome, and Floating UI:
<head>
<%!-- Pure Admin theme (includes core CSS) --%>
<link rel="stylesheet" href="/themes/audi/css/audi.css" />
<%!-- Font Awesome icons --%>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
<%!-- Floating UI (for tooltips, popovers, split buttons) --%>
<script src="https://cdn.jsdelivr.net/npm/@floating-ui/core@1.6.9"></script>
<script src="https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.13"></script>
<%!-- Your app JS --%>
<script defer phx-track-static type="text/javascript" src={~p"/assets/js/app.js"}></script>
</head>
<body>
<.fouc_prevention_script />
{@inner_content}
</body>Browse all available themes at pureadmin.io.
5. Register JS hooks
Add PureAdminHooks to your LiveSocket in assets/js/app.js and call initPureAdminEvents() once to wire the delegated click handlers (popconfirm, popover, copy-to-clipboard, tabs scroll, badge-group expand/collapse):
import { PureAdminHooks, initPureAdminEvents } from "keen_pure_admin"
// Merge with any existing hooks
const liveSocket = new LiveSocket("/live", Socket, {
hooks: { ...colocatedHooks, ...PureAdminHooks }
})
// Wire delegated click handlers. Idempotent; safe to call once at startup.
initPureAdminEvents()All component behaviour is delivered through PureAdminHooks and initPureAdminEvents — no inline onclick= handlers in the rendered markup, so apps can ship with strict CSP (script-src 'self') without 'unsafe-inline'. The one exception is the optional FOUC-prevention script (<.fouc_prevention_script />), which must run inline in <head> before CSS loads; for CSP-strict apps, attach a per-request nonce.
Next Steps
- Browse the component reference for all available components
- Check the JS hooks guide for interactive features
- See the live demo for visual examples