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

> Customize the refine-shadcn Sidebar with dynamic resource navigation. Learn how to use useMenu(), custom items, and the isCollapsed prop without altering core logic.

- Repository: [Ferdi ÜNAL/refine-shadcn](https://github.com/ferdiunal/refine-shadcn)
- Tags: how-to-guide
- Published: 2026-03-01

---

**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`](https://github.com/ferdiunal/refine-shadcn/blob/main/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`](https://github.com/ferdiunal/refine-shadcn/blob/main/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).

```tsx
// 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`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/sidebar.tsx), the implementation performs string replacement:

```tsx
// 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`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/layouts/default.tsx) typically manages this state and passes it down to the `Sidebar`.

```tsx
// 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:

```tsx
// 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:

```tsx
// 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`](https://github.com/ferdiunal/refine-shadcn/blob/main/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`](https://github.com/ferdiunal/refine-shadcn/blob/main/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.

### How do I ensure the correct record ID appears in edit and show links?

The sidebar handles this automatically at lines 44-49 of [`packages/theme/src/components/sidebar.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/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.