On this page
  1. Declare the tree
  2. The principles
  3. Scale it to your site
  4. Omission falls back; hiding is explicit
  5. Verify it
  6. Related reference

Organize your admin nav

Cairn’s default sidebar is flat: its own screens and whatever flat entries your site added, one unsectioned list, no group header. That’s a deliberate choice, not an unfinished one. A label costs a reader a category decision on every visit, and a zero-config sidebar is small enough that the decision never pays for itself. It stops being enough once your site adds several screens of its own, because at that point the flat list needs a shape, and the shape that serves the person signing in is what they do, not which screens shipped with cairn and which ones you built. navLayout lets you declare the whole sidebar, cairn’s own screens and yours interleaved, arranged around what your editors actually do. This guide covers the contract and the evidence behind the shape cairn recommends. It assumes Define an adapter and schema and Add a custom admin screen are already done, since the worked example below arranges a custom section alongside cairn’s own.

Declare the tree

navLayout is a plain-data array on the adapter’s editor group, an alternative to adminNav (declaring both throws at construction). Each node is one of three kinds. An engine reference places one of cairn’s own screens by id. A site entry is the same shape adminNav already uses. A section groups a mix of both under one label:

import type { NavLayout } from '@glw907/cairn-cms/sveltekit';

const navLayout: NavLayout = [
  {
    label: 'Club',
    roles: ['owner'],
    children: [
      { label: 'Events', icon: 'calendar', href: '/admin/club/events' },
      { label: 'Members', icon: 'users', href: '/admin/club/members' },
    ],
  },
  {
    label: 'Content',
    children: [{ screen: 'posts' }, { screen: 'pages' }],
  },
  {
    label: 'Site',
    children: [
      { screen: 'media' },
      { screen: 'vocabulary' },
      { screen: 'settings', label: 'Site settings' },
      { screen: 'editors' },
    ],
  },
];

{ screen: 'posts' } places the posts concept’s own list and editor, its icon and href staying engine-owned; { screen: 'settings', label: 'Site settings' } places the built-in Settings screen under a relabel, so it never collides with a same-named screen of your own. roles: ['owner'] renders the Club section only for that role; swap it for whatever your own vocabulary names a club-admin audience once you’ve declared one with defineRoles (see Give a role its own admin area). roles narrows against your declared names the same way, through the CairnRolesRegister augmentation. Wire the tree onto the adapter next to your other editor-experience knobs:

// src/lib/cairn.config.ts
import { defineAdapter } from '@glw907/cairn-cms';

export const cairn = defineAdapter({
  // ...content, backend, email, rendering...
  editor: { navLayout },
});

Every node validates when the runtime composes: an unknown screen id, a screen referenced twice, a nested section, an empty section, an empty relabel, or a roles name outside your vocabulary all throw an actionable error naming the bad node, so a typo fails at server start instead of rendering a broken sidebar. A site entry inside the tree validates the same way an adminNav entry does (the bundled icon allowlist, the built-in href collision); see the navLayout seam for the full contract.

The principles

navLayout enforces structure. It has no opinion of its own about what a good sidebar looks like. These are cairn’s, each grounded in published usability research or a survey of comparable administrator tools rather than house taste:

  • Nouns, not verbs. Name a section after the objects it manages, such as Posts or Members, not the action of managing them, such as Write or Manage people. No study measures verb labels against noun labels for administrator tools specifically, but nothing contradicts the near-universal convention, and a noun-based sidebar is one less thing for a new editor to learn.
  • Flat until it hurts. Skip a section header below roughly eight to ten items; a flat list under that size reads fine unsectioned. Splitting one list into two costs a real, measured category decision on every visit (Omanson, Miller & Joseph 2014), and no study sets a clean threshold for a sidebar this short. Treat “eight to ten” as where practitioner convergence and that measured cost start to outweigh a flat scan, not as a studied number.
  • Group by editor workflow, when you do group. A section named after what your editors do, the Club section in the worked example, reads better to the person using it than one that mirrors your codebase or cairn’s own built-in versus custom split.
  • Content first; settings, roster, and help sink last. What an editor uses daily belongs before what they need only occasionally. Configuration and who-has-access are infrequent, cross-cutting concerns, and every comparable administrator tool trails them the same way.
  • Stable arrangement. One tree, filtered by roles, never rearranged per role beyond subtraction. Reordering a sidebar by usage measurably slows a returning user down; a fixed layout that only drops items for a role performs about as well as one that never varies at all.
  • Don’t over-hide. hidden: true retires a door for good; it isn’t a decluttering tool, and the command palette is an escape valve, never a substitute for visible nav. One controlled study of hidden and collapsed navigation found discoverability down more than 20% and desktop task completion at least 39% slower (NN/g hidden-navigation study).

Scale it to your site

Three sizes cover most sites:

  • Default scale, roughly eight items or fewer. Stay flat. This is cairn’s own zero-config sidebar, and it’s the guide’s own advice against grouping before grouping pays for itself.
  • One growing domain. Once your site adds a real section of its own, lead the sidebar with it and let cairn’s own screens trail behind in a second section, the shape the worked example earlier in this guide follows.
  • Several roles, many screens, 15 or more. Reach for the full arrangement: sections by editor workflow, roles gating each one, settings and roster sunk to the last section, and every colliding name relabeled. This is the shape a large, multi-role site needs and a small one doesn’t.

No aesthetic linting enforces any of this. validateNavLayout catches a structural mistake. The preceding principles are yours to apply.

Omission falls back; hiding is explicit

A navLayout you declare doesn’t have to reference every one of cairn’s own screens. Anything the tree never mentions still renders, in a trailing group after a divider, in cairn’s own screen order (each declared concept, then Library, Tags, the nav-menu editor, Settings, Editors, Help). The preceding worked example never references Help, so Help lands there, in the same foot slot the zero-config sidebar already reserves for it. This means an engine update that ships a new built-in screen surfaces on your sidebar instead of silently vanishing because your tree predates it.

To remove a door on purpose instead, mark it hidden: true:

{ screen: 'vocabulary', hidden: true }

The route stays live; only the sidebar link disappears. That’s deliberate: nav placement is never authorization. Hiding a screen from the sidebar doesn’t stop a signed-in editor from typing its URL directly. Gate the route itself with requireSession, requireEditor, requireOwner, or your own check, the way Add a custom admin screen covers. The roles visibility on a navLayout node is the same kind of courtesy hidden is: it decides what renders, not what’s allowed.

Verify it

Sign in as each role your tree gates and confirm the sidebar shows exactly the sections and items that role should see, in the order you declared. Confirm an unreferenced screen (Help, in the worked example) still renders in the trailing fallback group, and that a screen you mark hidden disappears from the sidebar but its route still answers when you type its URL directly as an editor who should see it.

The navLayout seam documents every node type, validateNavLayout, and resolveNavLayout in full. The custom admin-nav seam documents AdminNavEntry and its icon allowlist, the shape a navLayout site entry reuses. ContentRoutesDeps navFilter is the per-request seam for a grant that depends on state outside cairn’s own role vocabulary, composed after every gate navLayout already applies. Give a role its own admin area covers declaring a role vocabulary with defineRoles in full, the prerequisite for a roles list naming anything beyond owner and editor.

Edit this page on GitHub(opens in a new tab)