Dialog

A modal window that interrupts the user to require action or display information.

The dialog component displays content in a focused modal overlay, forcing the user to interact with it before returning to the main page. It follows the WAI-ARIA Dialog Pattern(Opens in a new tab) .

Go/HTML template
 1{{- with partial "ui/dialog" (dict
 2  "trigger" "Open Dialog"
 3  "title" "Edit Profile"
 4  "description" "Make changes to your profile. Click save when you're done."
 5) }}
 6  <form class="flex flex-col gap-4">
 7    <div class="flex flex-col gap-2">
 8      <label for="name" class="text-sm font-medium">Name</label>
 9      <input id="name" value="John Doe" class="border border-border bg-background text-foreground rounded-sm px-3 py-2 text-sm" />
10    </div>
11    <div class="flex flex-col gap-2">
12      <label for="email" class="text-sm font-medium">Email</label>
13      <input id="email" value="john@example.com" class="border border-border bg-background text-foreground rounded-sm px-3 py-2 text-sm" />
14    </div>
15    <div class="ks-dialog-footer">
16      <button type="button" @click="closeDialog()" class="btn variant-ghost size-default">Cancel</button>
17      <button type="button" @click="closeDialog()" class="btn variant-default size-default">Save Changes</button>
18    </div>
19  </form>
20{{- end }}

Reference

ui/dialog

trigger
"trigger" "Open Dialog" — (string, optional)
The label text for the trigger button. Default: "Open Dialog".
title
"title" "Edit Profile" — (string, optional)
The dialog title rendered in a <h2> header. When set, aria-labelledby is automatically linked.
description
"description" "Make changes..." — (string, optional)
Supplementary description below the title. When set, aria-describedby is automatically linked.
open
"open" true — (bool, optional)
Initial open state. Default: false.

Installation

The Dialog component is disabled by default to save resources.
Uncomment the imports in the following files:

/assets/js/main.js
1- //import dialogModule from './modules/dialog.js';
2+ import dialogModule from './modules/dialog.js';
3
4- //dialogModule(Alpine);
5+ dialogModule(Alpine);
/assets/css/main.css
1- /* @import './components/_dialog.css'; */
2+ @import './components/_dialog.css';
Prerequisites
The Get Started: Manual Installation steps must be completed!

JavaScript

Create the Alpine module:

Alpine Js /assets/js/modules/dialog.js
 1export default function dialogModule(Alpine) {
 2  Alpine.data('ksDialog', (config = {}) => ({
 3    open: config.open || false,
 4
 5    openDialog() {
 6      this.open = true;
 7      document.querySelector('#ks-main-hook')?.setAttribute('inert', '');
 8      document.body.style.overflow = 'hidden';
 9    },
10    closeDialog() {
11      this.open = false;
12      document.querySelector('#ks-main-hook')?.removeAttribute('inert');
13      document.body.style.overflow = '';
14    },
15  }));
16}

Import and register it in main.js:

Alpine Js /assets/js/main.js
1import dialogModule from './modules/dialog.js';
2dialogModule(Alpine);

Hugo Partial

Create the partial file:

Go/HTML template layouts/_partials/ui/dialog.html
 1{{- $triggerLabel := .trigger | default "Open Dialog" -}}
 2{{- $title := .title | default "" -}}
 3{{- $description := .description | default "" -}}
 4{{- $open := .open | default false -}}
 5
 6{{- $bodyContent := "" -}}
 7{{- if .content }}
 8  {{- $bodyContent = .content | strings.TrimSpace -}}
 9{{- else }}
10  {{- $bodyContent = inner . | strings.TrimSpace -}}
11{{- end -}}
12
13
14<div x-data="ksDialog({ open: {{ $open }} })" x-id="['ks-dialog-title', 'ks-dialog-description']">
15  <button type="button" @click="openDialog()" class="btn variant-default size-default">
16    {{- $triggerLabel -}}
17  </button>
18
19  <template x-teleport="body">
20    <div
21      x-show="open"
22      x-cloak
23      x-trap.noscroll="open"
24      @keydown.escape.window="open && closeDialog()"
25      class="ks-dialog-container"
26    >
27      <div @click="closeDialog()" class="ks-dialog-overlay" aria-hidden="true"></div>
28
29      <div
30        tabindex="-1"
31        role="dialog"
32        aria-modal="true"
33        {{- with $title }}
34          :aria-labelledby="$id('ks-dialog-title')"
35        {{- end }}
36        class="ks-dialog-content"
37      >
38        <div class="ks-dialog-header">
39          {{- if $title }}
40            <h2 :id="$id('ks-dialog-title')" class="ks-dialog-title">{{ $title }}</h2>
41          {{- end }}
42          {{- if $description }}
43            <p :id="$id('ks-dialog-description')" class="ks-dialog-description">{{ $description }}</p>
44          {{- end }}
45        </div>
46
47        <div class="mt-4">
48          {{- $bodyContent | safeHTML -}}
49        </div>
50
51        <button
52          type="button"
53          @click="closeDialog()"
54          class="ks-dialog-close btn variant-ghost size-icon"
55          aria-label="Close"
56        >
57          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
58        </button>
59      </div>
60    </div>
61  </template>
62</div>

Hugo Shortcode

Go/HTML template layouts/_shortcodes/ui/dialog.html
 1{{- $path := "_partials/ui/dialog.html" -}}
 2{{- $partial := "ui/dialog" }}
 3{{- if templates.Exists $path -}}
 4  {{- partial $partial (dict
 5    "trigger"     (.Get "trigger")
 6    "title"       (.Get "title")
 7    "description" (.Get "description")
 8    "open"        (.Get "open")
 9    "content"     .Inner
10    )
11  -}}
12{{- else -}}
13  {{- $errorMsg := printf "Keystone UI: Missing partial `%s`." $path -}}
14  {{- if hugo.IsProduction -}}
15    {{- errorf $errorMsg -}}
16  {{- else }}
17    {{- warnf $errorMsg -}}
18  {{- end -}}
19{{- end -}}

CSS Styling

Create the CSS component:

Tailwind CSS assets/css/components/_dialog.css
 1@layer components {
 2  .ks-dialog-container {
 3    @apply fixed inset-0 z-90 flex items-center justify-center;
 4  }
 5  .ks-dialog-overlay {
 6    @apply absolute inset-0 bg-black/80 supports-backdrop-filter:backdrop-blur-xs;
 7  }
 8  .ks-dialog-content {
 9    @apply relative z-10;
10    @apply w-full max-w-lg;
11    @apply bg-popover text-popover-foreground;
12    @apply border-border rounded-lg border shadow-lg;
13    @apply p-6;
14  }
15  .ks-dialog-header {
16    @apply flex flex-col gap-1.5 text-center sm:text-left;
17  }
18  .ks-dialog-footer {
19    @apply flex flex-col-reverse gap-2 sm:flex-row sm:justify-end;
20  }
21  .ks-dialog-title {
22    @apply text-lg leading-none font-semibold tracking-tight;
23  }
24  .ks-dialog-description {
25    @apply text-muted-foreground text-sm;
26  }
27  .ks-dialog-close {
28    @apply absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100;
29    @apply focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none;
30    @apply size-4;
31    @apply [&_svg]:size-4;
32  }
33}

Import it in your main Tailwind file:

Tailwind CSS assets/main.css
1@import './components/_dialog.css';

Usage

Using Partials (Templates)

HTML layouts/home.html
1{{- with partial "ui/dialog" (dict
2  "trigger" "Open Dialog"
3  "title" "Dialog Title"
4  "description" "Optional description"
5) }}
6  <p>Your dialog content goes here.</p>
7{{- end }}

Using Shortcodes (Markdown)

Markdown content/example.md
1{{< ui/dialog trigger="Open Dialog" title="Dialog Title" >}}
2
3Your dialog content goes here.
4
5{{< /ui/dialog >}}

Accessibility

The component follows the WAI-ARIA Dialog Pattern(Opens in a new tab) :

  1. Focus Trap: Uses x-trap.noscroll to confine keyboard focus within the dialog when open.
  2. Modal State: role="dialog" and aria-modal="true" inform assistive technology.
  3. Labelled & Described: aria-labelledby links to the title element; aria-describedby links to the description when provided.
  4. Dismissible: Pressing Escape closes the dialog. Clicking the backdrop overlay also closes it.
  5. Scroll Lock: x-trap.noscroll prevents background scrolling while the dialog is open.
  6. Close Button: A dedicated close button with aria-label="Close" is provided.