Skip to content
Component installs need the registry setup
VegaStack Design

Breadcrumb Cascade

Two additions to a breadcrumb trail for a file browser — a crumb that takes dragged rows, and a menu of a segment's siblings.

Status
stable
Since
0.23.74
Accessibility pattern
menu with arrow keys; drops are pointer only

Last updated

Install

Add Breadcrumb Cascade from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/breadcrumb-cascade

The same command installs the registry items it composes: @vegastack/breadcrumb, @vegastack/button, @vegastack/dropdown-menu, @vegastack/use-drag-reorder.

Usage

import {
  BreadcrumbDropTarget,
  BreadcrumbSiblings,
} from "@/components/ui/breadcrumb-cascade";

<BreadcrumbItem>
  <BreadcrumbDropTarget
    href="/library/f/2"
    targetId="2"
    dragScope="library"
    onDropInto={move}
  >
    Projects
  </BreadcrumbDropTarget>
  <BreadcrumbSiblings label="Projects" items={siblings} />
</BreadcrumbItem>;

Both parts sit inside upstream's BreadcrumbItem, beside or in place of its BreadcrumbLink. BreadcrumbSiblings is a small chevron after a segment that opens a menu of the segment's siblings — the segment itself included and checked — so a reader can move sideways without going up a level first. The segment's own link is untouched.

Scope

  • Owns: the sibling menu of one segment, and a crumb that takes rows dragged from a list.
  • Does not own: the trail (upstream's Breadcrumb parts), the siblings' data, or the move itself — onDropInto hands the host the ids and the target.
  • Compose with: Breadcrumb, DataList (dragScope + onDropInto), and the host's "Move…" dialog as the keyboard path for a move.

Anatomy

BreadcrumbDropTarget
BreadcrumbSiblings — data-slot="breadcrumb-siblings-item" | "breadcrumb-siblings-menu" | "breadcrumb-siblings-status" | "breadcrumb-siblings-trigger"

Examples

Loading and empty

Pass onOpenChange to load a segment's siblings the first time its menu opens; loading shows "Loading…" in the menu until they arrive. A segment with nothing beside it says so.

Drop target

BreadcrumbDropTarget renders BreadcrumbLink, so the crumb is still the link it was. Rows dragged from a DataList with the same dragScope can be dropped on it — moving files up to a parent folder. While a drag is over it the crumb washes in the primary tint (data-drop-over), or in the destructive tint when canDropInto refuses (data-drop-invalid). A selected row carries the whole selection.

Name
Brief.pdf
Kickoff notes.md
Budget.xlsx

Drag a file onto a crumb.

API Reference

PropTypeDefaultDescription
items*readonly BreadcrumbSibling[]—The segment's siblings, the segment itself included (mark it current).
label*string—The segment's name — the menu trigger's and the menu's accessible names are built from it.
classNamestring—Extra classes on the trigger.
emptyMessagestring"Nothing else here"Shown when there is nothing to list.
linkRenderReact.ReactElement<unknown, string | React.JSXElementConstructor<any>><a />The element an entry's link renders — a router link such as <Link />.
loadingbooleanfalseThe siblings are loading: the menu shows loadingMessage in place of the entries.
loadingMessagestring"Loading…"Shown while loading.
onOpenChange((open: boolean) => void)—Called when the menu opens or closes — load the siblings on first open.
onSelect((id: string) => void)—Called with an entry's id when it is chosen (after its link, when it has one).
triggerLabel((label: string) => string)(label) => `Items next to ${label}`The trigger's accessible name.

Data attributes and CSS variables on BreadcrumbSiblings

AttributeValues
data-current""
data-slot"breadcrumb-siblings-item" | "breadcrumb-siblings-menu" | "breadcrumb-siblings-status" | "breadcrumb-siblings-trigger"
PropTypeDefaultDescription
id*string—A stable id, passed to onSelect.
label*string—The entry's name.
currentboolean—The segment the menu belongs to — marked with a check and aria-current.
hrefstring—Where the entry goes. Without it, choosing the entry only calls onSelect.
iconReact.ReactNode—A leading icon (a folder or a file kind).
PropTypeDefaultDescription
dragScope*string—The drag scope of the list whose rows may drop here (DataList's dragScope).
onDropInto*(move: BreadcrumbDropMove) => void | Promise<void>—Called for a valid drop. Return a promise for a server-gated move.
targetId*string—The segment's id — the folder rows dropped here move into.
canDropInto((move: BreadcrumbDropMove) => boolean)—Whether the drop is allowed — rows already in this folder, or a folder dropped into its own descendant. A refused segment shows data-drop-invalid and takes nothing.

Data attributes and CSS variables on BreadcrumbDropTarget

AttributeValues
data-drop-target""
PropTypeDefaultDescription
ids*string[]—The dragged row ids (a DataList drag carries its whole selection).
targetId*string—The segment's targetId.

Accessibility

  • The sibling trigger is an icon button named “Items next to” plus the segment’s name. The menu is the DS DropdownMenu: focus moves into it, the current segment carries aria-current="page" and a check, and Esc closes it with focus back on the trigger. While loading the menu is aria-busy and its one entry says "Loading…".
  • A drop target is pointer only, like every drag in the system: the keyboard path for a move is the host's "Move…" action (a MoveDialog or a FolderTree picker).
KeyAction
Enter / SpaceOpen the sibling menu; choose the focused entry
↓ / ↑Move through the siblings
EscClose the menu and return focus to the chevron
ContractStates tested
Behaviourdefault, open, current, loading, empty, drop-over, drop-invalid
Accessibilitybrowser-accessibility-test, labeled, busy, current, keyboard-menu
Visualdefault, hover, focus, drop-over, drop-invalid

Do / Don't

Do
Give the sibling menu the whole level, the current segment marked, and load it when it first opens.
Don't
Replace the segment's link with the menu — the crumb must still go where it says.
Do
Name the same dragScope on the list and its crumbs, and refuse the crumb of the folder the rows are already in.
Don't
Make a drop the only way to move something: keep a Move action for keyboard and touch.

On this page