惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
Forbes - Security
Forbes - Security
The Last Watchdog
The Last Watchdog
TaoSecurity Blog
TaoSecurity Blog
Schneier on Security
Schneier on Security
SecWiki News
SecWiki News
V
Vulnerabilities – Threatpost
Project Zero
Project Zero
O
OpenAI News
W
WeLiveSecurity
Security Archives - TechRepublic
Security Archives - TechRepublic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
H
Hacker News: Front Page
Cisco Talos Blog
Cisco Talos Blog
Spread Privacy
Spread Privacy
Help Net Security
Help Net Security
P
Privacy & Cybersecurity Law Blog
K
Kaspersky official blog
S
Security @ Cisco Blogs
Latest news
Latest news
AWS News Blog
AWS News Blog
U
Unit 42
Martin Fowler
Martin Fowler
阮一峰的网络日志
阮一峰的网络日志
S
Secure Thoughts
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Know Your Adversary
Know Your Adversary
Scott Helme
Scott Helme
博客园 - 司徒正美
B
Blog RSS Feed
C
Check Point Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
D
Docker
Google Online Security Blog
Google Online Security Blog
Jina AI
Jina AI
aimingoo的专栏
aimingoo的专栏
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Last Week in AI
Last Week in AI
月光博客
月光博客
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
SegmentFault 最新的问题
NISL@THU
NISL@THU
T
The Blog of Author Tim Ferriss
C
Cisco Blogs
Attack and Defense Labs
Attack and Defense Labs
小众软件
小众软件

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

Learn Command Line Interface (CLI) Development with Dart: From Zero to a Fully Published Developer Tool How to Bypass Cloud SMTP Restrictions Using Brevo and HTTP APIs How to Build a Live Options Database in Python – A Complete Guide How to Migrate to S3 Native State Locking in Terraform How to Use SCons to Build Software Projects [Full Handbook] How to Run Open Source LLMs Locally and in the Cloud QuRT: The Real-Time OS Inside Your Phone's Processor [Full Handbook] The Real Infrastructure Behind Remote Work (It’s Not Just Wi-Fi) The Lithography Handbook: Machines, Markets, and the Next Wave of Semiconductor Startups ITCM vs DTCM vs DDR: Embedded Memory Types Explained [Full Handbook] AI Paper Review: Improving Language Understanding by Generative Pre-Training (GPT-1) How to Build a Market Research Copilot with MCP and Python [Full Handbook] How to Build a Scoped Note-Taking API with Django Rest Framework and SimpleJWT The Complete SOC 2 Type II Implementation Handbook for Engineers: A Month-by-Month Roadmap with Real Commands Mastering the JavaScript Event Loop Data Science Insights: Why the Mean Lies When Handling Messy Retail Data How to Build High-Ranking SEO Landing Page How to Query Data in DynamoDB Using .Net How to Unblock Your AI PR Review Bottleneck: A Tech Lead’s Guide to Building a Codebase-Aware Reviewer How to Navigate Microservices as a Frontend Engineer How to Compress PDF Files in the Browser Using JavaScript (Step-by-Step) Stanford's youngest instructor talks InfoSec, AI, and catching cheaters - Rachel Fernandez interview [Podcast #217] Product Experimentation with Propensity Scores: Causal Inference for LLM-Based Features in Python How to Build a Multi-Agent AI System with LangGraph, MCP, and A2A [Full Book] How to Land Your First Cloud or DevOps Role: What Hiring Managers Actually Look For How to Deploy a Serverless Spam Classifier Using Scikit-Learn, AWS Lambda, & API Gateway How to Dockerize a Go Application – Full Step-by-Step Walkthrough Learn Hardware, Cloud, DevOps, Networking, Security, Databases, DNS, Git, and Linux Inside TreeHacks 2026, Stanford’s Elite Student Hakc Inside Stanford’s Elite Student Hackathon [Full Documentary] How to Measure Your AI Citation Rate Across ChatGPT, Perplexity, and Claude How to Deploy a Full-Stack Next.js App on Cloudflare Workers with GitHub Actions CI/CD How to Build a Multi-Tenant SaaS Platform with Next.js, Express, and Prisma How I Completed 15 freeCodeCamp Certifications in 4 Months: A Structured Learning Journey How to Build an Agentic Terminal Workflow with GitHub Copilot CLI and MCP Servers How AI Changed the Economics of Writing Clean Code How to Apply STRIDE Threat Modeling and SonarQube Analysis for Secure Software Development How to Set Up OpenID Connect (OIDC) in GitHub Actions for AWS How to Split PDF Files in the Browser Using JavaScript (Step-by-Step) How to Build Your Own Language-Specific LLM [Full Handbook] How to Build a Self-Learning RAG System with Knowledge Reflection How to Trace Multi-Agent AI Swarms with Jaeger v2 How I Tested Malaysia's Open Data Portals with Plain English How I Built a Production-Ready CI/CD Pipeline for a Monorepo-Based Microservices System with Jenkins, Docker Compose, and Traefik The Hidden Tax of Infrastructure: Why Your Team Shouldn’t Be Running It Anymore From Metrics to Meaning: How PaaS Helps Developers Understand Production From Symptoms to Root Cause: How to Use the 5 Whys Technique Product Experimentation for AI Rollouts: Why A/B Testing Breaks and How Difference-in-Differences in Python Fixes It How to Create a GPU-Optimized Machine Image with HashiCorp Packer on GCP 3D Web Development with Blender and Three.js How to Fix a Failing GitHub PR: Debugging CI, Lint Errors, and Build Errors Step by Step How to Merge PDF Files in the Browser Using JavaScript (Step-by-Step) How to Handle Stripe Webhooks Reliably with Background Jobs How to Build an Automatic Knowledge Graph for Your Blog with PHP and JSON-LD Understanding Proxies and Reverse Proxies: Your Gateway to Secure Networking The Evolution of Nvidia Blackwell GPU Memory Architecture How to Use PostgreSQL as a Cache, Queue, and Search Engine The New Definition of Software Engineering in the Age of AI Reclaim Your Time – Master Automation with Zapier How to Create Dynamic Emails in Go with React Email Why Many Beginner Self-Taught Developers Struggle (And What to Do About It) How to Build a Headless WordPress Frontend with Astro SSR on Cloudflare Pages How to Make Your GitHub Profile Stand Out How to Use Context Hub (chub) to Build a Companion Relevance Engine Why Chrome OS Is the Operating System the AI Era Was Built For How to Build Microservices-Based REST APIs for Healthcare Portals How to friction-max your learning with software engineer Jessica Rose [Podcast #216] Shadow AI Explained: Why Employees Are Using AI Behind Your Back Traditional Scraping vs AI Scraping: A Practical Guide for Developers and Data Teams How Database Indexes Work – A Practical Guide with PostgreSQL Examples How to Streamline Search in Web Applications with Elasticsearch How to Build an Open Source Data Lake for Batch Ingestion OpenAI Codex Essentials – AI Assisted Agentic Development Course Learn Software System Design How to Generate PDF Files in the Browser Using JavaScript (With a Real Invoice Example) How to Get Started with Terraform Service-to-Service Communication: When to Use REST, gRPC, and Event-Driven Messaging A Developer’s Guide to Lazy Loading in React and Next.js The Data Quality Handbook: Data Errors, the Developer's Role, and Validation Layers Explained. United States Residential Proxy: Why Local IP Accuracy Matters for SERP, Ads, and Pricing How to Build a Fashion App That Helps You Organize Your Wardrobe The AI Governance Handbook: How to Build Responsible AI Systems That Actually Ship How to Build a Local DevOps HomeLab with Docker, Kubernetes, and Ansible How to Use Mixins in Flutter [Full Handbook] How to Prep for Technical Interviews – A Guide for Web Developers GPT-5.4 vs GLM-5: Is Open Source Finally Matching Proprietary AI? Data Visualization Tools for Svelte Developers How to Keep Human Experts Visible in Your AI-Assisted Codebase Efficient Data Processing in Python: Batch vs Streaming Pipelines Explained How to Build and Deploy Multi-Architecture Docker Apps on Google Cloud Using ARM Nodes (Without QEMU) How to Build a Secure AI PR Reviewer with Claude, GitHub Actions, and JavaScript How to Build a Positioning-Based Crude Oil Strategy in Python [Full Handbook] How to learn programming and CS in the AI hype era – interview with dev and prof Mark Mahoney [Podcast #215] CUDA Programming for NVIDIA H100s How to Build Reliable AI Systems. How to Build an Online Marketplace with Next.js, Express, and Stripe Connect How to Build a Cost-Efficient AI Agent with Tiered Model Routing The WebCodecs Handbook: Native Video Processing in the Browser The Bluetooth LE Audio Handbook: From "Why Does My Call Sound Like a Tin Can?" to AOSP Implementation How to Set Up OpenClaw and Design an A2A Plugin Bridge
How to Build an Admin Dashboard Sidebar with shadcn/ui and Base UI
2026-04-15 · via freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
How to Build an Admin Dashboard Sidebar with shadcn/ui and Base UI

Admin dashboards are one of the most common real-world UI components you will build as a React developer. At the heart of nearly every dashboard is a sidebar, a persistent navigation panel that organizes pages, tools, and features into a clean, scannable structure.

Building a sidebar from scratch involves much more than an <nav> element. You need collapsible submenus, active state tracking across parent and child items, accessible keyboard navigation, a scroll area for long nav lists, and a consistent design system that holds together across screen sizes.

In this tutorial, you'll learn how to build a fully functional, accessible admin dashboard sidebar using shadcn/ui, a collection of beautifully designed, accessible React components, and a pre-built community block from Shadcn Space, which extends shadcn/ui with ready-to-use dashboard UI patterns.

By the end of this tutorial, you'll have a working sidebar that includes:

  • Grouped navigation sections with uppercase labels

  • Collapsible parent menu items with child links

  • Active state tracking across both parent and child items

  • A floating sidebar with an independent scroll area

  • A promotional card pinned inside the sidebar footer

Table of Contents

Prerequisites

Before you start, make sure you have the following:

  • Node.js 18+ installed on your machine

  • Basic knowledge of React and TypeScript

  • Familiarity with Tailwind CSS utility classes

  • A package manager installed (npm, pnpm, yarn, or bun)

You don't need prior experience with shadcn/ui, but it helps to have read through the official docs at least once.

What You Will Build

In this article, you'll build a fully functional admin dashboard sidebar with the following features:

  1. Floating sidebar shell: a card-style sidebar with rounded corners, a drop shadow, and a configurable width

  2. Grouped navigation: navigation items organized under section labels like Dashboards, Pages, Apps, and Form Elements

  3. Collapsible submenus: parent items like Blogs and Shadcn Forms that expand on click to reveal child links

  4. Active state tracking: visual highlighting of the selected parent and child item at all times

  5. Sidebar toggle: a trigger button in the page header that opens and closes the sidebar

  6. Promotional card: a "Get Premium" card at the bottom of the sidebar scroll area

shadcn/ui is a collection of beautifully designed, accessible React components built on top of Radix UI and styled with Tailwind CSS.

Instead of installing a traditional component library as a dependency, you copy components directly into your project using a CLI. This gives you full ownership of the code structure and styling. You can read every line, change anything, and the components never break because of a library update you didn't control.

Some key benefits of shadcn/ui include:

  • Accessible by default, built on Radix and Base UI primitives

  • Fully styled with Tailwind CSS utility classes

  • Zero lock-in: the code lives in your project, not inside node_modules

  • Works with Next.js, React, Astro, Vite, and other frameworks

  • A growing ecosystem of community-built blocks and registries

The Sidebar, Collapsible, ScrollArea, Card, and Button Components you'll use in this tutorial all come from shadcn/ui.

What is Shadcn Space?

Shadcn Space is an open-source library of pre-built UI blocks built on top of shadcn/ui. It provides ready-to-use dashboard layouts, sidebars, tables, cards, and other common admin UI patterns so you don't have to assemble them from individual primitives every time.

Each block in Shadcn Space is installable directly into your project using the shadcn CLI. Once installed, the code is yours: you can read it, extend it, and adapt it to your design system without any runtime dependency on Shadcn Space itself.

For this tutorial, you'll use the sidebar-06 block (it’s free to use), which is a floating admin sidebar with grouped navigation, collapsible submenus, and an integrated scroll area.

Shadcn Space also provides a companion Figma UI Kit that matches the design system used in the blocks, which is useful if you do design work alongside development.

You can explore the full block library and the getting-started documentation in the official Shadcn Space docs.

How to Set Up the Project

Start by creating a new Next.js project if you don't already have one:

npx shadcn@latest init --preset b0 --base base --template next

This command:

  • Creates a Next.js project

  • Configures Tailwind CSS

  • Sets up Base UI as the component foundation

  • Uses Nova style preset

  • Configures Lucide icons

  • Uses Inter font

  • Applies neutral theme tokens

Follow the prompts to configure your base color, CSS variables, and component output directory. This sets up the components/ui directory and the required Tailwind configuration that all shadcn/ui components depend on.

Once the initialization is complete, your components.json project will be created at the root of your project. This file tells the shadcn CLI where to place components, what path aliases you're using, and which styling configuration to follow.

Add this in components.json:

{
  "registries": {
    "@shadcn-space": {
      "url": "https://shadcnspace.com/r/{name}.json",
    }
  }
}

With shadcn/ui initialized, you can now pull in the sidebar-06 block from Shadcn Space. While Shadcn Space provides components for both Radix UI and Base UI, this tutorial uses the Base UI version. Run one of the following commands depending on your package manager:

npm:

npx shadcn@latest add @shadcn-space/sidebar-06

pnpm:

pnpm dlx shadcn@latest add @shadcn-space/sidebar-06

yarn:

yarn dlx shadcn@latest add @shadcn-space/sidebar-06

bun:

bunx --bun shadcn@latest add @shadcn-space/sidebar-06

This command fetches the block from the Shadcn Space registry and scaffolds all the required component files into your project automatically. It also installs any shadcn/ui primitives the block depends on (such as Sidebar, ScrollArea, Card, Button, and Collapsible) if they aren't already present in your components/ui directory.

You can preview the live block and find the installation command on their shadcn sidebar page.

How to Understand the Folder Structure

After installation, your project will contain the following new files:

app/
  sidebar-06/
    page.tsx                  ← Route entry point
assets/
  logo/
    logo.tsx                  ← Logo component
components/
  shadcn-space/
    blocks/
      sidebar-06/
        app-sidebar.tsx       ← Main sidebar shell
        nav-main.tsx          ← Navigation logic and rendering

Each file has a clearly defined responsibility:

  • app/sidebar-06/page.tsx: the route entry point that wires the sidebar into a page layout using SidebarProvider

  • assets/logo/logo.tsx: the logo component rendered in the sidebar header

  • components/shadcn-space/blocks/sidebar-06/app-sidebar.tsx: the main sidebar shell, including the header, scroll area, nav data, and promotional card

  • components/shadcn-space/blocks/sidebar-06/nav-main.tsx: all navigation rendering logic, including section labels, leaf items, collapsible parents, and active state management

You'll work through each of these files in detail in the sections below.

How to Build the Page Layout

Open app/sidebar-06/page.tsx. This file is the entry point for your dashboard page. It uses SidebarProvider to establish sidebar context across the page, and SidebarTrigger to render a toggle button inside the header.

import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/shadcn-space/blocks/sidebar-06/app-sidebar";

const Page = () => {
  return (
    <SidebarProvider
      className="p-4 bg-muted"
      style={{ "--sidebar-width": "300px" } as React.CSSProperties}
    >
      <AppSidebar />

      {/* Main content area */}
      <div className="flex flex-1 flex-col gap-4">
        <header className="flex h-14 shrink-0 items-center gap-2 rounded-xl bg-background px-4 shadow-sm">
          <SidebarTrigger className="cursor-pointer" />
        </header>
        <main className="flex-1 rounded-xl bg-background" />
      </div>
    </SidebarProvider>
  );
};

export default Page;

Let's break down the key parts of this layout:

SidebarProvider wraps everything on the page. It manages the sidebar's open/closed state and passes it down to child components via React context. Any component that needs to read or change the sidebar state, including SidebarTrigger and AppSidebar, must be a descendant of SidebarProvider.

The --sidebar-width CSS custom property controls the rendered width of the sidebar. It's set inline using a type assertion (as React.CSSProperties) because TypeScript doesn't know about this custom property by default. Setting it here rather than in a CSS file keeps the width configurable on a per-page basis.

SidebarTrigger is a toggle button component that reads the sidebar open/closed state from the nearest SidebarProvider context and flips it on click. It renders in the header so users always have access to the toggle regardless of scroll position.

bg-muted on SidebarProvider creates the light gray outer background that makes the floating sidebar card visually stand out from the page.

Open components/shadcn-space/blocks/sidebar-06/app-sidebar.tsx. This component is the main sidebar shell. It composes shadcn/ui's Sidebar, SidebarHeader, and SidebarContent layout primitives and wraps the scrollable navigation area in a ScrollArea component to handle overflow independently.

"use client";

import {
  Sidebar,
  SidebarContent,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuItem,
} from "@/components/ui/sidebar";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import Logo from "@/assets/logo/logo";
import { NavItem, NavMain } from "@/components/shadcn-space/blocks/sidebar-06/nav-main";
import {
  AlignStartVertical,
  PieChart,
  CircleUserRound,
  ClipboardList,
  Notebook,
  NotepadText,
  Table,
  Languages,
  Ticket,
} from "lucide-react";

The "use client" directive at the top is required because this component uses React state (through NavMain) and event handlers, both of which require the component to run in the browser rather than being server-rendered by Next.js.

How to Define the Navigation Data

Inside app-sidebar.tsxthe navigation structure is defined as a flat array of NavItem objects. Each item belongs to one of three categories:

  1. A section label marked with isSection: true and a label string. Renders as an uppercase group heading.

  2. A leaf item has a title, icon, and href, but no children. Renders as a direct navigation link.

  3. A parent item has a title, icon, and a children array of sub-items. Renders as a collapsible trigger.


export const navData: NavItem[] = [
  // Dashboards Section
  { label: "Dashboards", isSection: true },
  { title: "Analytics", icon: PieChart, href: "#" },
  { title: "CRM Dashboard", icon: ClipboardList, href: "#" },

  // Pages Section
  { label: "Pages", isSection: true },
  { title: "Tables", icon: Table, href: "#" },
  { title: "Forms", icon: ClipboardList, href: "#" },
  { title: "User Profile", icon: CircleUserRound, href: "#" },

  // Apps Section
  { label: "Apps", isSection: true },
  { title: "Notes", icon: Notebook, href: "#" },
  { title: "Tickets", icon: Ticket, href: "#" },
  {
    title: "Blogs",
    icon: Languages,
    children: [
      { title: "Blog Post", href: "#" },
      { title: "Blog Detail", href: "#" },
      { title: "Blog Edit", href: "#" },
      { title: "Blog Create", href: "#" },
      { title: "Manage Blogs", href: "#" },
    ],
  },

  // Form Elements Section
  { label: "Form Elements", isSection: true },
  {
    title: "Shadcn Forms",
    icon: NotepadText,
    children: [
      { title: "Button", href: "#" },
      { title: "Input", href: "#" },
      { title: "Select", href: "#" },
      { title: "Checkbox", href: "#" },
      { title: "Radio", href: "#" },
    ],
  },
  {
    title: "Form layouts",
    icon: AlignStartVertical,
    children: [
      { title: "Forms Horizontal", href: "#" },
      { title: "Forms Vertical", href: "#" },
      { title: "Forms Validation", href: "#" },
      { title: "Forms Examples", href: "#" },
      { title: "Forms Wizard", href: "#" },
    ],
  },
];

This flat array approach is intentionally simple to maintain. You don't need a nested tree structure because the NavMain component handles the rendering logic for each item type by inspecting each item's shape. Adding a new section, item, or submenu is as straightforward as appending a new object to the array.

How to Build the NavMain Component

Open components/shadcn-space/blocks/sidebar-06/nav-main.tsx. This file contains all the navigation rendering logic. Start with the type definition and the top-level NavMain function:

"use client";

import * as React from "react";
import { ChevronRight, LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import {
  Collapsible,
  CollapsibleTrigger,
  CollapsibleContent,
} from "@/components/ui/collapsible";
import {
  SidebarGroup,
  SidebarGroupLabel,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubItem,
  SidebarMenuSubButton,
} from "@/components/ui/sidebar";

export type NavItem = {
  label?: string;
  isSection?: boolean;
  title?: string;
  icon?: LucideIcon;
  href?: string;
  children?: NavItem[];
};

export function NavMain({ items }: { items: NavItem[] }) {
  const [activeParent, setActiveParent] = React.useState<string | null>(
    items.find((i) => !i.isSection)?.title || null
  );
  const [activeChild, setActiveChild] = React.useState<string | null>(null);

  return (
    <>
      {items.map((item, index) => (
        <NavMainItem
          key={item.title || item.label || index}
          item={item}
          activeParent={activeParent}
          setActiveParent={setActiveParent}
          activeChild={activeChild}
          setActiveChild={setActiveChild}
        />
      ))}
    </>
  );
}

activeParent tracks which top-level nav item is currently selected. It initializes to the title of the first non-section item, so the sidebar always has a selection on first render, and you never show the sidebar with nothing highlighted. activeChild tracks which sub-item inside a collapsible menu is selected.

Both state values are passed down as props to each NavMainItem, so every item in the list can read the current selection and trigger updates to it.

How to Handle Active States and Collapsible Menus

The NavMainItem function branches into one of three rendering paths based on the shape of the incoming item.

How to Render Section Labels

if (item.isSection && item.label) {
  return (
    <SidebarGroup className="p-0 pt-5 first:pt-0">
      <SidebarGroupLabel className="p-0 text-xs font-medium uppercase text-sidebar-foreground">
        {item.label}
      </SidebarGroupLabel>
    </SidebarGroup>
  );
}

Section labels use first:pt-0 to remove the top padding from the very first section, so the nav starts flush with the header.

How to Render Collapsible Parent Items

if (hasChildren && item.title) {
  return (
    <SidebarGroup className="p-0">
      <SidebarMenu>
        <Collapsible open={isOpen} onOpenChange={setIsOpen}>
          <SidebarMenuItem>
            <CollapsibleTrigger
              className="w-full"
              render={
                <SidebarMenuButton
                  id={`nav-main-trigger-${item.title.toLowerCase().replace(/\s+/g, '-')}`}
                  tooltip={item.title}
                  isActive={isParentActive}
                  onClick={() => setActiveParent(item.title!)}
                  className={cn(
                    "rounded-md text-sm font-medium px-3 py-2 h-9 transition-colors cursor-pointer",
                    isParentActive ? "bg-primary! text-primary-foreground!" : ""
                  )}
                >
                  {item.icon && <item.icon size={16} />}
                  <span>{item.title}</span>
                  <ChevronRight
                    className={cn(
                      "ml-auto transition-transform duration-200",
                      isOpen && "rotate-90"
                    )}
                  />
                </SidebarMenuButton>
              }
            />
            <CollapsibleContent>
              <SidebarMenuSub className="me-0 pe-0">
                {item.children!.map((child, index) => (
                  <NavMainSubItem
                    key={child.title || index}
                    item={child}
                    activeParent={activeParent}
                    setActiveParent={setActiveParent}
                    activeChild={activeChild}
                    setActiveChild={setActiveChild}
                    parentTitle={item.title}
                  />
                ))}
              </SidebarMenuSub>
            </CollapsibleContent>
          </SidebarMenuItem>
        </Collapsible>
      </SidebarMenu>
    </SidebarGroup>
  );
}

A useEffect inside NavMainItem syncs the local isOpen state with activeParent so that when a different parent is activated, the previously open collapsible stays open until the user explicitly closes it:

React.useEffect(() => {
  if (isParentActive) {
    setIsOpen(true);
  }
}, [isParentActive]);

The ChevronRight icon rotates 90 degrees when the submenu is open, using a Tailwind transition class:

<ChevronRight
  className={cn(
    "ml-auto transition-transform duration-200",
    isOpen && "rotate-90"
  )}
/>

How to Render Leaf Items

if (item.title) {
  return (
    <SidebarGroup className="p-0">
      <SidebarMenu>
        <SidebarMenuItem>
          <SidebarMenuButton
            id={`nav-main-button-${item.title.toLowerCase().replace(/\s+/g, '-')}`}
            tooltip={item.title}
            isActive={isParentActive}
            onClick={() => {
              setActiveParent(item.title!);
              setActiveChild(null);
            }}
            className={cn(
              "rounded-md text-sm font-medium px-3 py-2 h-9 transition-colors cursor-pointer",
              isParentActive ? "bg-primary! text-primary-foreground!" : ""
            )}
            render={<a href={item.href} />}
          >
            {item.icon && <item.icon />}
            {item.title}
          </SidebarMenuButton>
        </SidebarMenuItem>
      </SidebarMenu>
    </SidebarGroup>
  );
}

The render prop on SidebarMenuButton replaces the default button element with an <a> tag. This preserves correct anchor link semantics and accessibility while keeping the button's visual styling. When a leaf item is clicked, activeChild is reset to null since there is no child to track.

The NavMainSubItem function handles sub-items inside a collapsible. When a child is clicked, it sets both activeChild to itself and activeParent to its parent's title so the parent item remains visually highlighted:

if (item.title) {
  return (
    <SidebarMenuSubItem className="w-full">
      <SidebarMenuSubButton
        id={`nav-sub-button-${item.title.toLowerCase().replace(/\s+/g, '-')}`}
        className={cn(
          "w-full rounded-md transition-colors",
          activeChild === item.title ? "bg-muted! text-foreground!" : ""
        )}
        isActive={activeChild === item.title}
        onClick={() => {
          setActiveParent(parentTitle || "");
          setActiveChild(item.title!);
        }}
        render={<a href={item.href}>{item.title}</a>}
      />
    </SidebarMenuSubItem>
  );
}

The child uses a different active style (bg-muted with text-foreground) compared to the parent (bg-primary with text-primary-foreground). This visual distinction makes it easy to see both which section you are in and which specific page is currently active.

Sub-items also support nesting. If a child item itself has a children array, NavMainSubItem renders another Collapsible with a nested SidebarMenuSub, allowing you to build multi-level navigation trees without any changes to the data structure.

The full AppSidebar render function puts all of the pieces together:

export function AppSidebar() {
  return (
    <Sidebar variant="floating" className="p-4 h-full [&_[data-slot=sidebar-inner]]:h-full">
      <div className="flex flex-col gap-6 overflow-hidden">

        {/* Header with Logo */}
        <SidebarHeader className="px-4">
          <SidebarMenu>
            <SidebarMenuItem>
              <a href="#" className="w-full h-full">
                <Logo />
              </a>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarHeader>

        {/* Scrollable Navigation Content */}
        <SidebarContent className="overflow-hidden">
          <ScrollArea className="h-[calc(100vh-100px)]">
            <div className="px-4">
              <NavMain items={navData} />
            </div>

            {/* Promotional Card */}
            <div className="pt-5 px-4">
              <Card className="shadow-none ring-0 bg-secondary px-4 py-6">
                <CardContent className="p-0 flex flex-col gap-3 items-center">
                  <img
                    src="https://images.shadcnspace.com/assets/backgrounds/download-img.png"
                    alt="sidebar-img"
                    width={74}
                    height={74}
                    className="h-20 w-20"
                  />
                  <div className="flex flex-col gap-4 items-center">
                    <div>
                      <p className="text-base font-semibold text-card-foreground text-center">
                        Grab Pro Now
                      </p>
                      <p className="text-sm font-regular text-muted-foreground text-center">
                        Customize your admin
                      </p>
                    </div>
                    <Button className="w-fit h-9 px-4 py-2 shadow-none cursor-pointer rounded-xl hover:bg-primary/80">
                      Get Premium
                    </Button>
                  </div>
                </CardContent>
              </Card>
            </div>
          </ScrollArea>
        </SidebarContent>

      </div>
    </Sidebar>
  );
}

Let's walk through the key styling decisions:

variant="floating" gives the sidebar a card-like appearance with rounded corners and a subtle drop shadow. It visually lifts the sidebar off the background rather than making it flush with the page edge like a standard sidebar would.

[&_[data-slot=sidebar-inner]]:h-full is an arbitrary Tailwind variant selector that targets shadcn/ui's internal sidebar slot element. Without this, the sidebar inner container doesn't fill the full available height, which breaks the layout. The data-slot attribute is how shadcn/ui identifies internal sub-elements of compound components.

h-[calc(100vh-100px)] on ScrollArea makes the navigation list independently scrollable. The 100px offset accounts for the sidebar header and padding, so the scroll area doesn't overflow the viewport. The rest of the page layout remains static while the nav scrolls.

The bg-secondary card at the bottom of the scroll area is a common admin dashboard pattern, a soft prompt for an upgrade or onboarding action that lives passively in the sidebar without blocking navigation.

For more details on the Sidebar component's API, variants, and configuration options, refer to the official shadcn/ui sidebar docs.

Live Preview

f1538441-fa73-4eb0-af91-04f5bf4fab08

Summary

Congratulations! You have now built a complete, production-ready admin dashboard sidebar using shadcn/ui and a community block from Shadcn Space.

Here is a recap of everything you covered:

  • Setting up a Next.js project with shadcn/ui initialized and a pre-built sidebar block installed from Shadcn Space

  • Using SidebarProvider and SidebarTrigger to manage the sidebar open/closed state across a page layout through React context

  • Defining navigation data as a flat array of typed NavItem objects covering section labels, leaf items, and collapsible parent items

  • Rendering all three item types from a single navData source in the NavMain and NavMainItem components

  • Tracking activeParent and activeChild state in a single location and passing them as props so every item can read and update the shared selection state

  • Using Collapsible with a useEffect sync to keep parent items open when they are active, and animate the chevron icon on expand and collapse

  • Applying the floating variant, an arbitrary Tailwind slot selector, and ScrollArea with a calculated height to produce a polished, production-appropriate sidebar layout

This pattern scales well beyond what you built here. You can extend NavItem with additional fields like badge counts, permission flags, or external link indicators. You can swap in real href values and connect activeParent and activeChild to your router so the selection always reflects the current URL. You can also add more sections to navData without touching any rendering logic.

For a quick checkout, we have used the Shadcn Space free Shadcn dashboard block in this dashboard shell.

If you want to explore more pre-built admin UI blocks, components, and templates built on top of shadcn/ui, you can browse the full library at Shadcn Space.

Resources



Learn to code for free. freeCodeCamp's open source curriculum has helped more than 40,000 people get jobs as developers. Get started