v0.4.0 Per custom item components, tab position, items sorting
Summary of changes
Three additive, opt-in features for the sidebar. All changes are backwards compatible — existing configs render exactly as before.
1. Per-item custom component (customItems[].component)
A customItems entry can now render your own component in the nav row instead of a default link. The component is rendered through Payload's component system, so both server and client components are supported, and it's registered in the import map automatically.
customItems: [
{
slug: 'storage-meter',
component: './components/Sidebar#StorageMeter', // or { path, clientProps }
position: 'top', // honors the same group/position rules as link items
group: 'Content',
},
// regular link items keep working alongside it
{ slug: 'drafts', href: '/collections/posts?status=draft', label: 'Drafts' },
]- Props are deliberately minimal — the component receives
CustomNavItemComponentProps({ slug }) plus anyclientProps. For rich, shared link rendering usecustomComponents.NavIteminstead. - Supports the same
group/positionplacement as link items. When merged into an existing group, it is appended to the bottom of that group (same as link items). - Access control (
access) works the same as link items.
New exported type: CustomNavItemComponentProps.
2. Per-tab ordering (sort)
A new top-level sort option gives full control over the order of groups and items per tab, applied as a final pass over the default ordering. Tabs not listed are untouched.
payloadEnhancedSidebar({
sort: {
// keyed by tab id
shop: {
// orders the top-level groups within the tab
groups: (group, ctx) => {
if (group.isUngrouped && group.entities[0]?.slug === 'banner') return -100
if (typeof group.label !== 'string' && group.label.en === 'Featured') return 0
},
// orders the entities inside each group
items: (item, group, ctx) => {
if (item.type === 'custom-component') return 10 // place after collections
if (item.slug === 'products') return -10 // pin products first
},
},
},
})Sort key semantics (like CSS order):
number— explicit order index; lower comes first.string— sorted lexicographically (localeCompare).undefined— treated as0, i.e. keeps the default position.- Sorting is stable — anything you don't assign a key to keeps its default position. Avoid mixing numbers and strings in the same scope (numbers sort before strings).
Notes:
- Group/entity labels are passed raw (not translated) — branch on
group.label.enetc., or translate viactx.locale. itemssorts only within a group — it cannot move an item to another group (usegroupfor that).- When
groupsis set, ungrouped items become individual single-item units, so you can interleave them between real groups (e.g. a banner above one group, a CTA below another). This is visually a no-op for the default rendering. sortis a final pass and overridescustomItemsposition.- The tab bar order (icons on the left) is unaffected — that's simply the order of the
tabsarray.
New exported types: TabSortConfig, GroupSortFunction, ItemSortFunction, SortableGroup, SidebarSortKey, SidebarSortContext (plus ExtendedEntity, ExtendedGroup).
3. Tab bar position
Any tab, link, or custom slot can now be pinned to the bottom of the tabs bar — just above the actions area (folders / settings / logout) — via position: 'bottom' (default 'top').
tabs: [
{ id: 'dashboard', type: 'link', href: '/', icon: 'House', label: 'Dashboard' },
{ id: 'content', type: 'tab', icon: 'FileText', label: 'Content', collections: ['posts'] },
// pinned to the bottom, above the logout button
{ id: 'settings', type: 'tab', icon: 'Settings', label: 'Settings', globals: ['site-settings'], position: 'bottom' },
]- Bottom items render in config order, grouped together just above the actions.
- Known limitation: the tab bar is not scrollable/virtualized —
positionis intended for a small number of pinned items (settings, help, etc.). A large number of top tabs may collide with the bottom group.
Full Changelog: v0.3.3...v0.4.0