Adds a shadcn-style theme to the Payload admin panel, built on the payload-theme package. All settings are stored in a global and rendered as server-side CSS, so changes apply on save without a restart or rebuild. The stylesheet must be imported in custom.scss and the import map regenerated. Requires Payload 3.88 or newer.
Tema admin Payload bergaya shadcn yang seluruh konfigurasinya diatur dari panel admin
Installation
npm i payload-admin-theme1
Stars
0
Forks
0
Open Issues
1 months ago
Last Updated
0
Weekly Downloads
v1.2.0
npm Version
508.1 KB
Unpacked Size
2
Dependencies
Trends appear after 7 days of data. Daily snapshots have not started yet.
Trends appear after 7 days of data. Daily snapshots have not started yet.
Each point is npm's rolling 7-day download count.
Health Score
Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.
Badge for your README
[](https://payloaddirectory.dev/plugins/1342450007-root)<a href="https://payloaddirectory.dev/plugins/1342450007-root"><img src="https://payloaddirectory.dev/badge/1342450007-root.svg" alt="Payload Directory"></a>README
A shadcn-style admin theme for Payload CMS whose entire configuration lives in the admin panel — not in your code.
Save the form, the panel changes. No restart, no rebuild, no deploy.
Installation · How it works · Options · Limitations · Support
There are two places to configure the theme, and both edit the same data: the palette panel in the header (quick, for experimenting) and the System → Konfigurasi Admin page (complete, including logo and title).
| Area | What you can change |
|---|---|
| Colour | A single accent hex drives the whole panel. The 50–950 scale is computed in OKLCH, and the text colour on top of the accent is picked automatically so its contrast meets WCAG. |
| Shape (shadcn) | Corner rounding from none to full for buttons, inputs, cards, tables and popovers. |
| Typography | Inter, Geist, Helvetica, System, or Payload's own font. |
| Presets | Zinc, Ocean, Forest, Sunset, Berry, Swiss — accent, radius and typeface in one click. |
| Admin title | Shown in the sidebar and appended to the browser tab title. |
| Logo & icon | Uploaded from the media library, with an optional dark-mode variant. The icon doubles as the favicon. |
| Content width | Centred (~1280px) or full-bleed. |
| Navigation | Lucide icons (with an icon picker), groups and visibility per collection/global. Shared by the sidebar, the dashboard and the command palette. |
| Login | Background image (shown as uploaded), optional scrim, heading and supporting copy. |
| Advanced | Raw --pt-* token overrides and custom CSS. |
payload-themeThis plugin builds on top of payload-theme
(MIT, © Lider Bektaş). The entire visual layer — the shadcn stylesheet, the icon
sidebar, the dashboard, the ⌘K command palette, the login layout — comes from
there and is not copied. See NOTICE.md.
What this plugin adds is the configuration layer, and there is exactly one reason for it:
payload-themecomputes its theme once at boot from static options and stores it in the client config. Values coming from the database can never reach it without a restart.
So everything that must change on save was moved onto the path that is re-sent on every request: CSS rendered on the server.
pnpm add payload-admin-theme
One package. payload-theme is an internal dependency of this plugin: its
components are re-exported through payload-admin-theme/client and
payload-admin-theme/rsc, and its stylesheet ships inside
payload-admin-theme/styles.css. Your application never needs to know it exists.
1. Register the plugin in payload.config.ts:
import { payAdminTheme } from 'payload-admin-theme'
export default buildConfig({
plugins: [
payAdminTheme({
// Initial values, used until someone opens the theme page. Optional.
defaults: { accent: '#0d9488', siteTitle: 'Your Site' },
}),
],
})
2. Import the stylesheet in src/app/(payload)/custom.scss:
@import 'payload-admin-theme/styles.css';
This step stays manual because the stylesheet has to be bundled together with
your application's CSS. Injecting it through a runtime <link> would cause a
flash of unstyled panel on first load.
3. Regenerate the import map, then start the app:
npx payload generate:importmap
Open System → Konfigurasi Admin. Change anything, save, and see the result.
"Konfigurasi Admin" global (database)
│ re-read on every request (5s cache, cleared on save)
▼
resolveTheme() ── a half-filled document → final values, never throws
▼
buildTheme() ── one hex → OKLCH 50–950 scale + light/dark tokens
▼
buildCSS() ── tokens, radius, font, branding, custom CSS
▼
<style> rendered on the server on every admin page
The cascade order this relies on:
@layer payload — colour and radius tokens. Payload declares the layer
order payload-default, payload, so this block beats Payload's defaults
without !important, and still loses to your project's own unlayered CSS.--font-body and the branding rules. Payload declares
--font-body outside any layer, so a layered declaration would always lose.The static ThemeProvider from payload-theme is removed, so there is no
second token block competing with this one.
The header palette panel and the Warna & Bentuk tab edit the same fields.
The customiser that ships with payload-theme is not used: it stores its
choices in localStorage per browser and applies them as inline styles on
<html>. Inline styles always beat a stylesheet, so the moment anyone touched
it, the database theme stopped applying in that browser — and the Warna & Bentuk
tab had no way of knowing what that person was actually looking at.
The replacement writes every change to the global through the REST API and then
triggers router.refresh(). The consequence is deliberate: anything changed
from the header applies to every user, not just the browser that changed it.
Leftover localStorage from the old customiser is cleared automatically on load.
The other three header buttons (collapse sidebar, light/dark, user menu) behave exactly as before. Light/dark mode is deliberately not stored in Konfigurasi Admin: it is a per-user preference, and it lands in the same place as the "Admin Theme" radios on the Account page.
From strongest to weakest:
defaults from the plugin optionsdefaults#4f4ece, radius md, Payload's font)Rule 2 sits above rule 3 on purpose: selecting a preset in the panel is an explicit action someone just took, so it has to beat a default written in code.
A cleared text field is treated as not set, not as an empty value — clearing
the accent returns you to defaults, not to the library default. Boolean false
and the number 0 are still honoured as deliberate choices.
| Option | Default | Description |
|---|---|---|
defaults | {} | Initial values used until the global has been saved. |
globalSlug | admin-theme | Slug of the theme global. |
adminGroup | System | Sidebar group for the theme global. |
themeOptions | {} | Passed straight through to payloadTheme() (see below). |
installBaseTheme | true | Disable if you call payloadTheme() yourself. |
access | — | read / update access for the theme global. |
disabled | false | Stop enforcing the theme; the global remains so the database schema stays consistent. |
Two things cannot move into the panel, and that is not a design choice: both
are resolved when generate:importmap runs, not per request.
payAdminTheme({
themeOptions: {
// Lucide icons per collection/global
nav: { icons: { media: 'image', posts: 'newspaper', users: 'users' } },
// Your own dashboard widgets
dashboard: { widgets: ['/components/widgets/Stats#Stats'] },
},
})
payload-themeThis package builds on payload-theme
and re-exports everything it exports, through payload-admin-theme/client
and payload-admin-theme/rsc. Your application installs one package; the base
theme is an implementation detail.
Component paths registered by the base theme are rewritten from
payload-theme/* to payload-admin-theme/* so they resolve from your app —
under pnpm's strict layout they would not otherwise, because the base theme is
not your dependency.
Two rules keep that rewriting honest, and both are covered by tests:
export * rather than a
hand-written list. A new component in the base theme is therefore available
under the rewritten path automatically.client and rsc. A path from
any other subpath is left pointing at the original package, because failing
to resolve payload-theme/other is far easier to trace than resolving to a
module of ours that exists but has no such export.Version 1.1.1 and earlier used a hand-written re-export list, and
[email protected] broke it: the new AvatarProvider was rewritten to an
export that did not exist, and the admin panel answered 500. If you pinned the
base theme to work around that — pnpm.overrides or similar — you can remove
the pin.
A single full-height screen split 2 : 1 — two parts image, one part form.
The background image is uploaded from the Login tab and shown exactly as
uploaded: the accent glow and grid overlays that payload-theme paints on the
brand panel are switched off while an image is present, because both were
designed for a plain dark panel and read as a colour cast over a photograph.
An optional dark scrim is available on the same tab and defaults to 0. Raise it only if the heading and tagline — both white — get lost against a bright image.
Below 900px the image is hidden and the form returns to a single column — the
payload-theme behaviour, deliberately left untouched.
The Navigasi tab controls the icon, group and visibility of each
collection/global. Icons are chosen through a shadcn-style picker: a popover with
search over a grid of Lucide icons. Only the icon name is stored and it is
loaded through DynamicIcon, so none of the ~1500 icons end up in your bundle.
The same settings drive the sidebar, the dashboard cards and the command palette, so all three stay in step.
This works differently from the rest of the theme. Icons and groups are not CSS —
they are part of the Payload config (admin.custom.payloadTheme.nav.icons and
collection.admin.group). The plugin writes into the live config and then clears
Payload's client-config cache via _payload_doNotCacheClientConfig, the switch
Payload itself provides for runtime config changes. This is safe because the
value is not per-user: one navigation setting applies to everyone.
payload-theme
Dashboard component has no concept of groups, and a pseudo-element cannot be a
grid item of its own. Cards are still grouped and ordered to match the
sidebar via CSS order — they simply have no heading above each group.background-image. The payload-theme
sidebar is a client component that reads its logo from a config frozen at boot,
so the <img> inside it cannot change on save. Painting it through CSS moves
the logo onto the path that is re-sent on every request. As a consequence the
sidebar logo has no alt text — the logo link already carries an aria-label
from Payload.content on a pseudo-element, for the
same reason. The text is escaped so a title containing quotes cannot break the
stylesheet.<head> through Next's generateMetadata from admin.meta, which is read from
the config and never sees the database.[email protected] publishes extensionless ESM imports. Valid under a
bundler (Next) and under Payload's CLI loader, invalid under plain Node. If you
run the config through plain Node — Vitest, for example — inline the package:
test: { server: { deps: { inline: ['payload-theme'] } } }.link: or a workspace, the plugin and the application
must share a single copy of react, react-dom, @payloadcms/ui and
@payloadcms/next. Otherwise React context becomes two separate instances,
useConfig() returns undefined, and the admin panel fails to render. For npm
installs pnpm resolves this through peerDependencies; for local development,
keep the application and the plugin in one pnpm workspace.pnpm install
pnpm test # 49 unit + 24 integration tests (in-memory SQLite)
pnpm build
pnpm watch # rebuild dist while developing against an application
If this plugin saves you time, you can support its development:
MIT © Suryo Galih Kencana Harianja
See NOTICE.md for the payload-theme attribution.
Topics
payload theme
shadcn-style redesign of the Payload admin panel driven by one accent color, with dashboard, command palette and dark…
69909/wk26v3
theme management
Theme presets, live preview and WCAG contrast audit managed from the Payload admin, with SSR theme injection for Next.js
650/wk9v3
payload nav studio
Replace the Payload admin sidebar with a drag-and-drop navigation that each user can customize
640/wk13v3
payload enhanced sidebar
Tabbed sidebar navigation for the Payload admin panel that groups collections, globals and custom links
630/wk29v3
blockwright
Visual drag-and-drop page and theme builder for Payload with widgets, forms, menus and templates
610/wk3v3
payload plugin
Payload plugin adding layouts, templates, menus, forms and a visual editor for the Blockwright page builder
600/wk3v3