bro-canvas
v0.1.0
Published
Draggable, snappable, keyboard-accessible window canvas for React — a headless useWindowManager hook plus themeable Window, Taskbar, CommandPalette, and Minimap components.
Maintainers
Readme
bro-canvas
Draggable, snappable, keyboard-accessible window canvas for React. A headless
useWindowManager() hook owns the window model (z-order, focus, snapping with alignment
guides, minimize/restore, zen mode, tiling, named layouts); themeable components render it:
<Window> (8-handle resize, inline rename, a titleActions slot), <Taskbar> (status-dot
chips, filter, a children slot for app controls), <CommandPalette>, and <Minimap>.
Extracted from bro, a windowed multiplexer for Claude Code, which is its first consumer.
Install
npm install bro-canvasreact and react-dom (18 or 19) are peer dependencies.
Quick start
import { useWindowManager, Window, Taskbar } from 'bro-canvas'
import 'bro-canvas/theme.css'
import 'bro-canvas/style.css'
let uid = 0
export default function App() {
const mgr = useWindowManager({ taskbarH: 34 })
const spawn = () => {
const id = String(++uid)
mgr.addWindow({ id, title: `Window ${id}`, x: 60 + uid * 28, y: 40 + uid * 28, w: 420, h: 280, zIndex: mgr.maxZ + 1 })
mgr.focus(id)
}
return (
<div>
<button onClick={spawn}>New window</button>
{mgr.wins.map(w => (
<Window
key={w.id} id={w.id} title={w.title}
x={w.x} y={w.y} w={w.w} h={w.h} zIndex={w.zIndex}
minimized={w.minimized} active={w.id === mgr.activeId}
accent={w.accent} onSetAccent={mgr.setAccent}
pinned={w.pinned} onTogglePin={mgr.togglePin}
onFocus={mgr.focus} onMove={mgr.move} onResize={mgr.resize}
onClose={mgr.removeWindow} onRename={mgr.rename} onMinimize={mgr.minimize}
onInteractEnd={mgr.endInteract} onFitToView={mgr.fitToView}
>
your content
</Window>
))}
<Taskbar wins={mgr.wins} activeId={mgr.activeId} onItemClick={w => mgr.taskbarClick(w.id)}>
<button onClick={spawn}>+</button>
</Taskbar>
</div>
)
}Theming
Components never hard-code colors — they read a small CSS custom-property contract
(--bg-*, --text-*, --accent-*, --border, --font-*, --win-*). Import
bro-canvas/theme.css for a working dark default, or define the tokens yourself (per
:root or [data-theme=...]) to re-skin everything. bro-canvas/style.css is the
compiled component CSS and is always required.
API surface
useWindowManager(options)— headless state + systems:wins,activeId,guides,patch/update/addWindow/removeWindow,focus/focusRelative/focusNth,minimize/restore/taskbarClick/minimizeOthers/restoreAll,move/resize/endInteract(snapping + guide lines),tileWindows/snapHalf/arrange,toggleZen,fitToView/clampOnscreen, named layouts (saveLayout/loadLayout/deleteLayout/cycleLayout).<Window>— chrome: drag/resize (8 handles, touch-friendly), inline rename, status badge, accent picker, pin, minimize, close, and atitleActionsslot (<TitleButton>) for app buttons.<Taskbar>— window chips (status dot, accent stripe, pinned-first,aria-current), a filter input pastfilterThreshold,itemTag/itemFilterTextrender seams, and a right-aligned children slot for app controls.<CommandPalette commands onRun onClose>— fuzzy-filtered, listbox-accessible.<Minimap wins activeId onJump onClose>— scaled canvas overview.- Helpers:
bro-canvas/layout(pure geometry: snapping, tiling, grid),bro-canvas/fuzzy,bro-canvas/announce(screen-reader announcement strings),bro-canvas/useFocusTrap,bro-canvas/theme(derive a full token palette from two colors).
A runnable demo lives in examples/demo.
License
MIT
