Skip to content

Navigation

Every panel has a sidebar on the left and a topbar at the top. Anvil builds both from the panel, its pages and resources; you describe labels, icons, groups and order, not the HTML. Entries the user may not open are left out.

┌──────────────┬──────────────────────────────────────────────────────────┐
│ ▣ Brand │ ⌂ › Invoices › INV-2026-0042 🔍 Search ⌘K 🔔 🏢 ◐ (RK) │
│ ├──────────────────────────────────────────────────────────┤
│ ⌂ Dashboard │ │
│ ☑ Inbox 3 │ │
│ SALES │ page content │
│ ▤ Customers │ │
│ ▤ Invoices │ │
│ ▸ SETTINGS │ │
│ « │ │
└──────────────┴──────────────────────────────────────────────────────────┘
Part What it shows Configured in Page
Sidebar one entry per page and resource the user may open page / resource Sidebar
Groups headings and collapsible sections of the sidebar Panel::navigationGroups() Groups
Badges a count or short text next to an entry navigationBadge() Badges
User menu profile, own entries, logout Panel::userMenuItems() User menu
Topbar breadcrumbs, global search, notifications, tenant, theme page, resources, panel Topbar and breadcrumbs
use Anvil\Panel\Navigation\NavigationGroup;
use Anvil\Panel\Navigation\UserMenuItem;
use Anvil\Panel\Panel;
final class AdminPanel extends Panel
{
public function id(): string { return 'admin'; }
public function resources(): array
{
return [CustomerResource::class, InvoiceResource::class, UserResource::class];
}
public function pages(): array
{
return [ReportsPage::class, SettingsPage::class];
}
public function navigationGroups(): array
{
return [
'Sales',
NavigationGroup::make('Reports')->icon('bar-chart')->collapsible(),
NavigationGroup::make('Settings')->icon('gear')->collapsed(),
];
}
public function userMenuItems(): array
{
return [UserMenuItem::page('settings')];
}
}
final class InvoiceResource extends Resource
{
protected string $navigationIcon = 'receipt';
protected ?string $navigationGroup = 'Sales';
protected int $navigationSort = 10;
public function navigationBadge(): ?string
{
$overdue = $this->query()->where('status', 'overdue')->countAllResults();
return $overdue > 0 ? (string) $overdue : null;
}
}
  • Derived, not declared: there is no separate menu definition. A new resource or page appears in the sidebar as soon as it is registered in the panel.
  • Permissions decide: an entry is shown only if the user may open its target – the same check that protects the page. Hiding an entry never replaces that check.
  • Escaped: labels, badges and group names are escaped; icons are Bootstrap icon names without bi-.
  • Translations: labels are plain strings; return lang('…') for multilingual applications, see Extending Anvil.
  • Remembered in the browser: the collapsed sidebar and collapsed groups are stored in the local storage of the browser, the theme in a cookie (anvil_theme); nothing of it on the server.