Skip to content

WindowPane

Anything at all, drawn the way one of four operating systems draws a window. The title bar drags, the corners resize and the three buttons are real buttons, so a screenshot, a demo or a piece of a landing page can be shown as the thing it will be rather than as a picture of it.

tsx
import { WindowPane } from 'neba';

<WindowPane os="macos" title="Notes" width={520} height={320}>
  <Editor />
</WindowPane>;

It is not a real window: there is no desktop, no z-order and nothing outside the page. What it draws is the frame, and what it holds is yours.

Props

PropTypeDefaultDescription
os'macos' | 'macosx' | 'windows11' | 'windows10' | 'windows8' | 'windows7' | 'windowsxp' | 'linux''macos'Whose window this is a picture of. Decides where the controls sit and how they are drawn, how tall the title bar is and what colour, and how thick the frame is and how its corners are cut. A version is its own entry wherever the title bar is what changed: XP painted it Luna blue and framed the window in it, 7 made it glass, 8 threw both away, 10 ruled it off from the body, 11 rounded the corners, and macosx is Aqua
titleReactNodeThe window’s name, in the title bar. Also what names the window itself
iconReactNodeA glyph beside the title: the app’s mark
actionsReactNodeAnything else the title bar carries, set beside the controls
controlsboolean | ('minimize' | 'maximize' | 'close')[]trueWhich of the three buttons the title bar has. The order is the system’s rather than the array’s: macOS puts close first and Windows puts it last
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The scale of the chrome: the title bar’s height, its buttons and its type. It does not touch the content
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'The colour family the focus rings and an accent title bar take
accentbooleanfalseDyes the title bar with the colour family, the way Windows offers to
transparencynumber0From 0 to 1. It applies to the title bar, the body’s own fill and the border: never to the content. Anything above 0 also turns the acrylic on
activebooleanWhether this is the window in front. Left out, the window works it out for itself: it is in front until another WindowPane on the page is pressed or takes the focus, and a press on the page *around* the windows changes nothing. A window behind keeps its shape and loses its emphasis(grey traffic lights, no accent, one step less shadow) never its opacity
elevationshared0 | 1 | 2 | 32The shadow around the window. 0 means no shadow at all
positionshared'static' | 'absolute' | 'fixed''static'How the window is laid out. static leaves it in the flow (as a relatively positioned box, so offset moves nothing around it), absolute pins it inside the nearest positioned ancestor, fixed pins it to the viewport
draggablebooleanfalseLets the title bar be dragged
resizablebooleanfalseLets the edges and corners be dragged
widthnumber | stringThe window’s width
heightnumber | stringAnd its height. Left out, the window is as tall as what is in it
minWidthnumber180How small it may be dragged, in pixels
minHeightnumberThe same downward. Defaults to the title bar’s own height
offset{ x: number; y: number }How far it has been dragged from where the layout put it
defaultOffset{ x: number; y: number }{ x: 0, y: 0 }Where an uncontrolled window starts
onOffsetChange(offset: { x: number; y: number }) => voidCalled while the window is dragged
onResize(size: { width: number; height: number }) => voidFires with the window’s size, in pixels, while an edge is dragged
openbooleanWhether the window is on screen at all. Closing it renders nothing
defaultOpenbooleantrueWhere an uncontrolled window starts
onOpenChange(open: boolean) => voidCalled when the close button is pressed
minimizedbooleanWhether the window is rolled up to its title bar. A page has no dock to send it to, so this is what minimizing means
defaultMinimizedbooleanfalseWhere an uncontrolled window starts
onMinimizedChange(minimized: boolean) => voidCalled when the minimize button is pressed
maximizedbooleanWhether the window fills whatever is holding it. Its corners go square while it does
defaultMaximizedbooleanfalseWhere an uncontrolled window starts
onMaximizedChange(maximized: boolean) => voidCalled when the maximize button is pressed, and on a double click of the title bar
scrollbooleantrueWhether content taller than the window scrolls
localestringWhich language the title bar’s buttons name themselves in: a BCP 47 tag
minimizeLabelstringThe minimize button’s own name
maximizeLabelstringThe maximize button’s own name
restoreLabelstringAnd what that button is called while the window is maximized
closeLabelstringThe close button’s own name
resizeLabelstringThe name of the corner a keyboard resizes with
renderuseRender.RenderPropRenders something other than a div (render={<section />})
childrenReactNodeWhat is in the window

Every other <div> attribute passes through to the root, except title (here it is the window's name and a ReactNode), and onResize, which reports pixels rather than a DOM event. The shared axes (size color elevation position) are defined in prop conventions.

Examples

os

Eight systems, and a version is its own entry wherever the title bar is what changed:

osWhat it draws
macosThree coloured dots on the left, the title centred over the whole window, one flat sheet
macosxAqua: a short striped bar, glossy lights, a bold embossed title, square bottom corners
windows11Three rectangles hard against a rounded top-right corner, bar and body one Mica sheet
windows10The same three over square corners, a shorter bar, and a rule under it
windows8Flat and square, no rule, and a band of colour around the whole window
windows7Aero: a sheet of glass with the content sunk into it, the page blurred through the band as well as the caption, a glowing title and a wider close
windowsxpLuna: the glossy blue caption curve, a band of the same blue down the sides and along the bottom, coloured button plates
linuxA GNOME header bar: taller, with circular buttons and a centred title

The older systems paint their own chrome rather than the page's, so Luna stays blue and Aqua stays grey on a page switched to dark: the same choice Mockup's finishes make, because hardware and system chrome are not the theme.

XP and Aero are also the two that have a band rather than a hairline: the content is sunk into a stretch of the system's own material down both sides and along the bottom, which is most of what makes either of them recognisable. accent dyes the band with the caption, so a window in your own colour is framed in it too.

The buttons carry no other party's marks (a minimize is a line, a maximize is a box, a close is a cross), and none of the chrome writes a word except the title you gave it.

controls

true is all three buttons, false is none, and an array is exactly the ones named. The order is the system's rather than the array's, so ['close', 'minimize'] still puts close last on Windows.

Each one is a controlled/uncontrolled pair: open, minimized and maximized, each with a default* and an on*Change. Closing an uncontrolled window renders nothing; minimizing rolls it up to its title bar, because a page has no dock to send it to; maximizing fills whatever is holding the window, which is the nearest positioned ancestor with position="absolute" and the viewport with fixed. A double click on the title bar maximizes too.

draggable and resizable

draggable moves the window on left and top (never a transform, so no glyph is resampled for the length of the drag), and reports where it went through onOffsetChange. resizable puts a handle on all four edges and all four corners; minWidth and minHeight bound them, and onResize fires with the pixel size as they move.

Both need somewhere to move: give the window position="absolute" and a positioned ancestor, or position="fixed".

Which window is in front

Left out, active looks after itself: a window is in front until another WindowPane on the page is pressed or takes the focus. A press on the page around the windows changes nothing: a paragraph is not a desktop.

Being in front is drawn the way each system draws it: coloured traffic lights against grey ones on macOS, an accent title bar and an accent border on Windows 10, a tinted header bar on GNOME. And, on all four, one step more shadow than the windows behind it. Pass active to drive that yourself, which is what a caller keeping its own z-order wants.

Motion

Maximizing, restoring and rolling up are journeys between two geometries, so the window travels rather than jumps: left, top, width and height are what move, never a transform, so no glyph in the window is resampled on the way. A window that was never given a height is measured and pinned for the length of the roll-up, because auto is not a length a transition can start from.

A rolled-up window keeps its body in the tree, inert and clipped, which is what the roll-up travels over. A closed one fades before it goes rather than stopping existing. Every one of these is instant for a reader who has asked for reduced motion.

accent, transparency, active

accent dyes the title bar with color, the way Windows offers to, and on windows10 it takes the window's border with it, which is what that version does. transparency is how much of the page shows through the chrome, from 0 to 1; it applies to the title bar, the body's own fill and the border, never to the content on them, and anything above 0 also turns the acrylic on so what is behind is blurred rather than merely visible. active={false} pins the window behind whatever else is on the page.

Accessibility

  • The root is a role="group" labelled by its own title, so a screen reader reads the window's name before its contents.
  • The three title bar buttons are real <button>s with names from locale, and the maximize one renames itself to "Restore" while the window is maximized.
  • One resize handle (the bottom-right corner) is reachable from the keyboard and resizes with the arrow keys. The other seven are pointer affordances and are hidden from the accessibility tree; a keyboard reader has the same range through maximize.
  • Dragging is a pointer affordance as well. A window that must be movable without one should be given its offset by the caller.

Released under the MIT License