Skip to main content

Navigation

Saucebase provides a backend-driven navigation system built on Spatie Navigation. You register menu items in PHP, organize them into groups, and they're automatically shared to your Vue frontend via Inertia props.

How It Works

  • Register items in routes/navigation.php (or a module's routes/navigation.php)
  • Group items by purpose — main, secondary, user, settings, landing
  • Access them in Vue via usePage().props.navigation

The Navigation service loads all files automatically — no manual registration or event listeners needed.

Adding Navigation Items

Use Navigation::add() to register items:

routes/navigation.php
use App\Facades\Navigation;
use App\Navigation\Section;

Navigation::add('Dashboard', route('dashboard'), function (Section $section) {
$section->attributes([
'group' => 'main',
'slug' => 'dashboard',
'order' => 0,
]);
});

Attributes Reference

AttributeTypeDescription
groupstringNavigation group (main, secondary, user, settings, landing)
slugstringUnique identifier for the item
orderintSort order within the group (lower = first)
actionstringJavaScript action to trigger (e.g., 'logout')
externalboolRender as <a> instead of Inertia <Link>
newPageboolOpen in a new tab (target="_blank")
classstringCustom CSS classes
badgearrayBadge config: ['content' => '3', 'variant' => 'destructive']

Examples

routes/navigation.php
use App\Facades\Navigation;
use App\Navigation\Section;

// External link — opens in new tab
Navigation::add('Documentation', 'https://docs.example.com', function (Section $section) {
$section->attributes([
'group' => 'secondary',
'slug' => 'docs',
'external' => true,
'newPage' => true,
'order' => 0,
]);
});

// Badge — show a notification count
Navigation::add('Notifications', route('notifications.index'), function (Section $section) {
$section->attributes([
'group' => 'main',
'slug' => 'notifications',
'order' => 10,
'badge' => [
'content' => '3',
'variant' => 'destructive',
],
]);
});

// Action-based — triggers JavaScript instead of navigating
Navigation::add('Log out', '#', function (Section $section) {
$section->attributes([
'group' => 'user',
'action' => 'logout',
'slug' => 'logout',
'order' => 100,
]);
});

// Custom styling
Navigation::add('Admin', route('filament.admin.pages.dashboard'), function (Section $section) {
$section->attributes([
'group' => 'secondary',
'slug' => 'admin',
'order' => 10,
'class' => 'bg-yellow-500/10 text-yellow-600 hover:bg-yellow-500/20',
]);
});

Conditional Items

Two methods for conditional navigation:

  • addWhen(fn, ...) — Evaluates the callback at render time (every request). Use for conditions that change per-request like auth state.
  • addIf(bool, ...) — Checks the condition once at registration time. Use for static conditions like feature flags or database counts.
// addWhen — re-evaluated every request
Navigation::addWhen(
fn () => Auth::check() && Auth::user()->isAdmin(),
'Admin', route('filament.admin.pages.dashboard'),
function (Section $section) { /* ... */ }
);

// addIf — checked once when navigation loads
Navigation::addIf(
Product::displayable()->count() > 0,
'Pricing', route('pricing'),
function (Section $section) { /* ... */ }
);

Module Navigation

Modules register navigation in their own routes/navigation.php. The file is loaded automatically when the module is enabled in modules_statuses.json.

modules/Settings/routes/navigation.php
use App\Facades\Navigation;
use App\Navigation\Section;

Navigation::add('Settings', route('settings.index'), function (Section $section) {
$section->attributes([
'group' => 'user',
'slug' => 'settings',
'order' => 10,
]);
});

No additional registration is needed — just create the file and enable the module.

Groups organize items by where they appear in the UI. Use the group attribute to assign items:

GroupPurpose
mainPrimary sidebar/header navigation
secondaryLower sidebar items (docs, admin links)
userUser dropdown menu (settings, logout)
settingsSettings page sidebar
landingPublic landing page navigation

You can create custom groups by using any string as the group name. They'll appear in usePage().props.navigation under that key.

Frontend Usage

Navigation is shared via Inertia props, grouped by name:

<script setup lang="ts">
import { Link, usePage } from '@inertiajs/vue3';

const { main, secondary, user } = usePage().props.navigation;
</script>

<template>
<nav>
<template v-for="item in main" :key="item.slug">
<a
v-if="item.external"
:href="item.url"
:target="item.newPage ? '_blank' : undefined"
:class="item.class"
>
{{ item.title }}
</a>
<Link v-else :href="item.url" :class="[item.class, { 'font-bold': item.active }]">
{{ item.title }}
<span v-if="item.badge">{{ item.badge.content }}</span>
</Link>
</template>
</nav>
</template>
interface MenuBadge {
content?: string | number;
variant?: 'default' | 'secondary' | 'destructive' | 'outline';
class?: string;
}

interface MenuItem {
title: string;
url?: string;
slug?: string;
active?: boolean;
action?: string;
external?: boolean;
newPage?: boolean;
class?: string;
badge?: MenuBadge | boolean;
children?: MenuItem[];
}

What's Next?

  • Breadcrumbs — Hierarchical navigation trails
  • Modules — Creating and managing modules
  • Routing — Laravel and Inertia routing patterns