window-algebra
@johnhenry/window-algebra is a functional window manager for browser applications.
It is not a drop-in desktop replacement. It is a small set of parts you can put together
into a floating, tiling, tabbed, docking or hybrid window manager.
command → update (pure) → state → derive (pure) → layout tree → compile (pure) → render tree → DOM- State is immutable data.
update(state, command)returns{ state, events, effects }and never touches the DOM, timers, the clock or randomness. A bad command is rejected with an event, never thrown. - Layout is an algebra. Eleven primitives build a JSON tree. Named layouts such as
masterStack,bspand the dockingtreeare ordinary functions that return those primitives. - CSS is the layout engine. Tiled windows get flex weights, grid tracks and anchor relationships instead of pixel rectangles. The browser works out the geometry, and every window is a container-query container.
- The DOM is an effectful backend, not the source of truth. The renderer reconciles a keyed render tree, so a window keeps its element, and its mounted surface, when the layout changes around it.
Zero dependencies. ESM only. The core runs in Node, workers and browsers. Every entry point ships TypeScript declarations.
import { createState, update, derive, compile, presentationContext, toHTML } from "@johnhenry/window-algebra";
let state = createState({ layout: { type: "master-stack", ratio: 0.6 }, config: { gap: 8 } });for (const id of ["editor", "terminal", "browser"]) state = update(state, { type: "window/create", id }).state;
const tree = derive(state); // a JSON layout-algebra treeconst html = toHTML(compile(tree, presentationContext(state))); // flex: 0.6 1 0 …, no pixelsProvenance: a new package, never published under another name.
0.0.0is its first version under any name, not a sign of immaturity. Published on 2026-10-03; the source is at github.com/johnhenry/window-algebra.
What’s here
Section titled “What’s here”Guides
- Getting started: install, import maps for a no-build page, the pure core, and a browser window manager.
- The layout algebra: the eleven primitives, derived layouts and tree transforms.
- State, commands and events: the
{ state, events, effects }protocol and the 58 built-in commands. - Layouts, modifiers and drag-and-drop: layout specs, resizable splits, modifiers, and structural drops.
- Policy at a glance: the window-manager policy borrowed from xmonad, i3/sway, EWMH, ICCCM and Wayland.
- compile and the browser: the render tree, the DOM renderer, input, surfaces, pop-outs, and the React and custom-element bindings.
- Cross-tab sync:
attachSync, state snapshots over aBroadcastChannel, last writer wins. - Touch and pen gestures:
attachInput({ touch }), pinch, swipe and long-press. - Window chrome:
chrome: true, a built-in title bar, buttons and resize grips. - Theming: the
--wa-*custom properties, light, dark and high-contrast defaults. - The command palette:
createPalette,<wa-palette>and the pure command catalog. - Right-to-left layouts:
config.directionandattachDirection. - Examples: six self-verifying Node scripts, seventeen browser demo pages, the browser test suite and the benchmark.
- Limitations and traps: read this before shipping.
- Adding a new layout.
- Design document: the PRD, prior art and open questions.
API reference: every public export, verified against the source and tests: state, commands, events and effects, queries, the layout algebra, layouts and modifiers, drag and drop, compile and CSS, theming, geometry and interaction, the manager, browser adapters, the command palette, cross-tab sync, framework bindings, versioning and migration, TypeScript types and errors.
Source: github.com/johnhenry/window-algebra. MIT licensed.
Family
Section titled “Family”window-algebra is one of the family’s browser-first libraries. None of them depends on another. They fit together at the page level:
- html-modules: declarative HTML modules.
<html-import src="./ui.html" as="ui">turns each<html-export>in an ordinary HTML file into a native custom element (<ui--card>). Those elements are exactly what window-algebra’s surfaces host:htmlSurface(document.createElement("ui--card"))(or alazySurfacethat creates one on first mount) puts an HTML-module component in a window. window-algebra only ever callsmount(target)/unmount(), so it needs no knowledge of how the element was defined. Neither package depends on the other. See Surfaces here and html-modules’ Lazy loading for how a lazy import behaves inside a window. - mport: routes JavaScript imports across CDNs and compiles the result to a
standard import map. A no-build page using window-algebra needs an import-map entry for
each entry point it imports (see Getting started),
and for anything it loads alongside, such as React for the
/reactbinding, which the repository’sdemo/react.htmlcurrently fetches from esm.sh by a hard-coded URL. mport can produce that map with fallback across mirrors, instead of hand-written URLs. There is no dependency in either direction; the browser only sees the resulting import map. See mport’s Import maps, lockfiles and the CLI. - safe-fragment: Web Components that render untrusted HTML through
versioned security profiles. A
<safe-fragment>is an ordinary element, sohtmlSurface(safeFragmentEl)puts sanitized, less-trusted content in a window; window-algebra only callsmount/unmount. Prefer its defaultscope="light"in windows that may pop out: the rendered content moves with the element into the pop-out document (scope="shadow"is not isolation, and its content does not receive the copied page stylesheets). Neither package depends on the other. - Untrusted Desk: the Orrery planet whose desk is window-algebra, with html-modules components and safe-fragment notes.