Viewport Bookmarks

Saved-view list for the canvas — pinned spatial locations with optional active state. Install Viewport Bookmarks from the VLLNT UI registry with the shadcn CLI.

Report a bug

What it is and when to use

Viewport Bookmarks is a saved-view list for a canvas — the spatial parallel of pinned tabs. Each entry names a stored pan and zoom target, with an optional accent color and a secondary detail line. Rows render as buttons when you pass an onSelect handler, or as read-only text when you do not, and an active id highlights the current view. It ships with an empty state and localizable labels. It is pure presentation; your app owns the bookmark store and the camera animation.

Navigation

Saved-view list for the canvas — pinned spatial locations with optional active state. Part of the navigation family in VLLNT UI, it ships as a machine-readable registry entry — copy the source directly into your app with the shadcn CLI and own it, no runtime dependency on a component library.

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

Add Viewport Bookmarks to your project with the shadcn CLI. The source lands in your codebase, ready to adapt:

pnpm dlx shadcn@latest add https://ui.vllnt.com/r/viewport-bookmarks.json

Source

"use client"; import type { ComponentPropsWithoutRef, ReactNode } from "react"; import { cn } from "../../lib/utils"; /** * One saved viewport. * * @public */ export type ViewportBookmark = { /** Optional accent color for the row glyph. */ color?: string; /** Optional secondary line (zoom level, last-visited, owner). */ detail?: ReactNode; /** Stable identifier — used as the React key. */ id: string; /** Display name for the bookmark. */ label: ReactNode; }; /** * Localizable strings. * * @public */ export type ViewportBookmarksLabels = { /** Empty-state copy. Defaults to `"No saved views"`. */ empty?: string; /** Aria-label override. Defaults to `"Viewport bookmarks"`. */ region?: string; }; const DEFAULT_LABELS = { empty: "No saved views", region: "Viewport bookmarks", } as const satisfies Required<ViewportBookmarksLabels>; /** * Props for {@link ViewportBookmarks}. * * @public */ export type ViewportBookmarksProps = { /** Optional active bookmark id — renders the row in the selected state. */ activeId?: string; /** Bookmark entries in render order. */ bookmarks: ViewportBookmark[]; /** Localizable strings. */ labels?: ViewportBookmarksLabels; /** Click handler — receives the activated bookmark id. */ onSelect?: (id: string) => void; /** Optional title rendered above the rows. Defaults to `"Saved views"`. */ title?: ReactNode; } & ComponentPropsWithoutRef<"section">; const Row = (props: { active: boolean; bookmark: ViewportBookmark; onSelect?: (id: string) => void; }): React.ReactElement => { const { active, bookmark, onSelect } = props; const handleSelectBookmark = (): void => { onSelect?.(bookmark.id); }; const rowClass = "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs transition-colors"; const activeClass = active ? "bg-muted/60 text-foreground" : "text-muted-foreground hover:bg-muted/30 hover:text-foreground"; if (onSelect) { return ( <button aria-pressed={active} className={cn( rowClass, activeClass, "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", )} data-viewport-bookmark={bookmark.id} data-viewport-bookmark-active={active} onClick={handleSelectBookmark} type="button" > <RowBody bookmark={bookmark} /> </button> ); } return ( <span className={cn(rowClass, activeClass)} data-viewport-bookmark={bookmark.id} data-viewport-bookmark-active={active} > <RowBody bookmark={bookmark} /> </span> ); }; const RowBody = (props: { bookmark: ViewportBookmark }): React.ReactElement => { const { bookmark } = props; return ( <> <span aria-hidden="true" className="size-1.5 rounded-full" style={{ backgroundColor: bookmark.color ?? "oklch(var(--foreground))", }} /> <span className="flex flex-1 flex-col text-left"> <span className="truncate font-medium">{bookmark.label}</span> {bookmark.detail ? ( <span className="truncate text-[10px] text-muted-foreground" data-viewport-bookmark-detail > {bookmark.detail} </span> ) : null} </span> </> ); }; /** * Saved-view list for the canvas — the spatial parallel of a tab * bar's pinned tabs. Each bookmark stores a viewport target the host * resolves to a pan / zoom transition. Pure presentation; the host * owns the bookmark store and the camera animation. * * @example * ```tsx * <ViewportBookmarks * activeId={active} * bookmarks={[ * { id: "home", label: "Home base", color: "#5b8def" }, * { id: "incidents", label: "Incidents", detail: "5 open", color: "#ef4444" }, * ]} * onSelect={jumpTo} * /> * ``` * * @public */ export const ViewportBookmarks = ({ ref, ...props }: ViewportBookmarksProps & { ref?: React.Ref<HTMLElement> }) => { const { activeId, bookmarks, className, labels, onSelect, title = "Saved views", ...rest } = props; const resolvedLabels = { ...DEFAULT_LABELS, ...labels }; return ( <section aria-label={resolvedLabels.region} className={cn( "flex w-full flex-col gap-1 rounded-lg border border-border bg-background p-2 text-foreground", className, )} data-viewport-bookmarks ref={ref} {...rest} > <header className="px-2 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> {title} </header> {bookmarks.length === 0 ? ( <p className="px-2 py-3 text-center text-[11px] text-muted-foreground" data-viewport-bookmarks-state="empty" > {resolvedLabels.empty} </p> ) : ( <ul className="space-y-0.5"> {bookmarks.map((bookmark) => ( <li key={bookmark.id}> <Row active={activeId === bookmark.id} bookmark={bookmark} onSelect={onSelect} /> </li> ))} </ul> )} </section> ); }; ViewportBookmarks.displayName = "ViewportBookmarks";

Stories

Explore every variant and state in the interactive Storybook:

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Dependencies

  • @vllnt/ui@^0.3.0

FAQ

What is Viewport Bookmarks?
Viewport Bookmarks is a React component that renders a saved-view list for a canvas — the spatial parallel of pinned tabs. Each row names a stored pan and zoom target, with an optional accent color, detail line, and active state.
How do I add Viewport Bookmarks?
Run `pnpm dlx shadcn@latest add https://ui.vllnt.com/r/viewport-bookmarks.json` to install it into your React project via the shadcn CLI, then pass your bookmarks array to the ViewportBookmarks component.
Can Viewport Bookmarks be read-only?
Yes. When you provide an onSelect handler each row renders as a clickable button; omit it and the rows render as read-only text. It also includes a built-in empty state ("No saved views") and localizable region and empty labels.