Skip to content
Gx
GitHub

Dialog

A modal overlay with a trigger.

<dialog.Dialog id="demo-dialog" title="Edit profile" description="Change your display name.">
  <:footer><button.Button>Save</button.Button></:footer>
  <:trigger><button.Button variant={button.Outline}>Open dialog</button.Button></:trigger>
  Dialog body.
</dialog.Dialog>

Installation

Run the command in the app module.

Terminal
gx add dialog

The command also installs button and icons.

The command writes these files.

File Path in the app
Dialog.gx ui/dialog/Dialog.gx
Dialog.fixtures.go ui/dialog/Dialog.fixtures.go

Install button and icons first.

Copy each file to its path in the app. Change each import of a registry package to the path of that package in the app.

ui/dialog/Dialog.gx
package dialog

import "github.com/alternayte/gx/registry/icons"

props {
  // Id is the id of the dialog element.
  // The trigger points at it, so it must be unique on the page.
  Id          string
  // Title is the heading of the dialog. An empty value renders no heading.
  Title       string = ""
  // Description is the text below the title. An empty value renders no description.
  Description string = ""
  // Trigger is the control that opens the dialog.
  Trigger     gx.Node = nil
  // Footer is the content of the dialog footer, for example the main action. A nil value renders
  // no footer.
  Footer      gx.Node = nil
  // Open renders the dialog open. The default is closed.
  Open        bool = false
  // Class adds classes to the dialog element.
  Class       string = ""
  // Children is the content of the dialog body.
  Children    gx.Node = nil
  // Attrs adds HTML attributes to the dialog element.
  Attrs       gx.Attrs = nil
}

<span class="contents">
  if p.Trigger != nil {
    <span data-gx-open={"#" + p.Id} class="contents">{p.Trigger}</span>
  }
  <dialog id={p.Id} open={p.Open} data-gx-dismiss data-gx-trap class={gx.Cx("fixed inset-0 m-auto h-fit w-full max-w-[calc(100%-2rem)] gap-4 rounded-lg border border-border bg-background p-6 text-foreground shadow-lg outline-none open:grid sm:max-w-lg opacity-0 scale-95 transition-[opacity,scale,overlay,display] transition-discrete duration-200 open:opacity-100 open:scale-100 starting:open:opacity-0 starting:open:scale-95 motion-reduce:transition-none backdrop:bg-black/50 backdrop:opacity-0 backdrop:transition-[opacity,overlay,display] backdrop:transition-discrete backdrop:duration-200 open:backdrop:opacity-100 starting:open:backdrop:opacity-0 motion-reduce:backdrop:transition-none", p.Class)} {...p.Attrs}>
    if p.Title != "" || p.Description != "" {
      <div class="flex flex-col gap-2 text-center sm:text-left">
        if p.Title != "" {
          <h2 class="text-lg leading-none font-semibold">{p.Title}</h2>
        }
        if p.Description != "" {
          <p class="text-sm text-muted-foreground">{p.Description}</p>
        }
      </div>
    }
    if p.Children != nil {
      <div class="text-sm">{p.Children}</div>
    }
    if p.Footer != nil {
      <div class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">{p.Footer}</div>
    }
    <button type="button" data-gx-close class="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
      <icons.X class="size-4" />
      <span class="sr-only">Close</span>
    </button>
  </dialog>
</span>
ui/dialog/Dialog.fixtures.go
package dialog

import (
	"github.com/alternayte/gx"
	"github.com/alternayte/gx/registry/button"
)

var DialogFixtures = gx.Fixtures[DialogProps]{
	"Default": {
		Id:          "demo-dialog",
		Title:       "Edit profile",
		Description: "Change your display name.",
		Children:    gx.Text("Dialog body."),
		Footer:      button.Button(button.ButtonProps{Children: gx.Text("Save")}),
		Trigger:     button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("Open dialog")}),
	},
}

The theme must define these tokens: --background, --foreground, --border, --muted, --muted-foreground, --ring.

Usage

<dialog.Dialog id="edit" title="Edit profile" footer={saveButton}>
  <:trigger><button.Button variant={button.Outline}>Open</button.Button></:trigger>
  <p>Dialog body.</p>
</dialog.Dialog>

Examples

Default

<dialog.Dialog id="demo-dialog" title="Edit profile" description="Change your display name.">
  <:footer><button.Button>Save</button.Button></:footer>
  <:trigger><button.Button variant={button.Outline}>Open dialog</button.Button></:trigger>
  Dialog body.
</dialog.Dialog>

API reference

A tag sets a prop by its name with a lower-case first letter: Class is class.

dialog.Dialog

Prop Type Default Description
Id string Required Id is the id of the dialog element. The trigger points at it, so it must be unique on the page.
Title string "" Title is the heading of the dialog. An empty value renders no heading.
Description string "" Description is the text below the title. An empty value renders no description.
Trigger gx.Node nil Trigger is the control that opens the dialog.
Footer gx.Node nil Footer is the content of the dialog footer, for example the main action. A nil value renders no footer.
Open bool false Open renders the dialog open. The default is closed.
Class string "" Class adds classes to the dialog element.
Children gx.Node nil Children is the content of the dialog body.
Attrs gx.Attrs nil Attrs adds HTML attributes to the dialog element.

Do and do not

Do

  • Give every dialog a unique Id.
  • Put the main action in Footer and a close control in the corner.

Don't

  • Do not open a dialog from another dialog.
  • Do not put a long form in a dialog. Use a page.

Keyboard

Key Action
Enter, Space Opens the dialog from the trigger.
Tab Cycles through the controls of the dialog.
Escape Closes the dialog.