Repository navigation
feat(ui): close dialogs from a corner button, not an outside click - #295
Merged
Merged
Conversation
A click anywhere in the dimmed area around a dialog dismissed it, taking whatever it held with it: a half-filled sandbox form, a provider mid-connect, a folder being picked. That came from two independent mechanisms, so both go — the overlay's own onClick in the dialog context, and Kobalte's dismissable layer, which closes on outside pointerdown regardless of the overlay and needs onInteractOutside prevented on the content. In exchange every dialog now has a close button. Both Dialog primitives render the Kobalte.CloseButton themselves, as the last child of their content and pinned to its top-right corner at the same coordinates the header button used to land on — so headed dialogs look unchanged, while header-less ones (settings, release notes, the command palettes) get one at all. Keeping it inside Kobalte.Content keeps it inside the focus trap and the modal's pointer-events layer. Headers reserve the corner in padding-right, and the containers take a min-height so a short fit dialog cannot clip the button. That let the hideClose opt-outs go from every call site that had one, and in v1 the button no longer competes with the `action` slot, whose branch used to replace it outright — manage-models had a "Connect provider" button and no way out. Two dialogs keep an exception: the configure gate, which the dialog stack also marks persistent so a button there would be inert, and the two search-first palettes, which reserve the corner rather than let the button sit on the input. Escape still closes everything it did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quickbeard
force-pushed
the
dialog-close-button
branch
from
August 22, 2026 08:57
4e94b55 to
82769aa
Compare
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.
Dialogs closed when you clicked beside them, and none of them offered a close button. Both are fixed here.
Dropping the auto-close
A click anywhere in the dimmed area around a dialog dismissed it instantly, taking whatever it held with it — a half-filled sandbox form, a provider mid-connect, a folder being picked.
It came from two independent mechanisms, and both had to go:
packages/ui/src/context/dialog.tsx— the backdrop's explicitonClick, removed.onInteractOutside={(e) => e.preventDefault()}on all three dialog contents (v1Dialog,DialogV2,ImagePreview).Removing only the first would have looked right and changed nothing.
Escape still closes everything it did — that path is the context's own window listener, untouched, as is the
persistentguard on both.The corner close button
Both
Dialogprimitives now render theKobalte.CloseButtonthemselves, as the last child of their content and pinned to its top-right corner — at the same coordinates the header button used to land on, so dialogs that already had one look unchanged, while header-less ones (settings, release notes, the command palettes) get one at all. Keeping it insideKobalte.Contentkeeps it inside the focus trap and the modal's pointer-events layer; positioning it outside would have made it untabbable andaria-hidden.Headers reserve the corner in
padding-right(v1 52px, v2 42px), and both containers take amin-heightso a shortfitdialog cannot clip the button.That let the
hideCloseopt-outs go from every call site that had one — the sandbox, WSL, server, session-delete and manage-models dialogs were all hiding theirs. In v1 the button also no longer competes with theactionslot, whose branch used to replace it outright: manage-models had a "Connect provider" button and no way out.Two deliberate exceptions:
hideClose. The dialog stack marks itpersistentand refuses every user-driven close while no provider is configured, so a button there would be dead on click.padding-righton the v2 palette's search row,pr-10on the v1 file picker's).Verification
Drove headless Chrome over CDP against the Storybook stories, v1 and v2, headed and header-less:
Screenshots of each checked the placement, and caught two bugs that are fixed here: a short
fitdialog clipping its own close button against the container'soverflow: hidden, and a CSS specificity bug where the base rule outranked the header-less inset override.bun typecheckpasses across all 33 packages;bun testis clean inpackages/ui(14) andpackages/app(690).Fork bookkeeping
Three rows in
DIVERGENCES.md(the context, the primitives, the call sites) and a user-facing entry inCHANGELOG.mdunder Desktop / Web UI.🤖 Generated with Claude Code