🎯 Autonomous
Self-contained permission checking—no external permission props required.
The DashboardNavigation component provides a centralized, role-based navigation solution for dashboard pages with autonomous permission checking and extensive customization through slots.
DashboardNavigation extracts navigation logic from page layouts into a standalone, reusable component that handles role-based visibility, permission checking, and custom navigation items through a flexible slot system.
🎯 Autonomous
Self-contained permission checking—no external permission props required.
🔌 Slot-Based
Three customization slots (before, default, after) for flexible navigation layouts.
🛡️ Role-Based
Automatic visibility control based on user roles (admin, team_admin, team_manager).
⚡ Performance
Parallel permission checks with optional pre-computed permissions for layouts.
The component renders these navigation items by default:
| Item | URL | Required Role | Always Visible |
|---|---|---|---|
| Orders | /dashboard/orders | team_admin | ❌ |
| Events | /dashboard/events | admin | ❌ |
| Users | /dashboard/users | team_admin | ❌ |
| Clients | /dashboard/clients | team_manager | ❌ |
The simplest usage requires no props—the component handles all permission checking internally:
---import DashboardNavigation from '#components/astro/DashboardNavigation.astro'---
<nav> <DashboardNavigation /></nav>For better performance in layouts where permissions are already computed, pass them to avoid redundant checks:
---import DashboardNavigation from '#components/astro/DashboardNavigation.astro'import { canViewContent } from '#utils/role-guard'
// Compute permissions onceconst [canEditUsers, canEditClients, canEditProducts] = await Promise.all([ canViewContent(Astro.locals, ['team_admin']), canViewContent(Astro.locals, ['team_manager']), canViewContent(Astro.locals, ['team_admin', 'admin']),])---
<DashboardNavigation permissions={{ canEditUsers, canEditClients, canEditProducts, }}/>Use the hideItems prop to hide specific navigation items:
<DashboardNavigation hideItems={['events', 'users']} />This would show only Orders and Clients (if user has access).
The component provides three slots for customization:
Add items before the default navigation:
<DashboardNavigation> <li slot="before"> <Link.LinkButton href="/dashboard/home"> <Icon name="home" /> Home </Link.LinkButton> </li></DashboardNavigation>Add items after the default navigation:
<DashboardNavigation> <li slot="after"> <Link.LinkButton href="/dashboard/settings"> <Icon name="settings" /> Settings </Link.LinkButton> </li></DashboardNavigation>Replace all default navigation items with custom ones:
<DashboardNavigation hideDefaults={true}> <li> <Link.LinkButton href="/custom-dashboard">Custom</Link.LinkButton> </li> <li> <Link.LinkButton href="/reports">Reports</Link.LinkButton> </li></DashboardNavigation>You can combine multiple slots for maximum flexibility:
<DashboardNavigation hideItems={['users']}> <li slot="before"> <Link.LinkButton href="/dashboard/overview">Overview</Link.LinkButton> </li>
<!-- Default items (Orders, Events, Clients) render here -->
<li slot="after"> <Link.LinkButton href="/dashboard/analytics">Analytics</Link.LinkButton> </li> <li slot="after"> <Link.LinkButton href="/dashboard/settings">Settings</Link.LinkButton> </li></DashboardNavigation>type Props = { hideDefaults?: boolean hideItems?: ('orders' | 'events' | 'users' | 'clients' | 'products')[] permissions?: { canEditUsers?: boolean canEditClients?: boolean canEditProducts?: boolean } className?: string debug?: boolean}hideDefaultsbooleanfalsehideItemsArray<'orders' | 'events' | 'users' | 'clients'>[]permissionsType: Object
Default: undefined
Description: Pre-computed permission checks. If not provided, component computes them internally.
canEditUsers: User can view Users (team_admin role)canEditClients: User can view Clients (team_manager role)canEditProducts: User can view Products (team_admin or admin role)Events is not in this list: /dashboard/events only redirects signed-out
visitors, so its link is shown to every signed-in user rather than gated.
classNamestring""booleanfalse| Slot | Description | Example Use Case |
|---|---|---|
before | Content before default items | Add “Home” or “Overview” link |
| default | Replace all navigation | Fully custom navigation |
after | Content after default items | Add “Settings” or “Profile” link |
---import { Auth } from '#layouts'import DashboardNavigation from '#components/astro/DashboardNavigation.astro'import { Link } from '@fpkit/acss'---
<Auth pageTitle="Admin Dashboard" requiredRoles={['admin']}> <nav> <DashboardNavigation> <li slot="before"> <Link.LinkButton href="/dashboard">Dashboard Home</Link.LinkButton> </li> <li slot="after"> <Link.LinkButton href="/dashboard/reports">Reports</Link.LinkButton> </li> <li slot="after"> <Link.LinkButton href="/dashboard/system">System Settings</Link.LinkButton> </li> </DashboardNavigation> </nav></Auth>---import DashboardNavigation from '#components/astro/DashboardNavigation.astro'import { Link } from '@fpkit/acss'---
<DashboardNavigation hideDefaults={true}> <li> <Link.LinkButton href="/portal/overview">Overview</Link.LinkButton> </li> <li> <Link.LinkButton href="/portal/orders">My Orders</Link.LinkButton> </li> <li> <Link.LinkButton href="/portal/support">Support</Link.LinkButton> </li></DashboardNavigation>---import DashboardNavigation from '#components/astro/DashboardNavigation.astro'import { canViewContent } from '#utils/role-guard'
// Check if user is team managerconst isTeamManager = await canViewContent(Astro.locals, ['team_manager'])---
<DashboardNavigation hideItems={isTeamManager ? [] : ['clients']}> <li slot="after"> <Link.LinkButton href="/dashboard/team">Team</Link.LinkButton> </li></DashboardNavigation>Enable debug mode to see permission check results in the console:
<DashboardNavigation debug={true} />Console output:
DashboardNavigation - Computed permissions: { canEditUsers: false, canEditClients: true, canEditProducts: true}When using the component in layouts where permissions are already checked (e.g., Auth layout), pass pre-computed permissions to avoid redundant database queries:
❌ Not Recommended (redundant checks):
---const canEditUsers = await canViewContent(Astro.locals, ['team_admin'])const canEditClients = await canViewContent(Astro.locals, ['team_manager'])const canEditEvents = await canViewContent(Astro.locals, ['admin'])---
<!-- Component will re-check permissions internally --><DashboardNavigation />✅ Recommended (efficient):
---// Compute once, reuse everywhereconst [canEditUsers, canEditClients, canEditProducts] = await Promise.all([ canViewContent(Astro.locals, ['team_admin']), canViewContent(Astro.locals, ['team_manager']), canViewContent(Astro.locals, ['team_admin', 'admin']),])---
<!-- Pass pre-computed permissions --><DashboardNavigation permissions={{ canEditUsers, canEditClients, canEditProducts, }}/>The component uses Promise.all() internally for parallel permission checks, reducing latency by ~66% compared to sequential checks.
The DashboardNavigation component follows a clear separation of concerns:
The Auth layout uses DashboardNavigation for consistent navigation across protected pages:
---import DashboardNavigation from '#components/astro/DashboardNavigation.astro'---
<section class="flex"> <DashboardNavigation /></section>The layout handles page-level protection, while the component handles navigation visibility.
Auth Layout Role Guards
Learn about declarative page protection using Auth layout props.
Configurable Roles
Understand the role system and permission hierarchy.
@fpkit/acss Components
Explore the Nav and Link components used internally.
If you’re migrating from the original Auth layout navigation code:
---const [canEditUsers, canEditClients, canEditEvents] = await Promise.all([ canViewContent(Astro.locals, ['team_admin']), canViewContent(Astro.locals, ['team_manager']), canViewContent(Astro.locals, ['admin']),])---
<Nav> <ul> <li> <Link.LinkButton href="/dashboard/orders">Orders</Link.LinkButton> </li> {canEditEvents && ( <li> <Link.LinkButton href="/dashboard/events">Events</Link.LinkButton> </li> )} {canEditUsers && ( <li> <Link.LinkButton href="/dashboard/users">Users</Link.LinkButton> </li> )} {canEditClients && ( <li> <Link.LinkButton href="/dashboard/clients">Clients</Link.LinkButton> </li> )} </ul></Nav>---import DashboardNavigation from '#components/astro/DashboardNavigation.astro'---
<DashboardNavigation />Benefits:
hideItems to conditionally show/hide based on contextdebug={true} to troubleshoot permission issuesProblem: Expected navigation items don’t appear
Solutions:
<UserInfo showRoleBadge /><DashboardNavigation debug={true} />config/roles.config.tsProblem: Slow navigation rendering
Solutions:
Problem: Slot content doesn’t appear
Solutions:
before, after, or no name for default)hideDefaults={true}<li> for list items)Need help? Check the Auth Layout Role Guards guide or review the component source code.