browserbook
v0.1.0
Published
A configurable browser canvas for previewing real application pages.
Maintainers
Readme
Browserbook
A configurable browser canvas for real application pages. Run your application, start Browserbook, and compare routes at multiple viewport sizes in your existing browser.
Usage
Install Browserbook in the project you want to preview:
pnpm add -D browserbook@^0.1.0Create a configuration file in that project and start its application separately. Run
pnpm exec browserbook from the project directory, then open
Browserbook. Browserbook runs its own server and does not start or build
your application. Node 24.11 or newer is required.
To work on Browserbook itself, run these commands from this repository's root:
pnpm install --frozen-lockfile
pnpm run buildThe repository uses Node 24.14.0 and pnpm 11.18.0. Its direct dependencies are pinned to stable
releases. jsdom stays on 29.1.1 because version 30 requires Node 24.15 or newer, and
@types/node stays on the latest Node 24 line to match the runtime.
Developing the viewer
Use browserbook --dev from a Browserbook source checkout to serve its viewer source through Vite,
with React Fast Refresh and CSS hot updates on the configured Browserbook port. Configuration
updates use the same live event stream. This mode requires development dependencies and a built
CLI/bridge (pnpm run build from the repository root); the published package serves the compiled
viewer.
For the included HTML example, start pnpm run example, then run pnpm run dev in another
terminal from the repository root. The dev script builds the CLI, bridge, and compiled viewer,
then starts the viewer with HMR.
Viewer component and CSS edits update without rebuilding or manually refreshing. Restart the
command after editing the Node server/CLI. Plain browserbook continues to serve the compiled
viewer for use without the Vite development toolchain.
Configuration
The CLI reads browserbook.config.ts or browserbook.config.mjs in its working directory.
Use browserbook --config path/to/config.ts to select another file. Node 24.11 or newer is required.
import { defineConfig } from "browserbook";
export default defineConfig({
title: "My previews",
target: "http://localhost:3000",
port: 4400,
viewports: [
{ id: "desktop", label: "Desktop", width: 1280, height: 800 },
{ id: "mobile", label: "Mobile", width: 390, height: 844 },
],
pages: [
{ id: "home", label: "Homepage", path: "/" },
{
id: "login",
label: "Login",
path: "/preview/login",
scenarios: [
{ id: "default", label: "Default", query: { scenario: "default" } },
{ id: "error", label: "Error", query: { scenario: "error" } },
],
},
],
appearances: [
{ id: "light", label: "Light", query: { theme: "light" } },
{ id: "dark", label: "Dark", query: { theme: "dark" } },
],
canvas: { initialZoom: 35, spacing: 192, frameSizing: "fixed" },
});Pages and viewports require unique stable IDs, labels, and valid paths/dimensions. Scenarios may
override the page path. Paths resolve against target; existing query parameters are retained,
then appearance parameters are applied, then scenario parameters. Query values are strings.
Choices default to the first entry. Appearances, scenarios, and canvas settings are optional.
The default canvas uses 192px spacing and fixed-height frames. Single-page views automatically
wrap viewports in configured order and fit the complete arrangement into the visible canvas, up to
100% zoom. Resizing the canvas or changing frame heights updates the fit. Manual zoom or panning
pauses automatic fitting; the zoom and row arrangement are preserved when switching pages, with
the new page centered. Zoom to fit resumes automatic fitting. Focus mode restores the previous
page camera on exit. Collection rows retain the configured initial zoom (35% by default).
Group views center horizontally when the complete board fits, and keep a 24px left inset when it
overflows. Group automatic fitting and Zoom to fit stop at 25% for readability; manual zoom can
still go lower. Single-page fitting can reach 2%, with panning when frames still do not fit.
Canvas details fade as previews get smaller:
| Below | Hidden until hover or keyboard focus | | ----- | -------------------------------------------------------- | | 50% | Connection status text | | 35% | Each collection row's scenario, reset, and open controls | | 10% | Page names |
Viewport labels and widths stay visible, in the order Desktop · 1280px.
Connection status text hides below 240px of displayed frame width.
A small indicator remains for bridge warnings. Labels and controls fade within their existing rows
above the previews, retaining their space to avoid layout shifts. Fixed headers retain their details.
At 15% zoom or higher, each connected frame's … button overlays the right side of its
viewport label on hover or keyboard focus, reserving no label width. It stays visible while the menu
is open, and touch-capable devices always show them. Below 15%, the buttons stay hidden and cannot
receive focus; any open viewport menu closes. The fades take 200ms and respect reduced-motion preferences.
Each connected viewport's … menu offers Configured height and Full height.
The menu is hidden while its bridge is connecting, unavailable, or disconnected. Unavailable bridges
fall back silently to the configured height, without an unavailable or fixed-height label.
Choose Full height to expand that preview independently, or Configured height to restore
its configured height without reloading the page. A checkmark identifies the current mode.
Set canvas.frameSizing to "content" to start expanded instead. Full-height sizing requires the bridge.
Grouping pages
The same pages array accepts pages and nested groups. Existing flat configurations need no changes:
pages: [
{ id: "home", label: "Homepage", path: "/" },
{
id: "account",
label: "Account",
children: [
{
id: "authentication",
label: "Authentication",
collapsed: true,
children: [
{ id: "login", label: "Login", path: "/login" },
{ id: "signup", label: "Signup", path: "/signup" },
],
},
{ id: "settings", label: "Settings", path: "/settings" },
],
},
],Groups require an id, label, and nonempty children array. IDs must be unique across all pages
and groups. Groups cannot have a path or scenarios; those belong to pages. Groups start expanded
unless collapsed: true. A group's folder icon expands or collapses its sidebar branch; its label selects
all descendant pages without toggling the branch. Both controls support Enter and Space.
The selected item's ancestors open on navigation and when loading a deep link. Search matches page
and group labels/IDs, reveals matching ancestors, and restores expansion choices when cleared.
Matching a group shows its whole subtree in the sidebar. Search never limits the selected group's
canvas contents.
Viewing screens
The header's Dark mode toggle changes Browserbook's chrome independently of the preview appearance. It starts with your system appearance and remembers explicit choices in browser storage. Frames use a white fallback background so pages with transparent backgrounds stay readable in a dark viewer. Backgrounds painted by the target page still take precedence.
Browserbook opens on the first page, displaying it across all configured viewport sizes. Select a page in the sidebar to show that page, a group to show its descendant pages, or All pages at the top to show the complete catalog. Collections follow tree order, skipping group headings. The selected item stays highlighted as you scroll its canvas. Use Hide sidebar in the sidebar header and Show sidebar in the top-left corner to toggle it without losing its search or expanded groups. The canvas viewport and scrollbars are restricted to the area beside the sidebar. Hiding it expands the viewport, with an offset that preserves canvas and full-height preview positions without reflowing or refitting them. Configured-height focus mode keeps the frame centered and fitted within the visible area when toggling the sidebar. Navigation and Zoom to fit use the unobscured area at the time of the action. On narrow windows, open the sidebar with Screens. Selecting a page keeps your zoom and centers its first configured viewport in the canvas. Selecting a group or All pages keeps your zoom and aligns its first page to the top-left. Zoom controls float at the bottom right. Zoom to fit fits the currently visible page's viewports into the canvas.
Outside focus mode, scrolling over a preview moves the canvas. Click a preview or its caption to enter focus mode, with or without the optional bridge. Keyboard users can activate these buttons with Enter or Space. The initial click enters focus mode without clicking through to the embedded page. Only a configured-height focused iframe accepts pointer and keyboard interaction; exiting focus restores the guard without reloading the preview. Previews use the normal arrow cursor and show a thin blue outline with a 3px gap on hover or keyboard focus. There is no persistent active-frame state or badge. Press Escape to exit focus mode. When focus is inside the embedded page, this shortcut requires the optional bridge; without it, use Exit focus. Escape already handled by the embedded page's controls is left to them. The canvas keeps its position when an embedded page focuses an input, moves its caret, or scrolls content into view. This works for cross-origin pages without a bridge and does not alter the page's focus methods. Previews sit in a clipped layer that moves with the canvas controls; a separate scroll area supplies the scrollbars without containing the iframes. Configured-height focused frames scroll independently on both axes, including at their edges. To pan the canvas, scroll or drag a finger over the background or a non-focused preview, or use its scrollbars. With keyboard focus on the canvas, arrow keys pan in the overview; Page Up/Down and Home/End pan in canvas and full-height focus modes. Hold Space + left-drag, or middle-mouse drag, to pan the canvas. Space temporarily shows a grab cursor and covers previews so they cannot capture the drag. Start with keyboard focus on the canvas (press Escape to exit a focused bridged preview); Space in inputs, buttons, and embedded pages keeps its normal behavior. Middle-mouse dragging starts on the background or a non-focused preview. Releasing the mouse ends the drag. Focused mode reserves arrow keys for screen navigation. Tabbing to an offscreen viewer control brings that control into view without moving focus into a page.
Pinch and Ctrl/Cmd-wheel gestures zoom the canvas; over the sidebar, header, or floating controls, they are suppressed. A transparent layer keeps non-focused preview gestures on the canvas. During a canvas pinch, connected focused frames are temporarily covered until the gesture goes idle. The optional bridge forwards zoom gestures from inside interactive frames. For an unbridged focused frame, canvas touch and mouse dragging, wheel panning, and pinch/Ctrl/Cmd-wheel zoom are disabled, including on the background. Zoom is capped at Zoom to fit for all configured-height focused frames. Zoom out and back up to Fit using the controls; presets and custom values above Fit are clamped. The limit updates when the available space or selected viewport changes. The embedded page receives its own native interactions; Browserbook cannot intercept browser zoom gestures inside an unbridged iframe.
When a page is selected, its breadcrumbs, scenario picker, reset, and open controls stay in a fixed
header above the canvas. Group and All pages views have a fixed header with the selected
collection's breadcrumbs, page count, and reset control. Set the optional top-level title to
replace All pages in that header. The sidebar still labels the collection All pages. Groups
support two layouts:
rows(the group default): the original layout, with all configured viewports and page controls.grid: only the first configured viewport and page name. Click a tile or activate it with the keyboard to open the page view with all viewports. Grid previews are not interactive.
Set layout on each group. Nested groups use their own setting; they do not inherit the parent layout.
All pages defaults to grid, with sections labeled by each page's full parent path, preserving
grouping at any depth. Click a section heading to open that group. Set the top-level
allPagesLayout to rows to use the original layout.
export default defineConfig({
target: "http://localhost:3000",
allPagesLayout: "grid",
viewports: [
{ id: "desktop", label: "Desktop", width: 1280, height: 800 },
{ id: "mobile", label: "Mobile", width: 390, height: 600 },
],
pages: [
{
id: "account",
label: "Account",
layout: "grid",
children: [{ id: "profile", label: "Profile", path: "/profile" }],
},
],
});The grid fits compact previews into multiple columns on desktop, independently of page zoom. Narrow canvases use one column; zooming does not reflow the grid. Zoom to fit fits the whole grid. Pages outside the selection are unmounted. Nearby and active pages load lazily and stay mounted once visited. Switching between grid thumbnails and interactive page/row views loads fresh documents; temporary form/UI state is not shared. Browserbook remembers per-page scenario choices and the current appearance for the viewer session. Each viewport loads its own copy of the page; Browserbook does not deduplicate requests or cancel backend work that has already started.
Pages in the rows layout have individual reset controls. Reset group previews resets the selected group's pages; Reset all previews is available when All pages is selected. Preview-reported navigation scrolls to a destination within the current collection, or selects the destination page alone when it falls outside that collection.
Viewer links use ?view=page:login, ?view=group:account&page=login, or ?view=all&page=login.
The page parameter in collection links records the visible page without changing the selection.
Reloads, shared links, and browser Back/Forward restore both selection and page anchor. Explicit
navigation adds history; scrolling updates the current entry. Existing mode/page links continue
to work and are normalized to this format. Missing or invalid selections fall back to the first
page; invalid collection anchors fall back to that collection's first page. Scenario, appearance,
zoom, and search are not saved in the URL or persistent storage.
Focused mode
Click a preview or its caption to center that one screen beside the sidebar. The header always shows the page's full group breadcrumb path, separated from the viewport details, and keeps its page/scenario controls. Up/down page controls and a compact page counter form a slim vertical control at the bottom left when multiple pages are available. Left/right viewport controls sit at the bottom center, with zoom controls at the bottom right. On narrow windows, viewport navigation and zoom share one compact control at the bottom center. These controls also support keyboard navigation:
| Key | Action | | ------------ | ------------------------------------------------------------------------------------- | | Left / Right | Previous / next viewport, in configuration order | | Up / Down | Previous / next page in the selected collection, or sibling page when opened directly | | Escape | Exit focus, unless an open control handles it |
Navigation wraps from the last item to the first and from the first to the last on both axes. The sidebar highlights the focused page, reveals its parent groups, and scrolls it into view as you navigate. The selected collection remains the scope for up/down navigation. Opening a page directly uses pages with the same immediate parent; top-level pages use their top-level siblings. The viewport stays the same when changing pages. Vertical controls appear only when multiple pages are available; horizontal controls are disabled when only one viewport is configured. Selecting a page in the sidebar keeps focus active and retains the viewport. Selecting a group or All pages exits focus, restores the canvas zoom, and brings that collection's first page into view. Click a preview within a collection to browse that collection in focus. Search only filters the sidebar. Focused mode and the viewport choice last for the viewer session. Page changes within a collection update its URL anchor; sibling navigation from a directly opened page replaces the selected page in the URL. Neither adds history entries.
Focus mode starts at Configured height. The frame remains interactive, centered, and fully inside the viewing area. Zoom is capped at Zoom to fit, even with a bridge, and canvas panning is disabled. Zooming out is still available; resizing the viewer clamps the zoom to the new fit.
With a connected bridge, the header's … menu offers Configured height / Full height. Full height expands the frame to its measured content height and makes the iframe non-interactive. Scroll or drag over the preview or background to pan the canvas; pinch and the zoom controls support 2–400% zoom. Space + left-drag and middle-mouse drag also pan. Screens that fit remain centered; overflowing screens expose canvas scrollbars. The bridge measures height, while the viewer owns all pan and zoom gestures. Zoom to fit uses the configured device dimensions.
Switching back to configured height restores iframe interaction, clamps zoom to Fit, and recenters the frame. The height menu is hidden without a bridge. Focused height preferences remain separate from canvas preferences. Exit focus restores the canvas zoom and brings the last focused screen into view. Changing focus preserves mounted preview documents; selection and reset rules still apply.
Keyboard navigation and zoom gestures inside a preview require the current application bridge. Inputs, menus, editors, and application-handled shortcuts retain their keyboard behavior. For ordinary URLs, use the viewer's navigation buttons or focus the viewing area before using its keys.
Live configuration
The selected config file is watched. Saving it updates the canvas, re-evaluating its imports.
After changing an imported helper, save the config file to apply it. Invalid edits keep the last
valid configuration and show an error until corrected. Port changes require a restart.
Labels, ordering, and spacing preserve unchanged frames. URL changes reload affected frames.
After live reordering, browsers without CSS reading-flow support retain the previous Tab and
screen-reader order for mounted previews until reload. Sidebar and focused arrow navigation follow
the configured order.
Removed choices fall back to the first available choice. Valid group selections update their contents;
removed selections fall back to the first page and removed collection anchors to the first page
remaining in that collection.
Application bridge
Ordinary URLs need no integration. A small optional script enables content height measurement, pinch/Ctrl/Cmd-wheel zoom inside frames, and application-reported navigation.
import { connectBrowserbook } from "browserbook/bridge";
const preview = connectBrowserbook({
allowedOrigins: ["http://localhost:4400"],
});
// In your app's preview navigation handler:
if (!preview.navigate("/preview/login?theme=light&scenario=default")) {
window.location.assign("/preview/login?theme=light&scenario=default");
}
// When the preview environment unmounts:
preview.disconnect();Only install the bridge in development pages intended for previewing. Allow the viewer's exact origin, including its port. Importing during SSR is safe; connecting outside an iframe is a no-op. Navigation URLs must match a configured page/scenario URL to select its row. The bridge reports navigation without changing the iframe's assigned page. Your app supplies routing interception.
The target app implements themes, fixtures, mock submissions, and state isolation. Browserbook does not intercept network requests or make live actions safe. Reloading a frame resets document state; it does not clear cookies, persistent storage, or backend data.
Content sizing uses each configured height as a minimum. Without a bridge, frames retain their configured height and scroll normally in focus mode. A missing bridge does not prove a load failure. If a page cannot be embedded (for example due to its CSP), use Open standalone. Browserbook does not bypass embedding policies or provide device emulation, independent sessions, or cross-browser testing.
Plain HTML example
From the repository root, build and start the example target:
pnpm run build
pnpm run exampleIn another terminal, also from the repository root:
node bin/browserbook.mjs --config examples/html/browserbook.config.mjsOpen the example canvas. The connected row auto-sizes when adding/removing content. The plain row stays at a fixed height. Local notes survive while their page stays mounted in a collection; selecting a view that excludes the page discards its local notes.
Tech Interview Handbook demo
Preview the live Tech Interview Handbook without running a target application. From the repository root:
pnpm run example:tech-interview-handbookOpen the demo canvas. The command builds the CLI and starts the viewer with HMR. The demo configuration includes five representative layouts: the homepage, a documentation article with navigation and a table of contents, the blog listing, an individual blog post, and the Grind 75 study planner. Each page has desktop (1440px), tablet (768px), and mobile (390px) previews. Select a layout in the sidebar or All pages to compare the complete set.
This demo requires internet access and embeds the public site directly. It uses fixed-height previews because the site does not include Browserbook's optional bridge. Click a preview to enter focus mode and interact; full-height sizing, and bridge shortcuts are unavailable. If the site's embedding policy changes, use Open standalone.
shadcn/ui demo
Preview the live shadcn/ui website without running a target application. From the repository root:
pnpm run example:shadcn-uiOpen the demo canvas. The command builds the CLI and starts the viewer with HMR. The demo configuration includes five representative layouts: the homepage, a documentation article, the Button component reference, the blocks gallery, and the area charts gallery. Each has desktop (1440px), tablet (768px), and mobile (390px) previews. Select a layout in the sidebar or All pages to compare the complete set.
This demo requires internet access and uses fixed-height previews without the optional bridge. Full-height sizing is unavailable. Click a preview to enter focus mode and interact. In focus mode, use the zoom controls; canvas pan and zoom gestures are disabled. If the site's embedding policy changes, use Open standalone.
Vercel Commerce demo
Preview the live Vercel Commerce storefront without running a target application. From the repository root:
pnpm run example:vercel-commerceOpen the demo canvas. The command builds the CLI and starts the viewer with HMR. The demo configuration includes three representative layouts: the storefront homepage, the product listing with categories and sorting, and a product details page with an image gallery and color/size selection. Each has desktop (1440px), tablet (768px), and mobile (390px) previews. Select a layout in the sidebar or All pages to compare the complete set.
This demo requires internet access and uses fixed-height previews without the optional bridge. Full-height sizing is unavailable. Click a preview to enter focus mode and interact. In focus mode, use the zoom controls; canvas pan and zoom gestures are disabled. If the site's embedding policy changes, use Open standalone.
Package contents
The artifact contains the CLI, config helper/types, bridge, and compiled viewer assets. React, Base UI, icons, and styles are bundled into the viewer; target applications need no frontend peers. Jiti and Chokidar are runtime dependencies for config loading and watching. Vite+ is build tooling only. The viewer includes its own styles and does not depend on the target application's UI components or theme files.
