Skip to content

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.

glass/layout/sidebar-floating
Open ↗

Source

"use client";

import type { ReactNode } from "react";
import { CalendarDays, Home, MessagesSquare, Settings, Sparkles, Video, type LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";

export interface SidebarFloatingItem {
  label: string;
  href: string;
  icon: LucideIcon;
  badge?: number;
}

export interface SidebarFloatingProps {
  items?: SidebarFloatingItem[];
  activeHref?: string;
  user?: { name: string; initials: string };
  children?: ReactNode;
  className?: string;
}

const GLASS = "da-stroke bg-da-surface backdrop-blur-da backdrop-saturate-150";

const DEFAULT_ITEMS: SidebarFloatingItem[] = [
  { label: "Home", href: "#home", icon: Home },
  { label: "Meetings", href: "#meetings", icon: Video, badge: 3 },
  { label: "Calendar", href: "#calendar", icon: CalendarDays },
  { label: "Ask Halo", href: "#ask", icon: Sparkles },
  { label: "Messages", href: "#messages", icon: MessagesSquare },
];

/** Floating glass icon rail detached from the viewport edges: gradient active pill, tooltips on hover/focus, badges and avatar at the bottom; becomes a bottom bar on mobile. */
export function SidebarFloating({
  items = DEFAULT_ITEMS,
  activeHref = "#meetings",
  user = { name: "Amara Diallo", initials: "AD" },
  children,
  className,
}: SidebarFloatingProps) {
  const link = (it: SidebarFloatingItem, mobile = false) => {
    const active = it.href === activeHref;
    return (
      <a
        href={it.href}
        aria-current={active ? "page" : undefined}
        className={cn(
          "da-focus da-transition group relative grid size-11 place-items-center rounded-da-md",
          active ? "bg-gradient-to-br from-da-primary to-da-accent text-da-primary-fg shadow-da-md" : "text-da-muted-fg hover:bg-da-surface hover:text-da-fg",
        )}
      >
        <it.icon aria-hidden className="size-5" />
        <span className="sr-only">{it.label}</span>
        {it.badge !== undefined && (
          <span
            aria-hidden
            className="absolute -top-1 -right-1 grid size-5 place-items-center rounded-full bg-da-danger text-[10px] font-semibold text-da-danger-fg ring-2 ring-da-bg"
          >
            {it.badge}
          </span>
        )}
        {!mobile && (
          <span
            aria-hidden
            className={cn(
              "pointer-events-none absolute left-full z-20 ml-4 rounded-da-pill px-3 py-1.5 text-sm font-medium whitespace-nowrap text-da-fg opacity-0 shadow-da-md transition-[opacity,translate] duration-(--da-duration) group-hover:translate-x-1 group-hover:opacity-100 group-focus-visible:translate-x-1 group-focus-visible:opacity-100",
              GLASS,
            )}
          >
            {it.label}
          </span>
        )}
      </a>
    );
  };

  return (
    <div className={cn("min-h-dvh text-da-fg", className)}>
      <aside className={cn("fixed top-4 bottom-4 left-4 z-30 hidden w-[68px] flex-col items-center rounded-da-lg py-4 shadow-da-lg md:flex", GLASS)}>
        <span aria-hidden className="grid size-10 place-items-center rounded-full bg-gradient-to-br from-da-primary to-da-accent">
          <span className="size-3.5 rounded-full bg-da-bg/90" />
        </span>
        <nav aria-label="Main" className="mt-6 flex flex-1 flex-col gap-2">
          {items.map((it) => (
            <span key={it.href}>{link(it)}</span>
          ))}
        </nav>
        {link({ label: "Settings", href: "#settings", icon: Settings })}
        <span
          role="img"
          aria-label={user.name}
          className="mt-3 grid size-10 place-items-center rounded-full bg-gradient-to-br from-da-accent to-da-success text-xs font-semibold text-da-primary-fg ring-2 ring-da-bg"
        >
          {user.initials}
        </span>
      </aside>
      <nav aria-label="Main" className={cn("fixed inset-x-4 bottom-4 z-30 flex justify-around rounded-da-pill p-2 shadow-da-lg md:hidden", GLASS)}>
        {items.map((it) => (
          <span key={it.href}>{link(it, true)}</span>
        ))}
      </nav>
      <main className="pb-28 md:pb-0 md:pl-[100px]">{children}</main>
    </div>
  );
}

export default SidebarFloating;

modules/glass/layout/sidebar-floating/index.tsx

Props

PropTypeDefaultDescription
items{ label, href, icon, badge? }[]—Nav items.
activeHrefstring"#meetings"Current page.
user{ name, initials }—Avatar.
childrenReactNode—Page content.
classNamestring—Classes.

Other sidebar variants in Glass

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

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

App Sidebar

Friendly app shell: warm sidebar with emoji workspace chip, big periwinkle “New task” pill, search field, nav with peach count bubbles and a colored-dot board list; slide-in drawer on mobile.

Soft FlatSidebar

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 FlatSidebar

Sidebar Card

Floating white sidebar card with generous radius on a tinted page: two-dot logo, nav with lavender active pill, butter “tip of the day” card and user row; horizontal nav on mobile.

Soft FlatSidebar

Sidebar Rail

Slim rounded icon rail: round icons whose active item fills with its own pastel color, dark pill tooltips, emoji avatar at the bottom; floating bottom bar on mobile.

Soft FlatSidebar