Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Peek and Pin Icon Sidebar Block for React

A team workspace in a fixed-height window whose icon rail peeks open over the page on hover or focus without moving it, and a pin makes the open rail push the page and stay.

Framework
UI library

Preview

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Overview

Keep the page's room and still show the labels with this peek and pin icon sidebar for React with Base UI. A window framed by a hairline with a plus mark on each corner holds the Sidebar collapsed to its icons. Rest the pointer on the rail, or move focus into it, and after a short pause the full labelled panel opens over the page, with no layout shift, because the rail's gap stays at the icon width while it only peeks. Move away or press Escape and it closes. The pin in the panel's header, or the menu button in the top bar, makes the panel stay open and the page move over to make room. Five destinations (Inbox, Tasks, Docs, Calendar and People) each show their own list in real text, with a count and a dot on what is new. It's built from Sidebar, Toggle, Kbd, Avatar and Guides, with CSS transitions only. Below the medium breakpoint the rail is a sheet and nothing peeks. For team tools, admin consoles and any app whose navigation should stay out of the way until it is wanted.

FAQ

How does the panel open without moving the page?

The provider's open is driven by the block: open while the rail is pinned or peeking. The Sidebar keeps a "gap" element that holds the rail's width in the layout, and the panel itself is an absolutely positioned box over it. While the rail is not pinned, one class on the provider, [&_[data-slot=sidebar-gap]]:w-(--sidebar-width-icon)!, keeps the gap at the icon width even though the panel grows to 16rem, so it lies over the page instead of pushing it. Pinning drops that class, the gap grows with a short width transition and the page makes room. A shadow marks the panel while it only peeks.

Why does it wait before opening?

So that the pointer passing over the rail on its way to the page doesn't flash the panel open. The rail opens 120 ms after the pointer or focus arrives, and closes 100 ms after both are gone. Focus counts like the pointer: Tab into the rail and the panel opens with the labels and the pin, and Tab out and it closes. Escape closes it from anywhere in the window and keeps it closed until you leave the rail and come back; the icons keep their tooltips in the meantime. On a touch screen below the medium breakpoint the rail is a sheet, so none of this runs.

What does the pin do, and how is it different from the menu button?

Both do the same thing: the Sidebar's open state follows the pin. The pin sits in the panel's header, where the peek shows it, and says "Keep sidebar open" as a toggle button; the menu button in the top bar is the catalog trigger, which also opens the sheet on a phone. Unpinning returns to the icons and the peek. The status line at the foot says which of the two states you are in.

What stays in the page, and how do I connect it?

Everything. The five lists are typed in PLACES at the top of the file, the counts and the dots come from them, and the times are measured from one fixed AS_OF, so nothing depends on the clock. Replace PLACES with your data. The destinations are links to # whose handler shows the list instead of navigating. They're SidebarMenuButtons with render set to an a element. Point them at your routes and mark the current one with isActive.

How does it work on a phone?

Below the medium breakpoint the sidebar is a sheet that the menu button opens from the start edge, the right in a right-to-left layout. Choosing a destination closes it and puts focus back on the button. There is no peek and no pin there, since the sheet is already as wide as the labelled panel. The window keeps its height, and the status line says which list is open and how many items it holds.

Is it accessible?

The rail is a navigation landmark named after the product, and the open destination is marked as the current page. Each link's name says what is new in it ("Inbox, 2 new") even while only its icon shows, and a tooltip repeats it. The peek opens for keyboard focus as it does for the pointer, Escape closes it, and the pin is reachable by Tab once the panel is open. The panel's transitions stop under reduced motion. One polite status line reports the pin, a closed peek and each destination.

  • Pro

    Sidebar Toggle Navbar

    Mobile and drawers

    A navbar with a sidebar toggle for a project tracker: a button, the [ key and a tooltip fold a mini sidebar under the bar between icons only and icons with labels, and on phones the same button opens it as a sheet.

  • Pro

    A features section for a realtime sync engine: a thin icon rail of six streams and a panel with the stream's name at display size beside a vertical marquee of its sync events, with a visible Pause button.

  • Pro

    A note-taking app in a fixed-height window whose narrow rail keeps a caption under every icon, with a pill on the open destination, a dot on Inbox while it holds unopened notes and Alerts and Settings pinned at the foot.

  • Pro

    A messaging app in a fixed-height window whose icon rail opens a menu card beside each icon on hover or focus, with the section's name, one line and its links; choosing a link sets the page and the top bar shows the path.

  • Pro

    A reading app in a fixed-height window whose sidebar slides fully out of view, so the article's line length grows from 62 to 78 characters; the trigger, the edge rail and Ctrl or Command with B bring it back.

  • Pro

    Controlled State Sidebar

    State and theming

    A layout playground in a fixed-height window with a controlled sidebar: Open, Close, Toggle, a switch and Ctrl or Command with B all drive the provider, and every onOpenChange call is logged with where it came from.