Conversation
`DialogForm` rendered `Dialog > Header + Content > Form > [children, ButtonGroup]`, so its actions lived inside the scrolling body and rode the scroll away. A dialog that needed pinned actions could not use `DialogForm` at all. The form now wraps both slots — `Dialog > Header + Form > [Content, Footer > ButtonGroup]` — so the body scrolls while Submit and Cancel stay put. The buttons are still inside the `<form>`, which is why the form is the outer of the two and native submit is unchanged. Both the legacy and the modern branch move. `Dialog` also hands a DIRECT-CHILD `<form>` its flex context, so the same shape composed by hand needs no plumbing. Consumers were restating `display:flex / flexGrow:1 / height:'min 0' / gap:0` verbatim across unrelated features, each rediscovering the `min-height: 0` by debugging a footer pushed off the dialog. A form nested deeper is the consumer's own layout and is left alone. Actions now sit below `Content`'s bottom padding plus `Footer`'s top padding rather than one content gap, so a dialog with actions gains 24px between the last field and the buttons — measured 17px to 41px. That is the same spacing every hand-composed dialog footer already has. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 836c2e4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📦 NPM canary releaseDeployed canary version 0.0.0-canary-50ea57a. |
Contributor
🧪 Storybook is successfully deployed!
|
Contributor
🏋️ Size limit report
Compared against main at 565dc93 — run 35764087638, 2026-09-22T17:58:13Z.To see which modules changed, download the size-limit-statoscope-report artifact from this run and open report.html. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
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.
Closes CUB-4920.
The gap
DialogFormrenderedDialog > Header + Content > Form > [children, ButtonGroup]. The actions lived inside the scrolling body, so they rode the scroll away — and a dialog that needed pinned actions could not useDialogFormat all.Consumers dropped it, hand-composed
Dialog > Form > Content + Footer, and then had to carry the dialog's flex context through the intervening<form>themselves. The same four declarations arrived verbatim in two unrelated Cloud features, each rediscoveringheight: 'min 0'by debugging a footer pushed off the bottom of the dialog.The change
1.
DialogFormpins its actions. The form now wraps both slots —Dialog > Header + Form > [Content, Footer > ButtonGroup]— so the body scrolls while Submit and Cancel stay put. The buttons are still inside the<form>, which is why the form is the outer of the two and native submit is unchanged. Both the legacy and the modern branch move.2.
Dialoghands a direct-child<form>its flex context, so the hand-composed shape needs no plumbing:A form nested deeper is the consumer's own layout and is left alone (
$: '> form'is a raw tag selector — direct child only, nodata-elementonForm).3. The footer draws a top line only while the body scrolls. A permanent line looked wrong on short forms: nothing continues beneath the actions, so the rule is clutter. Once
Contentoverflows, the footer sits over content that carries on underneath it, and the line is what says so.Footercarries acontent-overflowmodifier while the body overflows, and itsborderkeys off it.borderin a footer's own styles still overrides it both ways ('top'for always,falsefor never).Dialogtracks this itself (use-content-overflow.ts), so plain dialogs and hand-composed forms get the line too. It finds its body structurally: theContentthat is a direct child of the dialog or of a direct-child<form>, which is the same pair of shapes the flex pass-through supports.Contentnow rendersdata-id="Content", likeFooteralready does, so a consumerqacannot hide it. The body's own box isn't enough to watch: it stops growing at max height and never changes in a fixed-height dialog (fullscreen,panel). So the hook also observes the body's children for resizes, and the child lists of the dialog, a direct-child form and the body for insertions. It never observes the subtree, because a picker's popover is aDialogtoo and a virtualized list scrolling inside it mutates every frame. Pure-CSS routes were ruled out: scroll-driven animations don't cover our Firefox target,scroll-state()queries are Chrome-only and can't style a sibling, and a background-painted line would be covered by every input scrolling past it.Actions now sit below
Content's bottom padding plusFooter's top padding, rather than a single content gap. Measured: 17px → 41px between the last field and the buttons.That +24px is exactly the chrome any hand-composed dialog footer already has, so this brings
DialogForminto line with the rest of the library rather than giving it a look of its own. But it is a visible change to every dialog in the product and wants a Chromatic review pass.Verification
pnpm testpnpm test:browserpnpm build(emits.d.ts— the broad typecheck)pnpm test:types/test:types:consumerpnpm audit-docs(Dialog, DialogForm)pnpm sizemain, budget 592 kB (fresh builds)pnpm chromatic:checkThe new browser test was falsified, not just run. Pinning and scrolling are both layout, and jsdom lays nothing out — every
getBoundingClientRect()is0 × 0andscrollHeight === clientHeight, so a build with the footer pushed off the bottom would pass a jsdom spec that looked identical.Removing only
height: 'min 0'— the single declaration the ticket says nobody guesses — fails all four cases. Restored, all four pass. The suite covers the legacy branch, the modern branch (ModernDialogFormis separate JSX and was initially untested), and the hand-composed shape with no styles on the form.The footer-line cases were falsified piece by piece. There are four: no line under a body that fits; a line once it scrolls, gone again when it fits; children added to a fixed-height body; one child growing in place inside it. Dropping the footer modifier fails three of them. Dropping the
> formhalf of the body selector fails the DialogForm case. Dropping the child resize observation fails only the grow-in-place case. Ignoring mutations fails only the added-children case. Two things had to be fixed before the last two breaks failed at all. The tests now wait forModal'sdata-enteredplus two frames, because the open transition's own resizes re-measured the body and detected the change by accident. The fixed-height fixture also reserves a stable scrollbar gutter, because a scrollbar that takes room narrows the body when it appears and fires its observer the same way.The frozen
legacy-contract/DialogForm cases (§7.1 #34) pass unchanged — they assert submit, delayed-reset and preserve behaviour, none of which this touches.Note on
flowThe ticket flags that
Form's base styles already setflow: column, so the explicitflow: 'column'inBudgetModalForm.tsxis a restated default. That is Cloud-side and not touched here.Follow-up
Both Cloud call sites can drop their
FORM_STYLESblock and move back ontoDialogFormonce console-ui bumps its pin.🤖 Generated with Claude Code