fix(layout): scroll the palette on overflow instead of stretching the host page - #188
Merged
StephenTangCook merged 3 commits intoAug 15, 2026
Merged
Conversation
The builder shell sizes itself with `h-full`, which resolves against a host that gives its container a definite height and computes to `auto` against one that doesn't. In the `auto` case every internal `overflow-y-auto` goes inert — nothing is bounded, so nothing has anything to scroll — and the shell grows to its tallest child. That child is the palette, whose full variant list runs a couple of thousand pixels, so an embed in ordinary document flow stretched the host page by that much instead of scrolling the rail independently of the preview. Cap the shell at `max-h-[var(--bk-max-height,100svh)]`. It bounds the unbounded case and stays inert whenever the host's own height is the smaller of the two; `--bk-max-height` tunes or removes it (`calc(100svh - 4rem)` to leave room for a page header, `none` to opt out). A scrolling palette also exposed a sticky-header bug: the search header was painted with the rail's own `bg-muted/20`, so it composited over whatever had scrolled beneath it and section headings read straight through the search box. `backdrop-blur` was softening them, not hiding them. Split the header into an opaque `bg-background` base plus the tint — the same two layers the rail itself is made of, so the match stays exact under any theme. Covered by a real-browser regression test against the built stylesheet, since jsdom implements neither layout nor `svh`. Its Chromium launch and stylesheet build are shared with the load-dialog cascade test.
…over Both buttons carried a label that was hidden below `md` and always shown above it, which put two full-width word buttons in the toolbar's secondary cluster. Rest them as bare icons instead and slide the label open on hover or keyboard focus, so the cluster stays compact without the icons becoming a guessing game. The reveal is a `0fr` → `1fr` grid track: a lone flexible column in an intrinsically-sized grid resolves to its item's max-content width, and the flex factor interpolates, which `width: auto` can't. Reduced-motion users get the same end states with no transition. The `aria-label` on each button is unchanged and was always the accessible name, so nothing shifts for assistive tech. Measured in a real browser rather than a play function: the effect is pure CSS, and the Storybook runner's `userEvent` dispatches synthetic pointer events that never set `:hover`.
Contributor
Cloudflare preview✅ Deployed |
Contributor
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
block-kitchen | d8f8cc5 | Commit Preview URL Branch Preview URL |
Aug 15 2026, 06:26 PM |
Same content, roughly 40% fewer lines — the explanations had grown into paragraphs that outweighed the code they sat on.
StephenTangCook
deleted the
claude/block-palette-scroll-overflow-phfe86
branch
August 15, 2026 18:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The block palette now scrolls inside its own rail, independently of the preview pane, instead of growing the builder — and the host page — to fit its full block list. Separately, the toolbar's Clear and View JSON buttons rest as bare icons and slide their label open on hover or keyboard focus.
Why
The palette stretched the page. The builder shell sizes itself with
h-full. That resolves against a host that gives its container a definite height (the demo, Storybook) and computes toautoagainst one that doesn't — a plain<div>in ordinary document flow. In theautocase every internaloverflow-y-autogoes inert: nothing is bounded, so nothing has anything to scroll, and the shell grows to its tallest child. That child is the palette. Measured in an unbounded host at an 800px viewport, the page came out 2607px tall with the rail at 2374px andasideScrolls: false— the reported "extends the page a huge amount." After the fix the same page is 982px with the rail bounded to 749px and scrolling its 2374px of content, and scrolling the rail leaveswindow.scrollYat 0.The cap is
max-h-[var(--bk-max-height,100svh)]on the shell. It bounds the unbounded case and stays inert whenever the host's own height is the smaller of the two, so nothing changes for hosts that already size the builder.--bk-max-heighton any ancestor tunes it (calc(100svh - 4rem)to leave room for a page header) or removes it (noneto keep the old grow-to-content behavior). Documented under a new Sizing section in the README.A scrolling palette exposed a sticky-header bug. The search header was painted with the rail's own
bg-muted/20, so it composited over whatever had scrolled beneath it — section headings read straight through the search box, withbackdrop-blursoftening them rather than hiding them. This already shipped for bounded hosts; it would have looked like a regression introduced here. The header is now an opaquebg-backgroundbase with the tint laid back on top — the same two layers the rail itself is made of, so the match stays exact under any theme rather than only where--mutedsits near--background.The toolbar's secondary cluster. Clear and View JSON each carried a label hidden below
mdand always shown above it, putting two full-width word buttons next to the primary action. They now rest as icons (40px vs 113px) and reveal the label on hover or keyboard focus. The reveal is a0fr→1frgrid track — a lone flexible column in an intrinsically-sized grid resolves to its item's max-content width, and the flex factor interpolates, whichwidth: autocan't. Reduced-motion users get the same end states with no transition, and thearia-labelon each button is unchanged and was always the accessible name, so nothing shifts for assistive tech.Test plan
pnpm typecheckpnpm lintpnpm test— 498 passing across both projects (unit+storybook)Two new real-browser test files measure the built,
@scope-wrapped stylesheet, since jsdom implements neither layout norsvh, and Storybook renders against plain unscoped Tailwind:test/builder-shell-scroll.test.tsx— the rail bounds and scrolls in an unbounded host; the cap stays inert under a host-supplied height;--bk-max-heightboth tightens and disables it; the sticky header paints at full alpha.test/toolbar-expanding-labels.test.tsx— driven with a real mouse move and a real Tab press, becauseuserEvent's synthetic pointer events never set:hover. Covers the collapsed resting width, hover expansion (neighbour unaffected), keyboard-focus expansion, and the unchanged accessible names.Each new assertion was confirmed to fail against the pre-fix code (
expected 'none' to be '800px',expected 51 to be 255,expected 113.14 to be less than 48).The Chromium launch and stylesheet build these share with
load-dialog-cascade.test.tsxmoved totest/built-stylesheet.ts; that test is otherwise untouched and still passes.Manual verification: screenshotted both host shapes in Chromium at 1280×800 — unbounded and bounded (definite-height ancestor, the demo's shape) — plus the sticky header parked mid-scroll before and after, and the toolbar at rest and hovered. Also confirmed the three new utilities (
max-h-[var(--bk-max-height,100svh)],grid-cols-[0fr],group-hover:grid-cols-[1fr]) survivescripts/scope-utilities.mjscorrectly anchored.Notes for reviewer
The cap is a default, not a hard ceiling, but it is a behavior change for any host currently relying on the builder growing past the viewport in document flow —
--bk-max-height: nonerestores that exactly.Generated by Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.