Meowland docs
Workbench

Component reference

DialogHeader

The title block of a dialog: an optional eyebrow, the title, a description and actions.

Preview

DialogHeader has no story of its own; this one draws it as part of Dialog.

Keyboard and focus return

Uses a real dialog.showModal. Focus moves into the dialog when it opens, Escape or cancel closes it, and focus returns to the open button.

packages/meowtail/stories/foundation.stories.tsxline 474tsx
render: () => (
  <div className="workshop-stack workshop-constrained">
    <Section
      title="One confirmation before finishing"
      description="Use Escape or cancel to return; the dangerous action keeps explicit text."
    />
    <Button data-open-dialog>Open confirmation dialog</Button>
    <Note data-feedback role="status">
      The current work is still in progress.
    </Note>
    <Dialog labelledBy="workshop-dialog-title" describedBy="workshop-dialog-description">
      <DialogPanel
        header={
          <DialogHeader
            title="End the current work?"
            titleId="workshop-dialog-title"
            description="This is a local demo and does not affect real tasks. You can cancel and keep reviewing components."
            descriptionId="workshop-dialog-description"
          />
        }
      >
        <DialogActions>
          <Button variant="quiet" data-close-dialog="cancel" autofocus>
            Cancel
          </Button>
          <Button variant="danger" data-close-dialog="confirm">
            End work
          </Button>
        </DialogActions>
      </DialogPanel>
    </Dialog>
  </div>
)

Installation

bun add meowtail

Usage

Taken from the examples on this page.

usage.tsxtsx
import { DialogHeader } from "meowtail";

<DialogHeader
  title="End the current work?"
  titleId="workshop-dialog-title"
  description="This is a local demo and does not affect real tasks. You can cancel and keep reviewing components."
  descriptionId="workshop-dialog-description"
/>

Examples

Other stories that draw DialogHeader.

Narrow-screen navigation

The title and close button share a fixed region; the body scrolls independently. After choosing a destination, pressing Escape, or closing via the scrim, focus returns to the trigger button.

packages/meowtail/stories/drawer.stories.tsxline 25tsx
render() {
  const open = signal(false);
  return (
    <Stack align="start">
      <Button
        variant="outline"
        onClick={() => {
          open.value = true;
        }}
      >
        <Icon name="menu" />
        Open navigation drawer
      </Button>
      <Drawer
        label="Main navigation"
        open={open}
        onOpenChange={(next) => {
          open.value = next;
        }}
        header={
          <DialogHeader
            title="Meowland"
            actions={
              <IconButton
                label="Close navigation"
                onClick={() => {
                  open.value = false;
                }}
              >
                <Icon name="x" />
              </IconButton>
            }
          />
        }
      >
        <Stack gap="compact">
          <NavItem
            label="Chat"
            icon={<Icon name="message-square" />}
            active
            onClick={() => {
              open.value = false;
            }}
          />
          <NavItem
            label="Memory and knowledge"
            icon={<Icon name="file-text" />}
            onClick={() => {
              open.value = false;
            }}
          />
          <NavItem
            label="Services"
            icon={<Icon name="network" />}
            onClick={() => {
              open.value = false;
            }}
          />
          <NavItem
            label="Messaging"
            icon={<Icon name="message-square" />}
            onClick={() => {
              open.value = false;
            }}
          />
          <NavItem
            label="Reminders"
            icon={<Icon name="list-checks" />}
            badge={
              <Tag kind="count" tone="warning">
                1
              </Tag>
            }
            onClick={() => {
              open.value = false;
            }}
          />
          {Array.from({ length: 8 }, (_, i) => (
            <NavItem
              label={`Authorized sample extension ${i + 1}`}
              icon={<Icon name="file-text" />}
              onClick={() => {
                open.value = false;
              }}
            />
          ))}
          <span>
            <StatusDot state="success" /> Runtime connected
          </span>
          <NavItem
            label="Settings"
            icon={<Icon name="settings" />}
            onClick={() => {
              open.value = false;
            }}
          />
        </Stack>
      </Drawer>
    </Stack>
  );
}

Fixed header and body scrolling

Short forms keep their natural height; long bodies and host-constrained windows share the same page shell. Forms still use native submit and cancel.

packages/meowtail/stories/dialog-layout.stories.tsxline 30tsx
render() {
  return (
    <Specimen
      label="Open a dialog"
      note="Each dialog keeps its title and close action fixed while the body scrolls."
    >
      <Inline gap="compact">
        {(["short", "long", "limited", "workspace"] as const).map((kind) => {
          let dialog: HTMLDialogElement | undefined;
          const name = {
            short: "Short form",
            long: "Long body",
            limited: "Limited height",
            workspace: "Full workspace",
          }[kind];
          return (
            <div>
              <Button
                variant="outline"
                size={kind === "limited" ? "small" : "default"}
                onClick={() => {
                  dialog?.showModal();
                }}
              >
                Open {name}
              </Button>
              <Dialog
                labelledBy={`${kind}-title`}
                style={
                  kind === "limited"
                    ? { maxHeight: "40svh" }
                    : kind === "workspace"
                      ? { height: "100dvh", maxHeight: "none" }
                      : undefined
                }
                ref={(element: HTMLDialogElement) => {
                  dialog = element;
                }}
              >
                <DialogPanel
                  as="form"
                  method="dialog"
                  header={
                    <DialogHeader
                      title={`${name} · Current item and close action`}
                      titleId={`${kind}-title`}
                      actions={
                        <IconButton
                          label={`Close ${name}`}
                          onClick={() => {
                            dialog?.close("cancel");
                          }}
                        >
                          <Icon name="x" />
                        </IconButton>
                      }
                    />
                  }
                >
                  <Stack gap="compact">
                    <Field label={`${name} draft`} htmlFor={`${kind}-draft`}>
                      <Input
                        id={`${kind}-draft`}
                        name="draft"
                        value="Keep the current input"
                        required
                      />
                    </Field>
                    {kind !== "short" ? (
                      Array.from({ length: 24 }, (_, i) => (
                        <p>
                          Body {i + 1}: Reading content, editing the draft, and closing the window
                          stay independent; the title and close entry always belong to the same
                          top region.
                        </p>
                      ))
                    ) : (
                      <p>A short form uses only the height its content needs.</p>
                    )}
                  </Stack>
                  <Button
                    type="submit"
                    value="save"
                    size={kind === "limited" ? "small" : "default"}
                  >
                    Save {name}
                  </Button>
                </DialogPanel>
              </Dialog>
            </div>
          );
        })}
      </Inline>
    </Specimen>
  );
}

API reference

Props

PropTypeDefault
titleRequired
Child–
eyebrow
Child–
description
Child–
actions
Child–
titleId
string–
descriptionId
string–

Also takes className, children and the attributes and events of its root element.

DOM parts

rootidentityeyebrowtitledescriptionactions

Registry

The component is registered as dialog-header in the meowtail component registry.