Skip to content

Sidebar Boards

Board navigator: starred favorites, then collapsible emoji spaces holding emoji boards with count bubbles; active board in a white pill; dashed “New space” button.

soft-flat/layout/sidebar-boards
Open ↗

Source

"use client";

import { useState, type ReactNode } from "react";
import { ChevronRight, Plus, Star } from "lucide-react";
import { cn } from "@/lib/utils";

export interface SidebarBoardsGroup {
  name: string;
  emoji: string;
  boards: { name: string; href: string; emoji: string; count?: number }[];
}

export interface SidebarBoardsProps {
  favorites?: { name: string; href: string; emoji: string }[];
  groups?: SidebarBoardsGroup[];
  activeHref?: string;
  children?: ReactNode;
  className?: string;
}

const DEFAULT_GROUPS: SidebarBoardsGroup[] = [
  {
    name: "Marketing",
    emoji: "📣",
    boards: [
      { name: "Q4 content", href: "#q4", emoji: "✍️", count: 12 },
      { name: "Newsletter", href: "#newsletter", emoji: "💌", count: 3 },
    ],
  },
  {
    name: "Product",
    emoji: "🧩",
    boards: [
      { name: "Website relaunch", href: "#website", emoji: "🚀", count: 9 },
      { name: "Bugs", href: "#bugs", emoji: "🐞", count: 4 },
    ],
  },
  { name: "People", emoji: "👥", boards: [{ name: "Hiring: designer", href: "#hiring", emoji: "🎨", count: 6 }] },
];

/** Board navigator: favorites with stars, then collapsible emoji spaces holding emoji boards with count bubbles; the active board gets a white pill. */
export function SidebarBoards({
  favorites = [
    { name: "Website relaunch", href: "#website", emoji: "🚀" },
    { name: "This week", href: "#week", emoji: "🗓️" },
  ],
  groups = DEFAULT_GROUPS,
  activeHref = "#website",
  children,
  className,
}: SidebarBoardsProps) {
  const [closed, setClosed] = useState<Record<string, boolean>>({});
  const row = (active: boolean) =>
    cn(
      "da-focus da-transition flex h-10 items-center gap-2.5 rounded-da-md px-3 text-[15px]",
      active ? "bg-da-surface font-semibold shadow-da-sm" : "text-da-muted-fg hover:bg-da-surface/60 hover:text-da-fg",
    );

  return (
    <div className={cn("flex min-h-dvh flex-col bg-da-bg text-da-fg md:flex-row", className)}>
      <aside className="bg-da-surface-2 p-4 md:sticky md:top-0 md:h-dvh md:w-72 md:shrink-0 md:overflow-y-auto">
        <nav aria-label="Boards">
          <p className="mb-2 flex items-center gap-1.5 px-3 text-xs font-bold text-da-muted-fg uppercase">
            <Star aria-hidden className="size-3.5 fill-da-warning text-da-warning" /> Favorites
          </p>
          <ul className="space-y-1">
            {favorites.map((f) => (
              <li key={f.href}>
                <a href={f.href} aria-current={f.href === activeHref ? "page" : undefined} className={row(f.href === activeHref)}>
                  <span aria-hidden>{f.emoji}</span>
                  {f.name}
                </a>
              </li>
            ))}
          </ul>
          <div className="mt-6 space-y-2">
            {groups.map((g) => {
              const isClosed = closed[g.name];
              return (
                <div key={g.name}>
                  <button
                    type="button"
                    aria-expanded={!isClosed}
                    onClick={() => setClosed((c) => ({ ...c, [g.name]: !c[g.name] }))}
                    className="da-focus flex h-10 w-full items-center gap-2 rounded-da-md px-3 font-da-display font-extrabold hover:bg-da-surface/60"
                  >
                    <ChevronRight
                      aria-hidden
                      className={cn("size-4 transition-transform duration-(--da-duration) ease-da-emphasized", !isClosed && "rotate-90")}
                    />
                    <span aria-hidden>{g.emoji}</span>
                    {g.name}
                  </button>
                  <ul hidden={isClosed} className="mt-1 space-y-1 pl-6">
                    {g.boards.map((b) => (
                      <li key={b.href}>
                        <a href={b.href} aria-current={b.href === activeHref ? "page" : undefined} className={row(b.href === activeHref)}>
                          <span aria-hidden>{b.emoji}</span>
                          <span className="truncate">{b.name}</span>
                          {b.count !== undefined && (
                            <span className="ml-auto rounded-full bg-da-surface px-2 text-xs font-bold text-da-muted-fg">{b.count}</span>
                          )}
                        </a>
                      </li>
                    ))}
                  </ul>
                </div>
              );
            })}
          </div>
          <a
            href="#new-space"
            className="da-focus mt-4 flex h-10 items-center gap-2 rounded-da-md border-2 border-dashed border-da-border-strong px-3 text-sm font-semibold text-da-muted-fg hover:border-da-primary hover:text-da-fg"
          >
            <Plus aria-hidden className="size-4" /> New space
          </a>
        </nav>
      </aside>
      <main className="min-w-0 flex-1">{children}</main>
    </div>
  );
}

export default SidebarBoards;

modules/soft-flat/layout/sidebar-boards/index.tsx

Props

PropTypeDefaultDescription
favorites{ name, href, emoji }[]—Starred.
groups{ name, emoji, boards }[]—Spaces.
activeHrefstring—Current.
childrenReactNode—Content.

Other sidebar variants in Soft Flat

Sidebar in other art directions

App Sidebar

App shell with a sticky left sidebar: workspace header, grouped nav with yellow active state and counters, usage meter, user footer. Becomes a top bar + slide-over drawer below lg.

BrutalistSidebar

Icon Rail

Compact 80px icon rail: ink brand square, square icon links with yellow active state, red counters and ink tooltips on hover/focus, settings pinned at the bottom. Server-safe, CSS-only tooltips.

BrutalistSidebar

Sidebar Collapsible

App sidebar that collapses from 256px to a 76px icon column with a stepped width animation; ink active item, labels become sr-only when collapsed, collapse toggle pinned at the bottom.

BrutalistSidebar

Sidebar Dual

Two-level navigation: an ink icon rail switches sections, and a paper second panel lists that section's links with counts and a yellow active link. Second panel hides on very small screens.

BrutalistSidebar

App Sidebar

Floating frosted sidebar inset from the viewport edges: gradient-orb brand, pill nav items with an ink active state and gradient badges, workspace group, gradient promo card and user chip. Becomes a floating pill top bar + glass drawer below lg.

GlassSidebar

Sidebar Adaptive

Adaptive navigation: a full glass sidebar with brand, gradient action button, labelled links and a usage meter on desktop; a floating bottom tab bar with a raised gradient “+” on mobile.

GlassSidebar

Sidebar Channels

Chat-app style double sidebar: a column of gradient workspace squircles plus a glass channel panel with collapsible sections, private/live icons, unread counts and LIVE meeting badges; drawer on mobile.

GlassSidebar

Sidebar Floating

Floating glass icon rail detached from the viewport edges: gradient logo orb, gradient active item, unread badges, glass tooltips on hover/focus, settings and avatar at the bottom; a floating bottom bar on mobile.

GlassSidebar

App Sidebar

Linear-style app shell: 240px translucent sidebar with workspace switcher, search, "New" button with shortcut, grouped 32px nav rows with counters, team list with color squares and settings at the bottom. Slide-in drawer below lg.

MinimalSidebar

Sidebar Inset

Inset app shell: the sidebar sits on the tinted page background while content lives in a raised rounded panel. Workspace header, collapsible nested sections with guide line, support/settings and user card; drawer on mobile.

MinimalSidebar

Sidebar Rail

Sidebar that collapses between a 224px labelled list and a 56px icon rail (⌘B or bottom button) with tooltips on hover/focus and corner badges; icon-only on mobile. Controlled or uncontrolled.

MinimalSidebar

Sidebar Tree

Docs / settings sidebar with a filter box and a nested collapsible tree auto-expanded to the active page (highlighted in indigo); stacks above content on mobile.

MinimalSidebar

App Sidebar

Typographic app sidebar: hairline right border, logo + site switcher, mono group labels, plain text links with right-aligned mono counts; the active link is ink with a signal dot; drawer on mobile.

Mono CleanSidebar

Sidebar Icons

Narrow icon rail (56px) of square hairline cells; the active cell is filled ink; labels appear as mono tooltips to the right on hover/focus; turns into a bottom bar on mobile.

Mono CleanSidebar

Sidebar Ink

Ink sidebar (stays near-black in dark mode): giant numbered text links “01 Overview”, the active one full white with an arrow, a storage meter as a hairline bar at the bottom; drawer on mobile.

Mono CleanSidebar

Sidebar Tree

CMS page tree: nested pages drawn with hairline guide lines and +/− toggles (aria-expanded), mono “Pages” header with an add button; active page ink with signal dot; drawer on mobile.

Mono CleanSidebar

App Sidebar

Classic enterprise app shell: white bordered sidebar with brand, entity switcher, grouped nav (uppercase group labels, count badges, blue active state), settings and user footer; off-canvas drawer on mobile.

Neo CorporateSidebar

Sidebar Collapsible

Sidebar that collapses from 240px to a 64px icon rail with a toggle button (aria-expanded); collapsed items keep tooltips (title) and sr-only labels. Always a rail on small screens.

Neo CorporateSidebar

Sidebar Dual

Two-level navigation: a narrow icon rail of top-level sections (tooltips, aria-pressed) and a secondary panel listing the selected section’s pages; the panel hides below md.

Neo CorporateSidebar

Sidebar Navy

Dark navy sidebar (fg-colored in light mode, surface in dark mode — dark in both): brand, search field, nav with white-tint active state and badges, and a cash-balance card at the bottom; drawer on mobile.

Neo CorporateSidebar

App Sidebar

Calm app shell: cream sidebar with leaf logo, pill nav items (sage pill for the active page), a small “this year” footprint card with a sprout, and settings at the bottom; drawer on mobile.

Organic SoftSidebar

Sidebar Floating

Floating sidebar: a rounded cream panel inset from the page edges with a soft shadow, italic serif group labels, a sage “New entry” pill and sage-tinted active items; drawer on mobile.

Organic SoftSidebar

Sidebar Garden

Deep sage sidebar (dark sage tint in dark mode) with cream text, a translucent active pill, count bubbles, rolling hills illustrated along the bottom and the user’s name on the hill; drawer on mobile.

Organic SoftSidebar

Sidebar Rail

Slim round icon rail: circular icon links on a sand column, the active one a sage circle with a leaf-shaped marker; labels appear as soft tooltips on hover/focus; becomes a bottom tab bar on mobile.

Organic SoftSidebar