# A shadcn sidebar for React apps: clear navigation on desktop and mobile

> Build a shadcn sidebar that scales with your React app: plan route groups, mark the active page, and make collapse and mobile navigation usable.
> By Dave · 2026-10-12
> Source: https://otf-kit.dev/blog/shadcn-sidebar-howto

## Why sidebars become hard to use as an app grows

A **shadcn sidebar** is a useful starting point for React navigation, but installing the component is only the first decision. The harder work is deciding what belongs in the sidebar, how people recognize their current location, and what changes when the same app is opened on a narrow screen.

Start with a route map, not a list of icons. Write down the destinations a user needs every day, group related pages under labels, and separate navigation from one-off actions such as creating a project or opening account settings. When those jobs are clear, the component structure becomes easier to choose and the sidebar stays understandable as the product adds screens.

The official shadcn/ui sidebar is composable: its documented pieces include a provider, sidebar, header, scrollable content, groups, menu items, footer, and trigger. It also supports different collapse behaviors. Those building blocks give a React app room to express its own route model; they do not decide that model for you.

## Map destinations before arranging components

List the destinations by the work users perform, not by the database or team that owns each feature. A workspace with overview, projects, reports, and settings might use two groups: “Workspace” for the first three destinations and “Account” for settings. If a page belongs to more than one group, choose one canonical place in the navigation and link to it from the page where it is relevant.

Keep primary routes visible. Put occasional destinations in a secondary group, and place account actions in a footer or profile menu only when users can still find them quickly. Avoid making the sidebar a dumping ground for every action in the application. A button that creates a report is an action; “Reports” is a destination. Keeping these distinct helps users predict what a click will do.

A plain navigation list is often enough:

~~~tsx
function WorkspaceNavigation({ pathname }: { pathname: string }) {
  const links = [
    { href: "/workspace", label: "Overview" },
    { href: "/workspace/projects", label: "Projects" },
    { href: "/workspace/reports", label: "Reports" },
  ];

  return (
    <nav aria-label="Workspace">
      <ul>
        {links.map((link) => (
          <li key={link.href}>
            <a
              href={link.href}
              aria-current={pathname === link.href ? "page" : undefined}
            >
              {link.label}
            </a>
          </li>
        ))}
      </ul>
    </nav>
  );
}
~~~

The link text identifies the destination, while aria-current="page" marks the active route. If you use the shadcn sidebar primitives, map this route data into SidebarGroup, SidebarMenu, SidebarMenuItem, and SidebarMenuButton rather than hard-coding the same links in several places. Keep routing in ordinary links so browser history, open-in-new-tab behavior, and direct URLs continue to work.

![Byte and Nova review a dashboard sidebar with grouped navigation and a highlighted active route.](https://cdn.otf-kit.dev/blog/shadcn-sidebar-howto/inbody1-hierarchy-active-20261012a.png)

## Make the active page obvious

A sidebar is a map. People should be able to glance at it and answer “Where am I?” without rereading every label. Use a restrained active treatment that combines more than a subtle color shift: a clear background, a border or indicator, and the current-page state in the link markup. Check contrast in both themes and at the smallest supported text size. Do not rely on color alone to distinguish the active route.

For nested routes, decide whether a parent group stays open while someone reads a child page. For example, when “Reports” contains “Usage” and “Exports,” navigating to either child should keep the parent visible and indicate the selected child. Test a direct visit to the child URL as well as navigation from the parent. If the active item is set only after a click, a refresh or shared deep link may show no selected page.

Use labels that describe the destination, not its visual position. “Billing” tells a user more than “Bottom section,” and “Workspace settings” distinguishes project configuration from personal preferences. If two destinations have similar names, make the difference visible in the sidebar rather than expecting users to remember where each one lives.

## Choose collapse behavior for the job

The shadcn sidebar supports a fixed panel, a sidebar that collapses to icons, and an off-canvas pattern. Choose based on screen space and task frequency. A permanent wide panel works well when people move among many destinations throughout the day. An icon rail can recover room for a dense work area, but it removes the text that made each destination immediately recognizable. An off-canvas panel can suit a phone where persistent navigation would crowd the screen.

If you collapse to icons, retain an accessible name for every destination and make the expanded label available on demand. Tooltips can help a sighted mouse user, but should not be the only way to identify an icon. Test the rail using a keyboard and a screen reader, and make sure the focus indicator remains visible when the panel narrows. The collapse control itself also needs a clear name and a state users can understand.

On a phone, test the trigger, the open panel, and the destination link as a complete flow. The panel should not cover the action that opens it, and a user should be able to reach and activate links without precision tapping. After a destination is chosen, the panel should close or otherwise make the new page content available. Verify what happens to keyboard focus when the panel opens and closes; do not assume that a desktop sidebar and a mobile drawer behave identically.

![Byte and Luna compare a collapsed desktop sidebar with a mobile navigation panel.](https://cdn.otf-kit.dev/blog/shadcn-sidebar-howto/inbody2-desktop-mobile-collapse-20261012b.png)

## Keep navigation usable with a keyboard

Tab through the page from the browser address bar. Confirm that the navigation links appear in a sensible order, the focused item has a visible treatment, and no collapsed item remains in the tab order while hidden. Use Enter to follow a link and the sidebar trigger with the keyboard. If a group expands and collapses, verify that its control communicates its expanded state and that its links remain reachable.

Destination links and application commands have different jobs. Keep route changes as links and controls such as “Create project” as buttons. This gives users familiar browser behavior and avoids making a list of destinations behave like a command menu. Do not add a custom keyboard interaction model unless the UI actually needs one; each extra mode adds behavior that has to be designed and tested.

If the product has more than one navigation region, give each one a useful name such as “Workspace” and “Account.” If there is only one, a short “Primary” label is sufficient. The label should describe its purpose, not its position on the screen. Also check that icon-only buttons such as collapse, search, and account controls expose a concise action name. Visible labels are easier for everyone to understand and maintain.

## Make growth a data change, not a redesign

Store route labels, destinations, icons, and grouping in one typed data structure when the same navigation appears across layouts. This makes it easier to add a page without copying markup into desktop, mobile, and collapsed variants. Keep permissions separate from presentation: a route may be hidden when a user lacks access, but a hidden link is not a substitute for checking authorization at the destination.

Keep group names short and the visible link labels predictable. If a group grows, use a deliberate hierarchy before adding a second level of nesting. Deep trees make users track several open states at once, while a flat list that is too long forces repeated scanning. Review the top tasks with real users or support questions, then put frequent destinations near the top and rarely used destinations in a clearly named secondary group.

Use one icon family and a consistent size and stroke so the sidebar reads as one system. If you are deciding how to keep those symbols consistent across a React interface, our guide to [choosing an icon set for a shadcn-style app](/blog/shadcn-icons-react-consistent-ui) covers package checks, visual comparison, and accessible names.

## Test the sidebar before shipping

Check the same routes at desktop and phone widths, in light and dark themes, and with a long project name. A useful review includes these cases:

- Open a nested page directly and confirm the right group and item appear active.
- Collapse and expand the desktop sidebar; verify every destination is still identifiable.
- Open the mobile panel, follow a link, and confirm the new page is usable immediately.
- Use only the keyboard to reach the trigger, groups, links, and any account actions.
- Test with text zoomed in and confirm labels do not overlap or disappear.
- Compare the sidebar against the rest of the interface so icons, spacing, and active states feel consistent.

Preview the [OTF sidebar component](/components/sidebar) to inspect the available implementation, then install the free SDK if you need the component in an existing React app. If navigation is only one part of a larger build still delayed by authentication, billing, data, or the surrounding screens, compare the kits and templates and move from SDK installation to a kit checkout when a fuller starting point solves that broader problem.

## Sources

- [shadcn/ui Sidebar documentation](https://ui.shadcn.com/docs/components/base/sidebar) — documented composition, grouping, active items, triggers, and collapse behavior.
