GrDialog

  • core
  • overlays
  • API · 14 props
  • ⌨ keys

A dialog for confirmations, forms and focused scenarios.

import { GrDialog } from '@feugene/granularity/components/GrDialog'

Overview

When to take it

  • a window with a header, a body and a footer — the typical layout is already assembled: the heading, the close button, a row of actions;
  • there is a form inside — the body scrolls independently, the footer with the buttons stays in place;
  • the window is opened by markup — v-model in the template rather than a call from code;
  • the focus has to land on a particular element — initialFocus instead of the first control that comes along.

When to take something else

NeedTake
The layout is non-standard: full screen, no header, a frame of your ownGrModal
Ask a yes/no questionGrConfirmDialog
Ask for a single valueGrPromptDialog
Call a window from code, with no markup in the templateGrDialogService
A panel at the edge of the screenGrDrawer
The content is tied to a button rather than to the centre of the screenGrPopover

Playground controls come from the API, not from hand

Stage

The window opens above the page with the props set on the right.

0 changed· state is in the URL

Examples 4 each with a live demo and its code

  • Title, body and actions out of the box

    title draws the heading and names the window, the close button comes with it, and #footer holds the actions. Focus, Esc and the locked page come from GrModal underneath; closed arrives after the animation — the safe moment to reset the form.

    Team9 of 12 seats used
    ALGWLRMO
  • A long text scrolls inside

    scroll-behavior="inside" keeps the title and «Got it» in place and scrolls only the body; size="lg" gives the text a readable width. The default outside scrolls the whole window with the page behind it locked — right for a form read top to bottom.

    Version 2.14 is outTen changes since September
  • A header of your own, paddings per section

    The #header slot replaces the heading and keeps the close button. header-config, body-config and footer-config set the paddings and the rule of one section without touching the others: here the footer has no line and sits closer to the choice above it.

    Invoices · September 2026214 invoices · €96,420.18
  • Without a header, but still named

    :show-header="false" removes the heading and the close button — for a card that explains itself. The window still needs a name, so aria-label takes the place of the missing title: a dialog without one is announced as just «dialog».

    Work faster with the keyboard.

API generated from the package · cannot drift

Props

PropTypedefaultDescription
titlestring | undefinedundefinedThe window title. It also gives the window its accessible name through `aria-labelledby`.
size"sm" | "md" | "lg" | "xl" | "full" | undefinedundefinedThe window width — the `GrModal` scale: from `sm` to `xl`, `full` is the whole screen.
ariaLabelstring | undefinedundefinedThe accessible name of the window when there is no heading at all: `showHeader: false` without `title` and without a `#header` slot would leave the window nameless.
closeOnBackdropboolean | undefinedtrueClose on a click on the backdrop — only a click that started on it.
closeOnEscboolean | undefinedtrueClose on Esc. The top layer of the stack closes.
showHeaderboolean | undefinedtrueShow the header. Without it both the title and the close button go — the window then takes its name from `ariaLabel`.
showCloseButtonboolean | undefinedtrueThe close button in the header. Without it the window is closed by Esc, the backdrop and the footer buttons.
headerConfigGrDialogSectionConfig | undefinedundefinedThe paddings and the line of the header: `paddingX`, `paddingY`, `bordered`.
footerConfigGrDialogSectionConfig | undefinedundefinedThe paddings and the line of the footer. The footer is drawn only when the `#footer` slot is passed.
bodyConfigGrDialogSectionConfig | undefinedundefinedThe paddings of the body. `bordered` does not apply to the body: it has no top line of its own.
closeLabelstring | undefinedundefinedThe a11y label of the close button (i18n).
scrollBehaviorGrModalScrollBehavior | undefined"outside"What scrolls when the content is long. With `inside` the header and the footer are pinned, and only the body moves.
initialFocusHTMLElement | null | undefinednullThe element that gets the focus on opening. By default — the panel of the window. An element **from the dialog itself** must not be passed here: a prop returning upwards what was born inside the subtree closes the render into a loop. The focus on your own content is set from the content — that is how it is done in `GrPromptDialog`.
modelValuerequiredv-modelboolean—Whether the window is open (`v-model`). The window has no state of its own: the parent opens and closes it.

Slots

SlotTypeDescription
defaultanyThe body of the window; the paddings are set by `bodyConfig`.
header{ title?: string | undefined; }The content of the header instead of the visible title; the close button remains. The accessible name of the window then comes from the hidden `title`.
footeranyThe footer with the actions of the window. Without the slot there is no footer.

Events

EventSignatureDescription
update:modelValue(event: "update:modelValue", value: boolean): void—
opened(event: "opened"): void—
closed(event: "closed"): void—

Methods

MethodsSignatureDescription
open() => voidAsk the parent to open the window: emits `update:modelValue` with `true`.
close() => voidAsk the parent to close the window: emits `update:modelValue` with `false`.
toggle() => voidToggle the window through `update:modelValue`.

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrDialogProps, GrDialogEmits, GrDialogHeaderProps } from '@feugene/granularity/components/GrDialog'

Filedist/types/components/GrDialog/GrDialog.vue.d.ts

GrDialogPropsinterface
interface GrDialogProps {
    modelValue: boolean;
    title?: string;
    closeOnBackdrop?: boolean;
    closeOnEsc?: boolean;
    showHeader?: boolean;
    showCloseButton?: boolean;
    size?: GrDialogSize;
    headerConfig?: GrDialogSectionConfig;
    footerConfig?: GrDialogSectionConfig;
    bodyConfig?: GrDialogSectionConfig;
    closeLabel?: string;
    ariaLabel?: string;
    scrollBehavior?: GrModalScrollBehavior;
    initialFocus?: HTMLElement | null;
}
GrDialogEmitsinterface
interface GrDialogEmits {
    (e: 'update:modelValue', value: boolean): void;
    (e: 'opened'): void;
    (e: 'closed'): void;
}

Filedist/types/components/GrDialog/GrDialogHeader.vue.d.ts

GrDialogHeaderPropsinterface
interface GrDialogHeaderProps {
    title?: string;
    showCloseButton?: boolean;
    config?: GrDialogSectionConfig;
    closeLabel?: string;
}

Filedist/types/components/GrDialog/GrDialogFooter.vue.d.ts

GrDialogFooterPropsinterface
interface GrDialogFooterProps {
    config?: GrDialogSectionConfig;
}

Filedist/types/components/GrDialog/dialogShared.d.ts

GrDialogSizetype
type GrDialogSize = GrOverlaySize;
GrDialogSectionConfiginterface
interface GrDialogSectionConfig {
    paddingX?: string;
    paddingY?: string;
    bordered?: boolean;
}

Filedist/types/components/GrDialog/safelist.d.ts

grDialogSafelistconst
const grDialogSafelist: string[];

Filedist/types/components/GrDialog/index.d.ts

GrDialogInstancetype
type GrDialogInstance = ComponentExposed<typeof GrDialogComponent>;

Filedist/types/components/GrDialog/GrDialogCloseButton.vue.d.ts

GrDialogCloseButtonEmitsinterface
interface GrDialogCloseButtonEmits {
    (e: 'click'): void;
}

Accessibility

Keys

EscTab / Shift+Tab

Usage from the library prose

The sections

headerConfig, bodyConfig and footerConfig set the padding and the border of each section (bordered is ignored for the body: it has no top border of its own). The footer is rendered only if the #footer slot is passed.

The header, the footer and the close button are separate components of the same subpath: GrDialogHeader, GrDialogFooter, GrDialogCloseButton. Inside GrDialog they are already assembled; they are taken directly when the layout is built on GrModal while the header and the footer are wanted unchanged.

The #header slot replaces the content of the header as a whole — the close button stays in place in the process. The accessible name of the window then goes down as an sr-only heading: a header of your own is not obliged to contain a DialogTitle, and a window without a name is announced by a screen reader as a nameless “dialog”.

Scrolling long content

scrollBehavior:

  • outside (the default) — the whole overlay scrolls, and the window moves up together with the page;
  • inside — the header and the footer are pinned, only the body moves.
vue
<GrDialog v-model="open" title="Profile settings" scroll-behavior="inside">
  <ProfileForm />
  <template #footer>
    <GrButton @click="submit">Save</GrButton>
  </template>
</GrDialog>

A form of twenty fields is exactly this case: with outside the buttons move off the screen together with the content, and you have to scroll to reach “Save”. Technically the header and the footer go into the layout slots of GrModal (#header/#footer), which lie outside the scrolling body; the body meanwhile enters the tab order, so that a long text without a single focusable element can be scrolled from the keyboard.

Full screen is size="full": the panel takes up the whole viewport, with no margins and no rounding.

The imperative API

open(), close() and toggle() through a ref on the component — as in the other overlays. The dialog is controlled, and the methods emit update:modelValue: the state lives in the parent’s v-model rather than inside (more on that in GrModal.md).

The focus and the lifecycle

initialFocus sets the element that gets the focus on opening; by default that is the panel of the window. An element from the dialog itself must not be passed here — a prop returning upwards what was born inside the subtree closes the render into a loop. The focus on your own content is set from the content; how that is done can be seen in GrPromptDialog.

opened and closed are emitted after the animation. closed is the only safe moment to unmount the content or reset the form: doing that on update:modelValue means cutting the closing animation short.