@payloadpack/theme
v0.2.4
Published
Admin theme for Payload 4: a warm dark palette, a comfortable density and Lucide icons, switchable from the user menu.
Maintainers
Readme
The theme adds three appearance controls to the user menu: a warm dark palette, a roomier density and Lucide icons. Each person picks their own, and the choice is rendered on the server, so pages never flash.
Install
bun add @payloadpack/themeimport { themePlugin } from '@payloadpack/theme'
export default buildConfig({
plugins: [themePlugin()]
})Then regenerate the import map:
payload generate:importmapAppearance
Open the user menu → Appearance:
The plugin renames Payload's Settings submenu in English only, so other languages keep Payload's translation. To rename it in another language, or pick a different name, set general.settings for that language in your own i18n.translations.
| Control | Options | Default | | ------- | -------------------- | ----------- | | Style | Stock, Neutral | Neutral | | Density | Compact, Comfortable | Comfortable | | Icons | Payload, Lucide | Lucide |
- Neutral gives dark mode a warm near-black palette and swaps Payload's blue selection for neutral greys. In both modes it draws a 2px focus ring in the text color and fills checked checkboxes and selected radios with the text color instead of blue.
- Comfortable sets 14px body text, 36px fields and buttons, 32px menu items and 18px checkboxes and radios, gives dialogs more room, and fixes Payload components that hard-code their sizes.
- Lucide replaces Payload's icons with Lucide and adds icons to the nav and the document menu. The icons are bundled, so nothing loads from a CDN.
Stock, Compact and Payload icons together are untouched Payload.
Choices are stored per browser in cookies named after your cookiePrefix, the same way Payload stores its light/dark theme.
Options
themePlugin({
codeEditorFontSize: 13
})| Option | Default | Description |
| -------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| codeEditorFontSize | 13 | Font size for code editors in Comfortable density. Payload's default is 11. Fields that set admin.editorOptions.fontSize keep their own size. false turns it off. |
Vertical tabs
Give a tabs field the VERTICAL_TABS class to list its tabs in a column beside the fields:
import { VERTICAL_TABS } from '@payloadpack/theme'
const field = {
type: 'tabs',
admin: { className: VERTICAL_TABS },
tabs: [
{ label: 'General', fields: [] },
{ label: 'Advanced', fields: [] }
]
}From 768px wide the tabs stack in a 270px column on the left. Narrower screens keep Payload's horizontal tabs. The layout applies whatever each person picks in Appearance. Payload's tab list moves between tabs with the Left and Right arrow keys, and that doesn't change.
Radio cards
Give a radio field the RADIO_CARDS class to show its options as stacked cards with the radio on the right:
import { RADIO_CARDS } from '@payloadpack/theme'
const field = {
name: 'plan',
type: 'radio',
admin: { className: RADIO_CARDS },
options: ['plus', 'pro', 'enterprise']
}A label can also be JSX. Its first element is the title and any element after it is shown as a muted description:
{ value: 'plus', label: <><strong>Plus</strong><span>For individuals and small teams.</span></> }Nav icons
With Icons set to Lucide, every collection and global in the nav gets an icon. Common names get a matching one automatically: a slug containing page, post, media, user, categor, product, order, form, tag, event or more than 30 other words is recognized. Anything else gets a generic icon.
To choose the icon for your own collection, set --payloadpack-icon on its nav link in your admin stylesheet, for example src/app/(payload)/custom.css. Nav links have the id nav-<slug> for collections and nav-global-<slug> for globals:
#nav-recipes {
--payloadpack-icon: url('/icons/chef-hat.svg');
}Any single-color SVG works. It is used as a mask, so it takes the nav's text color. To use a Lucide icon, copy its SVG from lucide.dev into public/icons/.
Customize
The theme's CSS sits in @layer payload, so your own custom.css overrides it without specificity fights.
Each toolbar sizes its buttons from its own token. All but the menu items and sidebar tabs default to Payload's --button-height:
| Token | Controls |
| ------------------------------------------- | ---------------------------------------- |
| --payloadpack-doc-header-button-height | Title bar with Edit, Versions and API |
| --payloadpack-doc-controls-button-height | Status and actions bar of a document |
| --payloadpack-list-header-button-height | List title bar and its selection bar |
| --payloadpack-list-controls-button-height | List toolbar: Filters, Group by, Columns |
| --payloadpack-row-button-height | Array and block rows |
| --payloadpack-menu-item-height | Popup menu items, 32px |
| --payloadpack-sidebar-tabs-height | Collections and Folders tabs, 38px |
Checkboxes and radios share their size and colors:
| Token | Controls |
| ------------------------------------- | ------------------------------------------ |
| --payloadpack-toggle-size | Box and circle size in Comfortable density |
| --payloadpack-toggle-bg | Unchecked background |
| --payloadpack-toggle-bg-hover | Unchecked background on hover |
| --payloadpack-toggle-border | Unchecked border |
| --payloadpack-toggle-checked-bg | Checked fill |
| --payloadpack-toggle-checked-border | Checked border |
| --payloadpack-toggle-mark | Tick and dot |
| --payloadpack-toggle-ring | Keyboard focus ring |
html {
--payloadpack-row-button-height: 2.5rem;
}Requirements
- Payload
4.0.0-beta.5or later - React 19
