DEV Community

Lider Bektaş
Lider Bektaş

Posted on • Originally published at github.com

I turned Payload CMS's admin panel into a shadcn-style dashboard — in 2 lines

Payload is, in my opinion, the best headless CMS in the Node ecosystem. The code-first config, the local API, the TypeScript story — all excellent.

But the admin panel looks like a database UI. Clients notice. Editors notice. And every time I shipped a Payload project, I found myself apologizing for the dashboard.

So I built payload-theme — a plugin that turns the stock panel into something people screenshot, without forking a single component.

Before and after payload-theme

Same panel, same data — the only difference is one plugin.


The whole install is 2 lines

pnpm add payload-theme
Enter fullscreen mode Exit fullscreen mode

Line 1 — add the plugin to payload.config.ts:

import { payloadTheme } from 'payload-theme'

export default buildConfig({
  plugins: [
    payloadTheme({ accent: '#0d9488' }),
  ],
})
Enter fullscreen mode Exit fullscreen mode

Line 2 — import the stylesheet in src/app/(payload)/custom.scss:

@import 'payload-theme/styles.css';
Enter fullscreen mode Exit fullscreen mode

Regenerate the import map, restart, done:

npx payload generate:importmap
Enter fullscreen mode Exit fullscreen mode

Remove the two lines and you're back to stock. No config surgery, no ejecting.


One accent color drives everything

You give it a single hex. The plugin builds an 11-step OKLCH scale at startup and recolors buttons, focus rings, the active nav pill, the login glow, sparklines — everything interactive.

OKLCH matters here: it's perceptually uniform, so the scale stays balanced whether your accent is a bright yellow or a deep purple. It also picks the text color on top of your accent automatically (WCAG contrast) — yellow accent gets black text, purple gets white.

The color math runs once at startup and lands as CSS custom properties. Zero runtime cost, SSR-safe, no FOUC.


What you actually get

A dashboard that's actually a dashboard — one stat card per collection with an animated count, a 30-day creation sparkline and a trend chip (+12% vs last month). Server-rendered through Payload's local API, so access control applies and there's no loading flash.

Dashboard

A ⌘K command palette — press ⌘K / Ctrl+K anywhere. Your five most recent documents wait at the top, search across collections as you type, create a new doc in any collection, toggle light/dark, or log out. No extra dependencies.

Command palette

A live theme customizer in the header — anyone can restyle the panel at runtime: six one-click presets, accent swatches + free hex, the full radius scale, four typefaces, light/dark and content width. And Copy config turns whatever's on screen into a paste-ready payloadTheme({ ... }) snippet — so the customizer doubles as your config generator.

Theme customizer

A sidebar that reads like a product — your logo up top, grouped collections with lucide icons, an accent pill on the active item, and a shadcn-style user block pinned to the bottom. Collapses on desktop, becomes a sliding drawer on mobile.

Edit views that look like real forms — a two-column field grid, shadcn-style inputs with accent focus rings, checkboxes rendered as toggles, and a sticky action bar that blurs the content scrolling underneath.

Edit view

Plus: a sticky document outline that follows your scroll, blocks rendered as a clean page-outline with per-type icons, an illustrated empty state, a real media grid, and a split-screen login you can brand.


Dark mode, designed — not inverted

Every surface, badge, card and glow is token-driven. Nested surfaces get lighter as they stack (no dark "wells"), and the accent is remapped so it stays vivid on dark backgrounds instead of being flatly inverted.

Dark edit view


How it works (for the curious)

It's a three-layer approach:

  1. Theme engine — the OKLCH scale + contrast + dark remapping, zero runtime deps, injected as CSS custom properties.
  2. Restyle layer — every component restyled with CSS inside @layer payload, so it overrides Payload's defaults without a specificity war. No component swaps here — the stock inputs, selects and tables just become shadcn.
  3. Replacement layer — the few things CSS can't do (custom sidebar, dashboard widgets, command palette) via Payload's admin.components API.

The design reference throughout is shadcn/ui — thin calm borders, an offset accent focus ring, consistent component heights, a muted secondary-text hierarchy.


Try it in 60 seconds

The repo ships a full demo panel — six collections, every field type, block-built pages, a seeded media library:

git clone https://github.com/liderbektas/payload-theme
cd payload-theme && pnpm install && pnpm build
cp dev/.env.example dev/.env
pnpm seed && pnpm dev
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000/admin, log in with dev@local.test / test1234, and play with the header's theme customizer live.


Links

If you're running a Payload project, I'd love for you to try it and tell me what breaks. ⭐ and feedback both very welcome.

Top comments (0)