Skip to content

Modal ​

Centered dialog over a dim mask. Focus stays inside the panel until it closes.

Mask uses color-mix(in srgb, #000 40%, transparent) at z-index 3990. The panel sits at 4000. A second modal stacks +10 per level. Escape and mask clicks apply only to the topmost dialog; closing it restores focus to the previous panel.

Demos ​

Basic

open is controlled. Dismissals emit update:open with detail: false and close.
Open modal

Unsaved edits will be written to the workspace. You can undo this later.

CancelSave

Width presets

sm 400 / md 520 / lg 720. A number is pixels. At ≤640px the panel uses a 16px side margin and max-height: 85vh.
sm · 400md · 520lg · 720

Current width token: md

Dialog confirm

preset="dialog" renders Cancel and Confirm. Confirm emits confirm and does not close by itself.
Confirm

The primary action emits confirm.

Danger confirm

confirm-variant="danger" paints the dialog primary button with the danger fill. It stays a flat footer button, the same chrome as the approved mock.
Delete

“Booth intro” will be removed permanently.

Form in body

preset="card" is a padded content shell. Put fields in the default slot.
Edit profile
CancelSave

Loading confirm

loading shows a spinner on the dialog primary button and ignores confirm clicks.
Publish

Uploading assets…

Non-closable

closable="false" hides the close button and ignores Escape.
Open locked

Finish this step to continue.

Continue

Mask not closable

mask-closable="false" keeps the mask but ignores clicks on it. Escape and the close button still dismiss when closable.
Open

Clicking the dim layer does nothing.

Nested modal

A second modal is allowed. Its mask is 4000 and its panel is 4010. Only the top dialog receives Escape and mask clicks. Closing it focuses the previous panel.
Open first

This panel stays underneath.

Open second

Escape closes only this layer.

Alpine ​

html
<div x-data="{ open: false }">
  <gk-button type="button" x-on:click="open = true">Open</gk-button>
  <gk-modal
    title="Hello"
    x-bind:open="open"
    x-on:update:open="open = $event.detail"
  >
    <p>Alpine can drive the same element.</p>
  </gk-modal>
</div>

The string "false" is treated as closed, so attribute bindings from Alpine work.

API ​

Props ​

PropTypeDefault
openbooleanfalse
titlestring''
closablebooleantrue
mask-closablebooleantrue
preset'dialog' | 'card' | ''''
widthnumber | 'sm' | 'md' | 'lg''md' (520px)
loadingbooleanfalse
confirm-variant'primary' | 'danger''primary'
confirm-textstring'Confirm'
cancel-textstring'Cancel'

confirm-text and cancel-text label the built-in dialog footer. Slot footer to replace that footer.

Slots ​

NameDescription
defaultBody
headerExtra header content beside title
footerActions. Replaces the dialog preset buttons when present

Events ​

NameDetail
update:openfalse when dismissed
closeDismissed (Escape, mask, close, cancel)
confirmDialog primary action. Does not close automatically

CSS parts ​

mask, panel, header, title, close, body, footer, plus cancel / confirm / spinner on the dialog preset buttons.

Enter and leave use a 180ms opacity and 10px translate. The panel is position: fixed inside the component shadow tree so ::part works. It is not portaled to document.body the way Select and Date Picker panels are.

Stacking ​

LevelMaskPanel
039904000
140004010

Drawers share this blocking stack, so a dialog opened after a drawer paints above it.