Resizable, overlays and windows
Patterns for when a static grid is not enough. All three work because a pane watches its own container and re-measures whenever it changes — so the interaction is yours to build, and the pane just follows.
The one principle
Everything on this page rests on a single behaviour: a pane watches its own container and re-measures whenever that container changes size. It does not care what changed it — a drag handle, a dialog opening, a tab becoming visible, a window being dropped into a new position.
So you never write resize handling for a pane. You build the interaction you want and the pane follows. Measured, in each case below.
These are patterns for your page, not LightPane features to switch on. There is no overlay API and no window manager to import. What LightPane guarantees is that a pane behaves correctly inside whichever of these you build — which is the hard part, and the part you would otherwise have to work around.
Reader-resizable panes
Let someone drag a pane wider when they want more columns. One CSS property, no JavaScript:
.resizable {
resize: horizontal; /* or `both` to allow height too */
overflow: auto; /* REQUIRED — `resize` is ignored without it */
min-width: 280px; /* the measured floor; see Part 2 */
max-width: 100%;
}
<div class="resizable" style="width:720px">
<div data-lp-service="ec2" data-lp-account="YOUR_ACCOUNT_HANDLE"></div>
</div>
Dragging the handle narrows the container; the pane collapses columns into the drawer as it goes, and brings them back on the way out. Dragged from 900 px down to 300 px and back, a six-column inventory pane went 6 → 5 → 3 columns and returned to 6. Nothing is lost on the way down; collapsed columns are in each row's drawer.
Two things to get right:
-
overflowmust not bevisible. Browsers ignoreresizeon a visible-overflow element, and they do it silently — the commonest reason this "doesn't work". -
Set
min-width. Without it the handle goes to zero and the reader can break the pane. 280 px is the floor at which every pane kind still renders cleanly.
resize has no touch equivalent, so this is a
pointer-and-keyboard affordance. It should always be an enhancement over
a layout that already works.
Expand a tile into an overlay
The pattern for dense dashboards: small tiles, click one to see it properly. A pane can be moved into an overlay and back — it keeps its data and re-measures for the new width, so there is no second copy and no refetch.
Use the native <dialog>. It brings ESC-to-close, focus
trapping, a backdrop and correct stacking; a hand-rolled div brings none
of them and you end up reimplementing all four, usually badly.
<div class="tile" id="tile-ec2">
<button type="button" class="expand" data-expands="tile-ec2">Expand</button>
<div data-lp-service="ec2" data-lp-account="YOUR_ACCOUNT_HANDLE"></div>
</div>
<dialog id="lp-overlay">
<button type="button" id="lp-overlay-close">Close</button>
<div id="lp-overlay-slot"></div>
</dialog>
(function () {
var dlg = document.getElementById('lp-overlay');
var slot = document.getElementById('lp-overlay-slot');
var home = null; // where the pane came from, so it can go back
document.querySelectorAll('.expand').forEach(function (btn) {
btn.addEventListener('click', function () {
var tile = document.getElementById(btn.dataset.expands);
var pane = tile.querySelector('[data-lp-service],[data-lp-pane]');
home = tile;
slot.appendChild(pane); // MOVE it — do not clone, do not re-create
dlg.showModal(); // the pane re-measures for the new width
});
});
// Covers the Close button, the ESC key and a backdrop click alike.
dlg.addEventListener('close', function () {
var pane = slot.firstElementChild;
if (pane && home) home.appendChild(pane);
home = null;
});
document.getElementById('lp-overlay-close')
.addEventListener('click', function () { dlg.close(); });
dlg.addEventListener('click', function (e) {
if (e.target === dlg) dlg.close(); // click outside the content
});
})();
#lp-overlay {
width: min(1100px, 92vw); /* see Part 2 on why not 100vw */
max-height: 86vh;
overflow: auto;
border: 0;
border-radius: 8px;
padding: 18px;
}
#lp-overlay::backdrop { background: rgba(26, 24, 20, 0.55); }
.tile { position: relative; }
.tile .expand { position: absolute; top: 8px; right: 8px; z-index: 2; }
Why move rather than copy
Putting a second container in the dialog would create a second pane: a second request, a second cache entry, and two copies that can disagree — and the reader would lose whichever rows they had expanded in the tile. Moving the element keeps one pane, one set of data and the reader's state.
Returning it
Listen for the dialog's close event, not just your Close
button. ESC and a backdrop click both close a dialog without touching
your button, and if the pane is not put back the tile is left empty.
The simpler version
If "expand" can mean "take up more of the grid" rather than "float above
it", you do not need any of this — toggle a class that changes the tile's
grid-column span. The pane re-measures exactly the same way,
there is no focus or keyboard handling to get right, and the page keeps
working with JavaScript disabled.
.dashboard > .is-expanded { grid-column: 1 / -1; } /* full width of the grid */
Tabs and drawers
Panes in a hidden tab need no special handling. A pane inside a
display:none parent renders, measures zero width, and lays
out correctly the moment the parent is shown — so you can put every
tab's panes in the DOM at the start and just toggle visibility.
The trade-off is first load, not correctness: panes in hidden tabs still
fetch. For two or three tabs, render everything up front. For a dozen,
add the containers when the tab is first opened and call
window.lpEmbedRun() afterwards — it skips panes that have
already rendered, so only the new ones start.
Movable windows
The most involved pattern: panes as windows on a canvas — dragged, resized, stacked, closed, reopened. LightPane's own Cloud Desktop demo works this way, with several cloud accounts open side by side.
There is no window manager to import. What follows is what that implementation settled on, offered as a starting point if you build one.
Snap to a grid
Free-form dragging produces windows misaligned by three pixels and a board that always looks slightly wrong. Snapping both position and size to a 20 px grid makes every arrangement look deliberate, and makes saved layouts compact.
Enforce minimum sizes in cells, not pixels
The desktop's floor is 12 × 8 cells — 240 × 160 px. The 240 matches the measured floor for inventory panes almost exactly, which is not a coincidence: it is where a pane stops being readable. If your windows can hold compound panes, use 280 px, which is the floor for those.
Open at a useful size
New windows open at 720 × 400 px. 720 is where an inventory pane shows every column it is going to, so a window opens showing everything and the reader resizes only to fit more on screen, never to see more.
Persist the layout, keyed by something stable
Save position, size and z-order so a board survives a reload. Key each window by the account handle, not a display name — a renamed account must not orphan its window, or worse, match a different one.
The pane part is the easy part
Resizing a window resizes the container, and the pane follows with no
involvement from your drag code. Put the pane in
data-lp-fill mode so it
fills the window's height with a single scrolling body rather than
overflowing it.
A window manager is real work: drag and resize on pointer and touch, z-order, focus, keyboard access, persistence, and a sensible answer for phones — where overlapping windows do not work at all, and you need the card grid as a fallback. It is the right pattern for an operator console people live in all day. It is the wrong pattern for a status page.
Choosing between them
| Pattern | Costs you | Reach for it when |
|---|---|---|
| Card grid | 5 lines of CSS | Always. Start here; most dashboards need nothing else. |
| Expand in place | 1 line of CSS, a class toggle | One pane sometimes needs the full width. |
| Resizable container | 4 lines of CSS | Readers have very different screens and know what they want. |
| Overlay | ~25 lines of JS | Dense tiles, and detail should not disturb the layout. |
| Movable windows | Days, and a mobile fallback | An operator console, used daily, arranged to taste. |
Build the simplest one that answers the question being asked. A pane behaves the same in all of them.