GrDialog
A dialog for confirmations, forms and focused scenarios.
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-modelin the template rather than a call from code; - the focus has to land on a particular element —
initialFocusinstead of the first control that comes along.
When to take something else
| Need | Take |
|---|---|
| The layout is non-standard: full screen, no header, a frame of your own | GrModal |
| Ask a yes/no question | GrConfirmDialog |
| Ask for a single value | GrPromptDialog |
| Call a window from code, with no markup in the template | GrDialogService |
| A panel at the edge of the screen | GrDrawer |
| The content is tied to a button rather than to the centre of the screen | GrPopover |
Playground controls come from the API, not from hand
The window opens above the page with the props set on the right.
0 changed· state is in the URL
<GrDialog title="Rename the report" v-model="value">The report keeps its link and its schedule; only the name changes.</GrDialog>Examples 4 each with a live demo and its code
Title, body and actions out of the box
titledraws the heading and names the window, the close button comes with it, and#footerholds the actions. Focus, Esc and the locked page come fromGrModalunderneath;closedarrives after the animation — the safe moment to reset the form.Team9 of 12 seats usedALGWLRMOA 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 defaultoutsidescrolls the whole window with the page behind it locked — right for a form read top to bottom.Version 2.14 is outTen changes since SeptemberA header of your own, paddings per section
The
#headerslot replaces the heading and keeps the close button.header-config,body-configandfooter-configset 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.18Without 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, soaria-labeltakes 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
| Prop | Type | default | Description |
|---|---|---|---|
title | string | undefined | undefined | The window title. It also gives the window its accessible name through `aria-labelledby`. |
size | "sm" | "md" | "lg" | "xl" | "full" | undefined | undefined | The window width — the `GrModal` scale: from `sm` to `xl`, `full` is the whole screen. |
ariaLabel | string | undefined | undefined | The 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. |
closeOnBackdrop | boolean | undefined | true | Close on a click on the backdrop — only a click that started on it. |
closeOnEsc | boolean | undefined | true | Close on Esc. The top layer of the stack closes. |
showHeader | boolean | undefined | true | Show the header. Without it both the title and the close button go — the window then takes its name from `ariaLabel`. |
showCloseButton | boolean | undefined | true | The close button in the header. Without it the window is closed by Esc, the backdrop and the footer buttons. |
headerConfig | GrDialogSectionConfig | undefined | undefined | The paddings and the line of the header: `paddingX`, `paddingY`, `bordered`. |
footerConfig | GrDialogSectionConfig | undefined | undefined | The paddings and the line of the footer. The footer is drawn only when the `#footer` slot is passed. |
bodyConfig | GrDialogSectionConfig | undefined | undefined | The paddings of the body. `bordered` does not apply to the body: it has no top line of its own. |
closeLabel | string | undefined | undefined | The a11y label of the close button (i18n). |
scrollBehavior | GrModalScrollBehavior | undefined | "outside" | What scrolls when the content is long. With `inside` the header and the footer are pinned, and only the body moves. |
initialFocus | HTMLElement | null | undefined | null | The 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-model | boolean | — | Whether the window is open (`v-model`). The window has no state of its own: the parent opens and closes it. |
Slots
| Slot | Type | Description |
|---|---|---|
default | any | The 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`. |
footer | any | The footer with the actions of the window. Without the slot there is no footer. |
Events
| Event | Signature | Description |
|---|---|---|
update:modelValue | (event: "update:modelValue", value: boolean): void | — |
opened | (event: "opened"): void | — |
closed | (event: "closed"): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
open | () => void | Ask the parent to open the window: emits `update:modelValue` with `true`. |
close | () => void | Ask the parent to close the window: emits `update:modelValue` with `false`. |
toggle | () => void | Toggle 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 type { GrDialogProps, GrDialogEmits, GrDialogHeaderProps } from '@feugene/granularity/components/GrDialog'Filedist/types/components/GrDialog/GrDialog.vue.d.ts
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;
}interface GrDialogEmits {
(e: 'update:modelValue', value: boolean): void;
(e: 'opened'): void;
(e: 'closed'): void;
}Filedist/types/components/GrDialog/GrDialogHeader.vue.d.ts
interface GrDialogHeaderProps {
title?: string;
showCloseButton?: boolean;
config?: GrDialogSectionConfig;
closeLabel?: string;
}Filedist/types/components/GrDialog/GrDialogFooter.vue.d.ts
interface GrDialogFooterProps {
config?: GrDialogSectionConfig;
}Filedist/types/components/GrDialog/dialogShared.d.ts
type GrDialogSize = GrOverlaySize;interface GrDialogSectionConfig {
paddingX?: string;
paddingY?: string;
bordered?: boolean;
}Filedist/types/components/GrDialog/safelist.d.ts
const grDialogSafelist: string[];Filedist/types/components/GrDialog/index.d.ts
type GrDialogInstance = ComponentExposed<typeof GrDialogComponent>;Filedist/types/components/GrDialog/GrDialogCloseButton.vue.d.ts
interface GrDialogCloseButtonEmits {
(e: 'click'): void;
}Accessibility
Keys
EscTab / Shift+Tab
Full contract of the package: docs/keyboard.md ↗
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.
<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.
Props in this example