Side Navigation Block
SideNavigation is a collapsible side menu built on MUI Stack (component="aside"), with optional floating overlay mode and data-driven links.
Installation
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-side-navigation-block
yarn add @nodeblocks/frontend-side-navigation-block
pnpm add @nodeblocks/frontend-side-navigation-block
bun add @nodeblocks/frontend-side-navigation-block
What You Need
| Item | Why it matters |
|---|---|
links | Navigation items (href, optional text, icon, isActive, unreadCount) |
header (optional) | Top link with optional icon and label |
isMenuOpen / setIsMenuOpen (optional) | Control open state together; omit both to use internal state (starts closed) |
isFloating (optional) | Floating drawer with portal, backdrop, and close button (false by default) |
Pass isMenuOpen and setIsMenuOpen together when you control the menu from the parent. If you omit both, the block keeps its own state (closed until the menu button is clicked).
Examples use inline mode (isFloating={false}) and start with the menu open so the sidebar is visible in the doc preview. Use isFloating for a drawer with portal and backdrop (see Storybook).
Code Examples
- Quick Start
- Links and layout
- Compound Components
- Block Override
function Example() { const [isMenuOpen, setIsMenuOpen] = React.useState(true); return ( <div style={{ minHeight: 320 }}> <SideNavigation isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} header={{ icon: HomeOutlined, text: 'Home', href: '#home' }} links={[ { icon: DashboardOutlined, text: 'Dashboard', href: '#dashboard' }, { icon: ShoppingCartOutlined, text: 'Products', href: '#products' }, { icon: SettingsOutlined, text: 'Settings', href: '#settings' }, ]} /> </div> ); }
Customize link state, unread badges, and panel width (sx on the root aside).
function Example() { const [isMenuOpen, setIsMenuOpen] = React.useState(true); return ( <div style={{ minHeight: 320 }}> <SideNavigation isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} header={{ icon: HomeOutlined, text: 'Workspace', href: '#home' }} links={[ { icon: DashboardOutlined, text: 'Overview', href: '#overview', isActive: true }, { icon: ShoppingCartOutlined, text: 'Inbox', href: '#inbox', unreadCount: 12 }, { icon: SettingsOutlined, text: 'Settings', href: '#settings' }, ]} sx={{ maxWidth: 280 }} /> </div> ); }
Counts above 99 display as 99... unless you override formatTextForUnreadCount on SideNavigation.Links.
Compose header, menu button, open menu panel, and links list sub-blocks explicitly.
function Example() { const [isMenuOpen, setIsMenuOpen] = React.useState(true); const header = { icon: HomeOutlined, text: 'Home', href: '#home' }; const links = [ { icon: DashboardOutlined, text: 'Dashboard', href: '#dashboard' }, { icon: ShoppingCartOutlined, text: 'Products', href: '#products' }, { icon: SettingsOutlined, text: 'Settings', href: '#settings' }, ]; return ( <div style={{ minHeight: 320 }}> <SideNavigation isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} links={links} > <SideNavigation.MenuButton isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} /> <SideNavigation.OpenMenu isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} > <SideNavigation.Links isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} header={header} links={links} /> </SideNavigation.OpenMenu> </SideNavigation> </div> ); }
Root JSX children replace the default blocks. In this pattern, you compose the child blocks (MenuButton, OpenMenu, and Links) explicitly and pass props directly to each sub-block.
Use function children to prepend blocks or change order (same pattern as Storybook).
function Example() { const [isMenuOpen, setIsMenuOpen] = React.useState(false); return ( <div style={{ minHeight: 320 }}> <SideNavigation isFloating={false} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} header={{ icon: HomeOutlined, text: 'Home', href: '#home' }} links={[ { icon: DashboardOutlined, text: 'Dashboard', href: '#dashboard' }, { icon: SettingsOutlined, text: 'Settings', href: '#settings' }, ]} > {({ defaultBlocks, defaultBlockOrder }) => ({ blocks: { ...defaultBlocks, customBlock: isMenuOpen ? ( <div style={{ padding: 12, margin: 8, background: '#eef4ff', border: '1px solid #cddcff', borderRadius: 8, fontSize: 14, }} > Custom notification block </div> ) : null, }, blockOrder: ['customBlock', ...defaultBlockOrder], })} </SideNavigation> </div> ); }
When to use block overrides
Use overrides when you need a banner or custom section above the default blocks while keeping menu behavior. defaultBlockOrder is menuButton, openMenu, overlay, closeButton, links; only menuButton and openMenu render on the root aside—overlay, closeButton, and links are composed inside openMenu (floating: portal + backdrop; inline: Links only).
Important Props
Core Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
links | { icon?: SvgIconComponent; text?: ReactNode; href: string; isActive?: boolean; unreadCount?: number }[] | Yes | - | Navigation link items |
header | { icon?: SvgIconComponent; text?: ReactNode; href: string } | No | undefined | Optional header link above links |
isFloating | boolean | No | false | When true, open menu uses a portal, backdrop, and close button |
isMenuOpen | boolean | No | internal false | Whether the menu panel is open |
setIsMenuOpen | (open: boolean) => void | No | internal setter | Pass with isMenuOpen for controlled usage |
Content Props
Set links and header on the root. Subcomponents read them from context unless you override a block locally.
| Component | Prop | Type | Required | Default | Description |
|---|---|---|---|---|---|
Links | children | ReactNode | No | Renders header + links | Replaces the default header and link list |
Links | formatTextForUnreadCount | (unreadCount: number) => number | string | No | 99... when count > 99, else count | Badge text for unreadCount |
MenuButton | children | ReactNode | No | Menu / MenuOpen icon | Toggle button content |
MenuButton | onClick | React.MouseEventHandler | No | toggles isMenuOpen | Custom click handler |
Overlay | onClick | () => void | No | () => setIsMenuOpen(false) | Backdrop click (floating mode) |
CloseButton | children | ReactNode | No | Close icon | Close control inside the floating panel |
OpenMenu | container | PortalProps['container'] | No | document.body | Portal target when isFloating |
Links, MenuButton, Overlay, CloseButton, and OpenMenu are SideNavigation.Links, etc. Link icon values use MUI SvgIconComponent (e.g. HomeOutlined).
Layout and Composition Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | No | undefined | Compound subcomponents or function override returning blocks and blockOrder |
className | string | No | undefined | Class on root aside (nbb-side-navigation applied by default) |
sx | SxProps | No | undefined | MUI system styles for the root aside |
SideNavigation inherits StackProps (except children). Root uses component="aside", maxWidth: 300, direction="column". Root-level render order is menuButton, then openMenu; defaultBlockOrder is menuButton, openMenu, overlay, closeButton, links.
Default UI Blocks
| Block | Built on | Notes |
|---|---|---|
SideNavigation (root) | Stack (component="aside") | maxWidth: 300; renders menuButton + openMenu by default |
SideNavigation.MenuButton | IconButton + SvgIcon | Toggles menu; Menu icon by default, MenuOpen when inline and open |
SideNavigation.OpenMenu | Box + Portal | Floating: portal with Overlay, panel, CloseButton, Links; inline: Links only |
SideNavigation.Overlay | Box | Backdrop inside floating openMenu when open |
SideNavigation.CloseButton | IconButton | Inside floating panel (Close icon); closes menu |
SideNavigation.Links | Stack (component="nav") + Link | Default header/link list; text and unread counts show when open |
TypeScript
import { SideNavigation } from '@nodeblocks/frontend-side-navigation-block';
import { DashboardOutlined, HomeOutlined } from '@mui/icons-material';
import type { SvgIconComponent } from '@mui/icons-material';
import type { ReactNode } from 'react';
type NavLink = {
icon?: SvgIconComponent;
text?: ReactNode;
href: string;
isActive?: boolean;
unreadCount?: number;
};
const links: NavLink[] = [
{ icon: DashboardOutlined, text: 'Dashboard', href: '/dashboard', isActive: true },
];
<SideNavigation
header={{ icon: HomeOutlined, text: 'Home', href: '/home' }}
links={links}
/>;