How to Customize the Sidebar Component with Dynamic Resource Navigation in refine-shadcn

The Sidebar component in refine-shadcn automatically synchronizes with Refine's menu system via the useMenu() hook, enabling dynamic navigation customization through resource definitions, custom menu items, and the isCollapsed prop without touching core component logic.

The Sidebar component in the refine-shadcn theme serves as a sophisticated navigation wrapper built on top of Refine's core routing infrastructure. Located at packages/theme/src/components/sidebar.tsx, it seamlessly integrates with dynamic resource definitions to generate navigation links that automatically handle resource IDs, active state highlighting, and responsive layout changes. By leveraging standard Refine hooks and a flexible props interface, you can fully customize the sidebar behavior to match any application structure while maintaining perfect synchronization with the current route.

Understanding the Core Architecture

The component relies on three essential hooks to maintain navigation state. At lines 15-20 of packages/theme/src/components/sidebar.tsx, the implementation extracts:

  • Menu definitions via const { menuItems } = useMenu(); – automatically populated from your Refine resource configuration
  • Current resource parameters via const resourceParams = useResourceParams(); – provides access to the active record ID
  • Location state via const { pathname } = useLocation(); – used for active route matching

This architecture ensures that any changes to your Refine resources immediately reflect in the sidebar without requiring manual updates to the component itself.

Adding Dynamic Menu Items

You can extend the sidebar navigation by modifying the resources array passed to your Refine configuration. Each resource can include a meta object containing icon, title, and label properties that the sidebar renders via the internal GetIcon utility (lines 22-30).

// src/app/refine.ts
import { Refine } from "@refinedev/core";
import { BarChart2 } from "lucide-react";

const resources = [
  {
    name: "posts",
    list: "/posts",
    edit: "/posts/edit/:id",
    show: "/posts/show/:id",
    meta: {
      title: "Blog Posts",
      icon: <FileText className="mr-2 h-4 w-4" />,
    },
  },
  {
    name: "analytics",
    list: "/analytics",
    meta: {
      title: "Analytics",
      icon: <BarChart2 className="mr-2 h-4 w-4" />,
    },
  },
];

export const App = () => (
  <Refine
    resources={resources}
    routerProvider={routerProvider}
    dataProvider={dataProvider}
  />
);

The Sidebar automatically renders the new "Analytics" entry because it consumes the useMenu() hook, which aggregates all registered resources and their metadata.

Building Resource-Aware URLs

The component dynamically constructs URLs for resource actions by substituting the :id placeholder with the actual resource parameter. At lines 44-49 in packages/theme/src/components/sidebar.tsx, the implementation performs string replacement:

// Internal implementation (lines 44-49)
item.edit?.toString()?.replace(":id", resourceParams.id as string)
item.show?.toString()?.replace(":id", resourceParams.id as string)

This means "Edit" and "Show" links in your sidebar automatically point to the correct record when you are viewing a specific resource. For example, if you are viewing posts/edit/123, the sidebar's Edit link for the Posts resource will correctly resolve to /posts/edit/123.

Controlling the Collapsed State

The isCollapsed prop controls whether the sidebar displays full labels or compact icons with tooltips. The layout component at packages/theme/src/layouts/default.tsx typically manages this state and passes it down to the Sidebar.

// packages/theme/src/layouts/default.tsx
import { useState } from "react";
import { Sidebar } from "@/components";

export const DefaultLayout = ({ children }: { children: React.ReactNode }) => {
  const [collapsed, setCollapsed] = useState(false);

  return (
    <div className="flex h-screen">
      <Sidebar isCollapsed={collapsed} />
      <main className="flex-1">{children}</main>
      <button
        onClick={() => setCollapsed(!collapsed)}
        className="absolute left-0 top-2 p-2"
      >
        {collapsed ? "→" : "←"}
      </button>
    </div>
  );
};

When isCollapsed is true, the component renders a compact variant (lines 59-95) that displays icons inside tooltips rather than full text labels, maximizing screen real estate for the main content area.

Customizing Icons and Active States

The sidebar determines the active navigation item by checking if the current pathname matches any of the defined routes (lines 51-58). It uses a combination of exact matching and partial path checks:

// Active state detection logic (lines 51-58)
const isActive = paths.includes(currentPathname) || 
  paths.some((path) => 
    path !== "/" && currentPathname.includes(path)
  );

For custom styling, you can override the active state appearance by modifying the button variant classes. The component uses buttonVariants({ variant: "ghost" }) from the shadcn/ui Button component combined with conditional active classes:

// Custom active link wrapper example
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/ui/button";
import { Link } from "@/components/link";

const ActiveLink = ({ 
  href, 
  isActive, 
  children 
}: { 
  href: string; 
  isActive: boolean; 
  children: React.ReactNode 
}) => (
  <Link
    href={href}
    className={cn(
      buttonVariants({ variant: "ghost" }),
      "justify-start w-full",
      isActive && "bg-primary text-primary-foreground font-medium"
    )}
  >
    {children}
  </Link>
);

The GetIcon function (lines 22-30) handles icon rendering by cloning the provided React element and injecting size classes (h-4 w-4), ensuring consistent iconography across all menu items regardless of the icon library used.

Summary

  • Dynamic menus: The sidebar automatically reflects changes to Refine resources via the useMenu() hook, requiring no manual list management in the component itself.
  • Resource-aware routing: URL placeholders like :id are automatically substituted with current resource parameters, ensuring edit and show links always point to the correct record.
  • Collapse control: The isCollapsed prop toggles between full-label and tooltip-only modes, managed by the parent layout component at packages/theme/src/layouts/default.tsx.
  • Icon flexibility: Supply any React element as meta.icon in your resource definition; the sidebar normalizes sizes via the GetIcon utility.
  • Active state logic: The component matches current pathname against resource routes to highlight the active navigation item without requiring manual state tracking.

Frequently Asked Questions

How does the Sidebar component know which menu items to display?

The component calls useMenu() from @refinedev/core (line 15 in packages/theme/src/components/sidebar.tsx), which aggregates all resources registered in your Refine configuration. It transforms these resources into menu items using their name, list, edit, show, and meta properties, rendering them automatically without requiring a static menu definition.

Can I add menu items that are not tied to Refine resources?

Yes. While the sidebar primarily consumes useMenu(), you can customize the menu array at the Refine configuration level or create a wrapper component that intercepts the menuItems array and injects additional entries before passing them to the sidebar. The component will render any object matching the expected menu item interface.

The sidebar handles this automatically at lines 44-49 of packages/theme/src/components/sidebar.tsx. It retrieves the current ID via useResourceParams() and performs a string replacement on the route definition, converting /posts/edit/:id to /posts/edit/123 based on the active route parameters.

What determines whether a menu item appears as "active"?

The active state is calculated using the current pathname from useLocation() (line 18) and comparing it against the resource paths array (lines 51-58). An item is active if the pathname exactly matches one of the resource routes or contains the base path (for nested routes), ensuring parent resources remain highlighted when viewing child pages.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →