Responsive sidebar

A source-copy application shell with persistent desktop navigation and an accessible mobile Sheet.

BlocksUpdated October 8, 2026
Application shellsresponsive-sidebar

A reusable frame for application navigationLink to section

ResponsiveSidebar gives a React application one bounded shell: persistent desktop navigation at the md breakpoint, a left-side mobile Sheet, a header and action region, and one main-content landmark. Brand, routes, active state, labels, icons, badges, footer, actions, and page content remain consumer-owned.

It does not include authentication, user identity, billing, dashboard data, backend calls, or product-specific navigation.

Source-copy Block

Copy the complete canonical directory into your application. There is no Blocks package, Registry installer, CLI, or automatic update channel for this Block today.

Canonical identityLink to section

ContractValue
CategoryApplication shells
Category slugapp-shells
Block slugresponsive-sidebar
Sourceapps/marketing/content/blocks/app-shells/responsive-sidebar/
ExportResponsiveSidebar
Informational future Registry identityapp-shells-responsive-sidebar

The Registry identity is reserved for future distribution work. It is not an available installation method and this Block does not create Registry metadata or generated JSON.

Copy sourceLink to section

Configure the public UI package and tokens, then create src/components/blocks/responsive-sidebar/ in your application. Copy all three files below, keeping their relative imports and client directives. In the Source tab, choose a file and use its copy button, or select the code manually when clipboard access is unavailable.

To retrieve all three files at once, open Code → Download ZIP in the Blocks catalog. Extract the archive and move its src/components/blocks/responsive-sidebar/ folder into your application.

File structureLink to section

index.tsx
sidebar-navigation.tsx
types.ts
FileResponsibility
index.tsxApplication layout, mobile Sheet and public exports.
sidebar-navigation.tsxShared desktop and mobile navigation, brand and footer.
types.tsNavigation items, router adapter and component props.

Import ResponsiveSidebar and its types from the directory's index.tsx entry point. Customize the navigation without changing the surrounding layout.

Skip to content

Overview

Your workspace, at a glance.

Keep track of projects and the work that matters next.

Active projects
3
Team members
3

Project activity

3 projects
  • Website redesign

    Design

    72% complete
  • Platform release

    Engineering

    48% complete
  • Brand guidelines

    Marketing

    100% complete

Fictional workspace · Changes stay in this preview.

Explore Blocks

The Preview tab renders the real canonical ResponsiveSidebar with fictional consumer navigation and content. Its links stay inside the preview document and no network or product service behavior is added. The Source tab includes the complete canonical files at build time, not a duplicated implementation.

Your application copy does not receive automatic updates. Follow the usage example below, connect your own behavior, and validate it in your application. Return to the Blocks catalog to choose another pattern.

Install by copying sourceLink to section

  1. Copy the complete responsive-sidebar directory to an application-owned path, such as src/components/blocks/responsive-sidebar/.
  2. Install React 18 or newer and the public @pycolors/ui package.
  3. Load the public @pycolors/tokens/tokens.css contract and configure the existing semantic Tailwind utilities used by your application.
  4. Import the Block from its copied local entry point.
app-shell.tsx
"use client";

import { ResponsiveSidebar } from "./components/blocks/responsive-sidebar";

const items = [
  { id: "overview", label: "Overview", href: "/overview" },
  { id: "projects", label: "Projects", href: "/projects", badge: "4" },
] as const;

export function AppShell({ children }: { children: React.ReactNode }) {
  return (
    <ResponsiveSidebar
      activeItemId="overview"
      brand={<strong>Acme</strong>}
      headerActions={<button type="button">Create project</button>}
      headerTitle={<h1>Overview</h1>}
      items={items}
      mobileDescription="Choose a workspace destination."
      mobileTitle="Workspace navigation"
      navigationLabel="Primary workspace navigation"
      sidebarFooter={<small>Acme workspace</small>}
    >
      {children}
    </ResponsiveSidebar>
  );
}

Icons are optional consumer-owned React nodes. The Block treats icon content as decorative and keeps the visible item label as the link's accessible name.

PropsLink to section

The preview includes local Overview, Projects and Team views, grouped navigation, a workspace identity and a member footer. “Add demo project” adds a fictional row only in the preview; no project is saved or sent to a backend. The integration guide links are ordinary navigation. Use the preview's Reset control to return to the initial state.

When embedding the shell inside an existing page, pass contentAs="div" to avoid nesting main landmarks. The default remains contentAs="main" for a full application shell. The skip link still targets the focusable content element. The mobile Sheet has a visually hidden accessible title and description so the workspace brand appears once. Navigation labels wrap, and narrow-screen navigation links and the menu trigger have at least 44px height.

PropTypeDefaultPurpose
brandReactNoderequiredConsumer brand or workspace identity.
navigationLabelstringrequiredAccessible label shared by desktop and mobile navigation.
mobileTitlestringrequiredAccessible Sheet title.
mobileDescriptionstringNavigate the application.Accessible Sheet description.
mobileTriggerLabelstringOpen navigationAccessible name for the icon-only mobile trigger.
itemsreadonly ResponsiveSidebarNavItem[]requiredPrimary ungrouped navigation items.
groupsreadonly ResponsiveSidebarNavGroup[][]Optional labelled navigation groups.
activeItemIdstringnoneStable item ID that receives aria-current="page".
renderLink(props) => ReactNodenative anchorRouter adapter seam.
sidebarFooterReactNodenoneOptional consumer footer content.
headerTitleReactNodenoneHeader title or context.
headerActionsReactNodenoneConsumer actions for the header.
contentAs"main" | "div"mainContent element; use div inside an existing main landmark.
contentIdstringmain-contentMain landmark ID and skip-link target.
skipToContentLabelstringSkip to contentVisible-on-focus skip-link text.
classNamestringnoneRoot layout customization.
childrenReactNoderequiredMain application content.

Navigation item IDs must be unique and stable across items and every group. The Block never derives active behavior from labels or URLs.

Responsive behaviorLink to section

  • Below md, the persistent sidebar is hidden and the labelled header trigger opens a left-side Sheet.
  • The Sheet is bounded to the viewport width and repeats the same consumer brand, navigation, groups, active state, and optional footer.
  • Native-anchor navigation closes the Sheet. A custom router must invoke the provided onNavigate callback after activation.
  • At md and above, the w-64 sidebar is sticky, viewport-height, and independently scrollable while the header and main content remain flexible with min-w-0.
  • Responsive visibility uses CSS utilities rather than viewport JavaScript.

Router adaptationLink to section

The default renderer uses native anchors. Adapt a client router through renderLink; preserve the supplied classes, active semantics, children, and mobile close callback.

router-adapter.tsx
<ResponsiveSidebar
  {...shellProps}
  renderLink={({ active, children, className, item, onNavigate }) => (
    <RouterLink
      aria-current={active ? "page" : undefined}
      className={className}
      href={item.href}
      onClick={onNavigate}
    >
      {children}
    </RouterLink>
  )}
>
  {children}
</ResponsiveSidebar>

RouterLink is a placeholder for the consumer's router component; the canonical Block does not import Next.js or another routing library.

Accessibility and keyboard behaviorLink to section

  • A visible-on-focus skip link targets the single focusable main landmark.
  • Desktop and mobile navigation expose the required consumer label.
  • The active native link uses aria-current="page".
  • The mobile trigger is a labelled Button with visible focus styling.
  • The public @pycolors/ui Sheet supplies dialog focus containment, Escape and outside-interaction dismissal, a labelled close button, and focus restoration to the trigger.
  • SheetTitle and SheetDescription provide the mobile dialog name and description.
  • Navigation remains native link navigation; there is no custom roving focus or arrow-key model.

Consumers must keep item labels visible, preserve aria-current in a custom renderer, provide meaningful navigation and Sheet labels, and avoid placing a second main landmark inside children. These guarantees are verified by focused interaction and axe tests; they are not a claim of full WCAG certification.

Styling and customizationLink to section

The Block uses semantic background, foreground, muted, border, accent, and ring utilities backed by the public tokens contract. It inherits the consumer's light or dark token values and introduces no colors, fonts, theme provider, global CSS, or local UI primitive copies.

After copying, the consumer owns customization and future maintenance. Compare future canonical changes deliberately and apply only the updates that fit the application. Rollback is removal or reversion of the copied directory and its local integration; no package version or remote Block state is involved.

Explicit non-goalsLink to section

This Block does not provide user/session state, permissions, billing, metrics, data fetching, server actions, product navigation, a full dashboard, a package, a Registry item, generated Registry JSON, a CLI, automatic updates, a release, or deployment. Starter Free informed the composition but is not imported and is not source authority.