shell · Shell
BottomNavbar
@devalok/shilp-sutra/shell/bottom-navbarView in Storybook Hand-curated previews ship in rolling waves. See it live in Storybook →
Reference
- Import: @devalok/shilp-sutra/shell/bottom-navbar
- Server-safe: No
- Category: shell
Props
currentPath?: string (optional)
user?: BottomNavbarUser | null (drives per-item role gating, optional)
primaryItems?: BottomNavItem[] (max 4 recommended, optional)
moreItems?: BottomNavItem[] (overflow items in "More" sheet, optional)
indicator?: 'pill' | 'underline' | 'tint' | 'none' (active-item indicator; default 'pill')
labelVisibility?: 'always' | 'selected'
className?: string
BottomNavItem: { title: string, href: string, icon: IconInput, activeIcon?: IconInput, exact?: boolean, badge?: number, roles?: string[], canView?: (user: BottomNavbarUser | null) => boolean } BottomNavbarUser: { name: string, role?: string }
Defaults
currentPath: '/'
user: null
primaryItems: []
moreItems: []
indicator: 'pill'
labelVisibility: 'always'
Example
<BottomNavbar
currentPath="/dashboard"
primaryItems={[
{ title: 'Home', href: '/', icon: <IconHome /> },
{ title: 'Tasks', href: '/tasks', icon: <IconChecklist /> },
]}
/>Composability
- Mobile-only bottom nav — fixed to bottom of viewport. Desktop should use Sidebar instead.
- Responsive switch pattern: Use
useIsMobile()hook to conditionally render Sidebar (desktop) or BottomNavbar (mobile). Example:const isMobile = useIsMobile() return isMobile ? <BottomNavbar ... /> : <Sidebar ... /> - Primary vs overflow:
primaryItems(max 4) for the always-visible slots;moreItemsgo into a "More" sheet that opens on tap. Don't exceed 4 primary — the bar becomes cramped. - Router integration via LinkProvider: Each nav item is rendered using the framework-specific Link component registered in LinkProvider. Without LinkProvider, you get full-page reloads on tap.
- Badge numbers cap at 99+ (composes the
Badgeprimitive). - Role gating: each item may declare
roles: string[](shown only whenuser.rolematches) or acanView(user)predicate for arbitrary logic (canViewwins). Items with neither are always visible. Gating applies to bothprimaryItemsandmoreItems. - Overflow sheet: the "More" surface is the DS
Sheet(side="bottom") — it inherits focus trap, scroll lock, return-focus, andaria-modal; the trigger is wired witharia-haspopup/aria-controlsautomatically. - Indicator (animated): the active indicator slides to the selected item (shared-element
layoutId) and fades in on first appearance. Modes:pill(default, Material-3 tonal pill behind the icon),underline(top accent bar),tint(subtle bg on the whole active cell),none(no shape — pair withactiveIconfor the iOS filled-icon look).labelVisibility="selected"shows labels only for the active item. - Filled-when-active: set
activeIconon an item (e.g. a Tabler*Filledvariant) to swap the icon for a filled version while it's the active route; falls back toicon. - Not for desktop: The viewport-fixed positioning + touch-optimized sizing don't translate well to desktop. Hide behind
md:hidden.
Gotchas
- Designed for mobile viewports — fixed to bottom of screen
- Max 4
primaryItemsrecommended; overflow goes inmoreItemsshown in a "More" sheet - Use with
useIsMobile()hook to conditionally render instead of Sidebar - Requires LinkProvider for framework-specific link components (e.g., Next.js Link)
Changes
v0.53.0
- Changed Overflow "More" menu re-founded on the DS
Sheetprimitive — inherits focus trap, scroll lock, return-focus,aria-modal, and trigger↔panel ARIA wiring (was a hand-rolledrole="dialog"with none of these). ComposesBadgefor notification counts and the Sheet's built-in close (≥ touch target). - Added Per-item role gating:
roles?: string[]andcanView?: (user) => booleanonBottomNavItem. The previously-inertuserprop now drives it. - Added
activeIconper item — a filled/alternate icon shown while active (falls back toicon). Tightened the icon lozenge padding so icon-only items read less airy. - Added
indicator(defaultpill— Material-3; plusunderline,tint,none) andlabelVisibility('always' | 'selected'). The active indicator animates (slides) between items via a shared-elementlayoutId. - Added Label truncation + logical (RTL-safe) properties; overflow grid adapts to item count.
- Fixed Notification badge
zoom-inanimation now reduced-motion gated.
v0.19.0
- Changed Background elevated from
bg-surface-1tobg-surface-2for visual hierarchy above app background - Changed "More" menu and interactive items bumped accordingly
v0.18.0
- Fixed Removed incorrect
role="button"andtabIndexfrom overlay
v0.16.0
- Added
badge?: numberonBottomNavItem— notification count badge (red dot, 99+ cap)
v0.1.1
- Changed Decoupled from Next.js via LinkProvider
v0.1.0
- Added Initial release