@mshafiqyajid/react-sidebar
Collapsible navigation sidebar with sections, nested items, badges, icon-only collapse mode, four variants, smooth width animation, and full keyboard navigation (↑ ↓ → ← Enter). Headless hook + styled component. Zero dependencies, fully typed.
Playground #
Active: dashboard
Props
TSX
import { SidebarStyled } from "@mshafiqyajid/react-sidebar/styled";
import "@mshafiqyajid/react-sidebar/styles.css";
<SidebarStyled
items={items}
activeId="dashboard"
onItemClick={(id) => setActiveId(id)}
collapsed={collapsed}
onCollapse={setCollapsed}
/>Install #
npm install @mshafiqyajid/react-sidebar Quick start #
import { SidebarStyled } from "@mshafiqyajid/react-sidebar/styled";
import "@mshafiqyajid/react-sidebar/styles.css";
const items = [
{
label: "Main",
items: [
{ id: "dashboard", label: "Dashboard", icon: <HomeIcon /> },
{
id: "projects",
label: "Projects",
icon: <FolderIcon />,
children: [
{ id: "active", label: "Active", badge: "12" },
{ id: "archived", label: "Archived" },
],
},
],
},
];
function App() {
const [activeId, setActiveId] = useState("dashboard");
return (
<SidebarStyled
items={items}
activeId={activeId}
onItemClick={(id) => setActiveId(id)}
/>
);
} Variants #
Four visual styles, controlled via variant.
// Default — transparent, no border
<SidebarStyled items={items} variant="default" />
// Bordered — subtle right border
<SidebarStyled items={items} variant="bordered" />
// Filled — accent-tinted background
<SidebarStyled items={items} variant="filled" />
// Floating — rounded corners + drop shadow
<SidebarStyled items={items} variant="floating" /> Controlled collapse #
const [collapsed, setCollapsed] = useState(false);
<SidebarStyled
items={items}
collapsed={collapsed}
onCollapse={setCollapsed}
/> When collapsed the sidebar shrinks to 56 px and shows only icons. Tooltips appear via the native title attribute on hover. Set defaultCollapsed for uncontrolled initial state.
Header and footer slots #
<SidebarStyled
items={items}
header={
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
<Logo />
<span>My App</span>
</div>
}
footer={<UserAvatar name="Shafiq" />}
/> Nested items #
Add children to any SidebarItem. Clicking the parent toggles its sub-list open/closed with a 200 ms animation. Keyboard: → expands, ← collapses.
const items = [
{
items: [
{
id: "projects",
label: "Projects",
children: [
{ id: "active", label: "Active", badge: "12" },
{ id: "archived", label: "Archived" },
],
},
],
},
]; Headless hook #
import { useSidebar } from "@mshafiqyajid/react-sidebar";
function MyNav({ sections }) {
const {
isCollapsed,
toggle,
isExpanded,
toggleSection,
getItemProps,
getSectionProps,
sidebarProps,
} = useSidebar({
sections,
activeId: "dashboard",
onItemClick: (id) => navigate(id),
});
return (
<nav {...sidebarProps} data-collapsed={isCollapsed ? "" : undefined}>
<button onClick={toggle}>
{isCollapsed ? "Expand" : "Collapse"}
</button>
{sections.map((section, i) =>
section.items.map((item) => (
<div key={item.id} {...getItemProps(item)}>
{item.icon}
<span>{item.label}</span>
</div>
))
)}
</nav>
);
} Keyboard navigation #
| Key | Action |
|---|---|
| ↓ / ↑ | Move focus between visible items |
| → | Expand a collapsed nested section, or focus its first child if already open |
| ← | Collapse an expanded nested section, or focus parent item |
| Enter / Space | Activate leaf item or toggle nested section |
API — SidebarStyled #
| Prop | Type | Default | Description |
|---|---|---|---|
| items | SidebarSection[] | — | Required. Navigation sections. |
| collapsed | boolean | — | Controlled collapsed state. |
| defaultCollapsed | boolean | false | Initial uncontrolled collapsed state. |
| onCollapse | (c: boolean) => void | — | Fires on collapse change. |
| showCollapseButton | boolean | true | Show the collapse toggle at the bottom. |
| variant | "default" | "bordered" | "filled" | "floating" | "default" | Visual style. |
| size | "sm" | "md" | "lg" | "md" | Item size and sidebar width. |
| activeId | string | — | Marks the matching item with aria-current="page". |
| onItemClick | (id, item) => void | — | Fires when a leaf item is clicked. |
| header | ReactNode | — | Slot rendered at the top of the sidebar. |
| footer | ReactNode | — | Slot rendered at the bottom. |
| className / style | — | — | Passed to the root <nav>. |
| ref | Ref<HTMLElement> | — | Forwarded to the root <nav>. |
SidebarItem #
| Field | Type | Description |
|---|---|---|
| id | string | Required, unique. |
| label | string | Required. |
| icon | ReactNode | Left-side icon. Shown in both expanded and icon-only mode. |
| href | string | When provided the item renders as an <a>. |
| active | boolean | Marks item active (alternative to activeId). |
| disabled | boolean | Greyed, not interactive. |
| badge | ReactNode | Small badge shown to the right of the label. |
| children | SidebarItem[] | Nested sub-items. Makes the item a collapsible parent. |
CSS variables #
| Variable | Default | Description |
|---|---|---|
| --rsb-bg | #ffffff | Sidebar background |
| --rsb-fg | #18181b | Item text color |
| --rsb-accent | #6366f1 | Active / focus accent |
| --rsb-border | #e4e4e7 | Section separators |
| --rsb-width-sm | 200px | Width at size="sm" |
| --rsb-width-md | 240px | Width at size="md" |
| --rsb-width-lg | 280px | Width at size="lg" |
| --rsb-collapsed-width | 56px | Width when collapsed |
| --rsb-duration-width | 250ms | Collapse animation |
Dark mode is applied automatically when a [data-theme="dark"] ancestor is present.