Sidebar
A sidebar of sections whose single active marker arcs between rows and crossfades out of one section's ink into the next, as a static rail or as the same panel in a drawer.
Marker
Pick a row and the marker arcs to it, arriving in the colour of the section it lands in — jump between sections and it crossfades on the way, and the label is shoved aside as it arrives. The drawer is the same panel again, over the page.
Installation
npx shadcn@latest add fujiDevv/calamansi-ui/sidebarHow to use
import { Sidebar } from "@/components/ui/sidebar"export function Demo() {const [open, setOpen] = useState(false)return (<>{/* beside the content, from md up */}<Sidebarsections={SECTIONS}activeHref={pathname}marker="pip"className="sticky top-20 hidden h-[calc(100vh-5rem)] w-60 md:flex"/>{/* the same panel, over it, on a phone */}<Sidebarvariant="drawer"sections={SECTIONS}activeHref={pathname}open={open}onOpenChange={setOpen}onNavigate={() => setOpen(false)}/></>)}
Props
Options you can pass to customize this component.
sectionsoptionalSidebarSection[]`{ label, color, items }` — an eyebrow, the section's ink, and its rows. A row is a label, or `{ label, href, icon, badge, disabled }`; one with an href renders as a link, one without as a button.
activeHrefoptionalstringThe row to mark, matched against an item's href — the route-driven case. A route the nav does not contain leaves the marker off rather than parked on the wrong row.
valueoptionalnumberActive row as a flat index, for controlled use.
defaultValueoptionalnumberActive row as a flat index on mount. Ignored once `activeHref` or `value` is given.
onChangeoptional(index: number, item: SidebarItem) => voidFired with the flat index and the row that was picked.
onNavigateoptional(item: SidebarItem) => voidFired on every selection, whatever drives the active row. This is where a drawer gets closed.
headeroptionalReactNodeSits above the list and does not scroll — a wordmark, a project switcher.
footeroptionalReactNodePinned below the list and does not scroll — an account row, a version.
variantoptionalraildrawerThe static panel, or the sliding overlay. Both draw the same list, so they can be mounted side by side.
openoptionalbooleanWhether the drawer is showing. Drawers only.
onOpenChangeoptional(open: boolean) => voidFired when the drawer asks to close — Escape, the scrim, the close button. The page behind it is locked and unlocked for you.
navLabeloptionalstringThe nav's accessible name, and the drawer's dialog name.
markeroptionaldotpipbarglowShape of the active marker: a plain dot, the citrus-seed pip, a slim bar, or a dot carrying its own glow.
markerColoroptionalstringMarker colour, and the fallback for any section that does not bring its own.
fadeoptionalbooleanFade a list that overflows at both edges, with the 3rem of padding the stops are matched to — at rest the rows sit inside the opaque zone. The padding comes and goes with the fade, so turning it off leaves no dead space.
classNameoptionalstringThe panel's own box — width, position and borders. The rail does not position itself, so sticking it is your call: `sticky top-20 h-[calc(100vh-5rem)] w-60`.
Dependencies
License & Usage
- •Free to use and modify in personal and commercial projects.
- •Attribution to Calamansi UI is appreciated when using a component.
- •Please do not resell the components as your own kit.