react ~4 KB 0 deps v0.1.0 ↗ GitHub ↗

@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
Select an item from the sidebar
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.

<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 #

KeyAction
↓ / ↑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 / SpaceActivate leaf item or toggle nested section

API — SidebarStyled #

PropTypeDefaultDescription
itemsSidebarSection[]Required. Navigation sections.
collapsedbooleanControlled collapsed state.
defaultCollapsedbooleanfalseInitial uncontrolled collapsed state.
onCollapse(c: boolean) => voidFires on collapse change.
showCollapseButtonbooleantrueShow the collapse toggle at the bottom.
variant"default" | "bordered" | "filled" | "floating""default"Visual style.
size"sm" | "md" | "lg""md"Item size and sidebar width.
activeIdstringMarks the matching item with aria-current="page".
onItemClick(id, item) => voidFires when a leaf item is clicked.
headerReactNodeSlot rendered at the top of the sidebar.
footerReactNodeSlot rendered at the bottom.
className / stylePassed to the root <nav>.
refRef<HTMLElement>Forwarded to the root <nav>.

SidebarItem #

FieldTypeDescription
idstringRequired, unique.
labelstringRequired.
iconReactNodeLeft-side icon. Shown in both expanded and icon-only mode.
hrefstringWhen provided the item renders as an <a>.
activebooleanMarks item active (alternative to activeId).
disabledbooleanGreyed, not interactive.
badgeReactNodeSmall badge shown to the right of the label.
childrenSidebarItem[]Nested sub-items. Makes the item a collapsible parent.

CSS variables #

VariableDefaultDescription
--rsb-bg#ffffffSidebar background
--rsb-fg#18181bItem text color
--rsb-accent#6366f1Active / focus accent
--rsb-border#e4e4e7Section separators
--rsb-width-sm200pxWidth at size="sm"
--rsb-width-md240pxWidth at size="md"
--rsb-width-lg280pxWidth at size="lg"
--rsb-collapsed-width56pxWidth when collapsed
--rsb-duration-width250msCollapse animation

Dark mode is applied automatically when a [data-theme="dark"] ancestor is present.

Edit this page on GitHub