Skip to content
Components

Panel

The surface docked beside a live map: one subject — a parcel's report, a design being built — while the map behind it stays fully usable.

The panel

Live, on a stand-in canvas. Scroll the body and the header's rule arrives; close it and it enters again from its own edge.

GdSidePanel wires its own GdPanelHeader and GdPanelFooter — you pass a title, a meta line and a footer slot. The body is yours: here, two GdPanelSections.

Panel, header or drawer

Three surfaces that all put a title over some content, and the differences are contracts rather than looks.

  • Not GdPageHeader — that is a full-bleed band opening a DOCUMENT, carrying the page's one <h1>. A panel floats over a canvas and its title is a subject, not a page.
  • Not GdDrawer — the drawer is a native <dialog> opened with showModal(): focus trapped, everything behind it scrimmed and inert, Escape answered from anywhere. Every one of those is right for a settings sheet and is the opposite of what a panel needs.
  • So: a plain <aside> — complementary to the map, which stays the main content. No trap, no scrim, no inert, nothing stacked above --gd-z-panel.

Non-modal is the whole point: the user reads the report AND pans, clicks the next lot, toggles a layer. A surface that makes the map unreachable while describing a piece of it has misunderstood the job.

The earned hairline

At rest GdPanelHeader has no bottom border. The rule and its scroll shadow arrive only once the body beneath has actually moved.

A header that always carries a rule reads as detached from the panel it belongs to. The line is a claim that there is content above the fold, and at scrollTop: 0 that claim is false. GdPanelFooter's rule is unconditional for the same reason inverted: a footer always has content above it, so its line is a fact rather than a claim.

  • The header watches the scrolling element itself, passed as scroller — every panel that hand-rolled this wrote the same passive listener and then got the reset wrong.
  • A programmatic scrollTop = 0 — a new selection, a tab swap — fires no scroll event, which strands the rule on over a body that is back at the top. Call the exposed sync() after any scroll you set from code; GdSidePanel.scrollToTop() already does.
  • The border fades, the shadow does not: animating a shadow repaints the full width of the body's top edge every frame, for a mark nobody watches arrive.

The parts

Usable without GdSidePanel — a bottom sheet on a phone, a drawer's body, a card that wants the same spine.

Header

42 Jarrah Street

L14 RP80432Paddington
The heading LEVEL is the caller's — a panel beside a page's h1 is an h2, one nested deeper is deeper. The SIZE is not: a panel title is a panel title wherever it lands, or the outline and the type start disagreeing about which is the hierarchy.

Section

Lot area
812
Frontage
15.2m
Creek floodingRear of lot · mapped overlay
The section does not draw its own rule — it draws the rule BETWEEN itself and the section before it. A border on every section puts a line above the first one, a few pixels under the header's hairline, and two rules that close together read as a mistake rather than as structure.

The label is a real heading, not a styled span: a panel with twelve unlabelled blocks is a panel a screen-reader user has to read linearly to navigate.

Footer

stretch is for the panel whose floor is ONE door — a right-aligned button in a 400px bar reads as an afterthought rather than as the panel's entire point.

It pins two ways on purpose: as the last flex child of a panel column it is simply inert and the body scrolls past it; dropped inside a scrolling body it sticks to the bottom edge. The safe-area inset is not decoration — on a phone this bar is the bottom of a sheet resting on the home indicator, and without it the panel's primary action sits under the gesture bar.

Navigate

Esc