UI componentsdocs/ui/components/overlays.md

Overlays

Content layered above the page: dialogs, drawers, popovers, menus and tooltips.

← Component reference

Centered dialog with focus trap, Escape, scroll lock and enter/exit motion.

function Example() {
    const open = signal(false);
    return (
        <>
            <Button onClick={() => open.set(true)}>Open modal</Button>
            <Modal
                open={open()}
                onClose={() => open.set(false)}
                title="Edit profile"
                description="Changes are saved when you press Save."
                footer={
                    <Group gap="0.5rem">
                        <Button variant="ghost" onClick={() => open.set(false)}>
                            Cancel
                        </Button>
                        <Button onClick={() => open.set(false)}>Save</Button>
                    </Group>
                }
            >
                Modal body content.
            </Modal>
        </>
    );
}

Slots: root backdrop body close description footer header panel title

PropTypeRequiredDefaultDescription
onClose() => voidyesCalled when the user closes it (close button, Escape, backdrop); set open to false.
openbooleanyesWhether the dialog is shown (controlled).
childrencontentBody content.
closeOnBackdropbooleanClose when the backdrop is clicked (default true).
closeOnEscapebooleanClose on Escape (default true).
descriptioncontentText under the title; also the dialog's accessible description.
footercontentBottom bar content, usually the action buttons.
hideClosebooleanHide the header close button.
labelstringAccessible name when there is no visible title.
mountElementPortal target (defaults to document.body).
placement"center" | "top"Vertical placement (default center).
role"dialog" | "alertdialog"alertdialog for confirmations that interrupt the user.
size"full" | "sm" | "md" | "lg" | "xl"Width preset; override freely with --a-modal-width.
titlestringHeading; also the dialog's accessible name.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

ConfirmDialog #

Confirmation built on the shared dialog surface (focus trap, Escape, scroll lock, motion). Initial focus goes to Cancel — the safe choice. Slots match Modal (root backdrop panel …); attributes land on the panel.

function Example() {
    const open = signal(false);
    return (
        <>
            <Button variant="danger" onClick={() => open.set(true)}>
                Delete project
            </Button>
            <ConfirmDialog
                open={open()}
                danger
                title="Delete project?"
                message="This cannot be undone."
                confirmLabel="Delete"
                onConfirm={() => open.set(false)}
                onCancel={() => open.set(false)}
            />
        </>
    );
}

Slots: root backdrop body close description footer header panel title

PropTypeRequiredDefaultDescription
messagestringyesThe question to confirm.
onCancel() => voidyesCalled on cancel, Escape or backdrop click.
onConfirm() => voidyesCalled when the user confirms.
openbooleanyesWhether the dialog is shown (controlled).
cancelLabelstring"Cancel"Cancel button text.
confirmLabelstring"Confirm"Confirm button text.
dangerbooleanDestructive action: the confirm button uses the danger style.
titlestringDialog heading.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Drawer #

Panels that slide in from an edge.

Drawer #

Edge-anchored dialog. Slots match {@link Modal}.

function Example() {
    const open = signal(false);
    return (
        <>
            <Button onClick={() => open.set(true)}>Open drawer</Button>
            <Drawer open={open()} onClose={() => open.set(false)} title="Filters">
                Drawer content.
            </Drawer>
        </>
    );
}

Slots: root backdrop body close description footer header panel title

PropTypeRequiredDefaultDescription
onClose() => voidyesCalled when the user closes it (close button, Escape, backdrop); set open to false.
openbooleanyesWhether the dialog is shown (controlled).
childrencontentBody content.
closeOnBackdropbooleanClose when the backdrop is clicked (default true).
closeOnEscapebooleanClose on Escape (default true).
descriptioncontentText under the title; also the dialog's accessible description.
footercontentBottom bar content, usually the action buttons.
hideClosebooleanHide the header close button.
labelstringAccessible name when there is no visible title.
mountElementPortal target (defaults to document.body).
role"dialog" | "alertdialog"alertdialog for confirmations that interrupt the user.
side"top" | "bottom" | "left" | "right""right"Edge the drawer slides in from.
size"full" | "sm" | "md" | "lg"Width (or height, for top / bottom).
titlestringHeading; also the dialog's accessible name.

BottomSheet #

Mobile bottom sheet (UIkit / Mantine Drawer bottom). Built on the shared dialog frame: focus trap, Escape (topmost layer), scroll lock, exit motion. Slots match {@link DialogBaseProps} (panel is the host); theme key BottomSheet.

function Example() {
    const open = signal(false);
    return (
        <>
            <Button onClick={() => open.set(true)}>Open sheet</Button>
            <BottomSheet open={open()} onClose={() => open.set(false)} title="Share">
                Sheet content.
            </BottomSheet>
        </>
    );
}

Slots: root backdrop body close description footer header panel title

PropTypeRequiredDefaultDescription
onClose() => voidyesCalled when the user closes it (close button, Escape, backdrop); set open to false.
openbooleanyesWhether the dialog is shown (controlled).
childrencontentBody content.
closeOnBackdropbooleanClose when the backdrop is clicked (default true).
closeOnEscapebooleanClose on Escape (default true).
descriptioncontentText under the title; also the dialog's accessible description.
footercontentBottom bar content, usually the action buttons.
hideClosebooleanHide the header close button.
labelstringAccessible name when there is no visible title.
mountElementPortal target (defaults to document.body).
role"dialog" | "alertdialog"alertdialog for confirmations that interrupt the user.
titlestringHeading; also the dialog's accessible name.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Popover & hover card #

Floating panels anchored to a trigger.

Popover #

Click-to-toggle panel anchored to a trigger. Escape / outside click close it and return focus to the trigger.

function Example() {
    const open = signal(false);
    return (
        <Popover open={open()} onOpenChange={open.set} label="Share" panelLabel="Share project">
            Anyone with the link can view this project.
        </Popover>
    );
}

Slots: root arrow panel trigger

PropTypeRequiredDefaultDescription
onOpenChange(open: boolean) => voidyesCalled with the next open state (trigger click, Escape, outside click).
openbooleanyesWhether the panel is open (controlled).
arrowbooleanShow a small arrow pointing at the trigger.
childrencontentPanel content.
labelcontent"Open"Label for the built-in trigger button
panelLabelstringAccessible name for the panel (defaults to the trigger label when it's text).
placementPopoverPlacement"bottom-start"Preferred side and alignment; flips and shifts to stay in view.
trigger(api: PopoverTriggerApi) => unknownRender your own trigger: trigger={(t) => <MyButton {...t.attrs} />}.

PopoverTriggerApi — Attributes to spread on a custom trigger so it stays wired for a11y.

FieldTypeRequiredDescription
openbooleanyesWhether the panel is open.
toggle() => voidyesOpens or closes the panel.
attrs{ "aria-expanded": boolean; "aria-controls": string; "aria-haspopup": "dialog"; onClick: () => void; }yesARIA and event attributes to spread onto your trigger element.

PopoverPlacement

type PopoverPlacement = | "bottom-start" | "bottom-end" | "bottom" | "top-start" | "top-end" | "top";

HoverCard #

Hover/focus card panel (Mantine HoverCard).

<HoverCard dropdown={<Text>Ada Lovelace · Analyst of engines</Text>}>
    <Anchor href="#ada">@ada</Anchor>
</HoverCard>

Slots: root dropdown target

PropTypeRequiredDefaultDescription
dropdowncontentyesPanel content shown on hover/focus.
childrencontentThe trigger (hovering or focusing it opens the card).
closeDelaynumber160Milliseconds after leaving before the card closes (lets the pointer reach it).
openDelaynumber120Milliseconds of hover or focus before the card opens.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Tooltip #

Hover/focus tooltip linked with aria-describedby; Escape dismisses and the tooltip itself is hoverable (WCAG 1.4.13).

Interact with the example: callbacks show up here.
<Tooltip content="Copies the deploy URL">
    <Button variant="outline" onClick={() => {}}>
        Copy link
    </Button>
</Tooltip>

Slots: root arrow target tooltip

PropTypeRequiredDefaultDescription
contentcontentyesTooltip text or content.
arrowbooleantrueShow an arrow pointing at the target.
childrencontentThe element the tooltip describes (shown on hover and focus).
closeDelaynumber80Delay before hiding, ms (default 80) — lets the pointer reach the tooltip.
disabledbooleanNever show the tooltip.
openDelaynumber250Delay before showing on hover, ms (default 250). Focus shows immediately.
placement"top" | "bottom" | "left" | "right""top"Preferred side; flips to stay in view.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Action menus and context menus.

Action menu with roving focus (↑ ↓ Home End, type-ahead), Escape, outside click, focus restore and enter/exit motion.

Nothing chosen yet

function Example() {
    const open = signal(false);
    const last = signal("");
    const pick = (label: string) => () => last.set(label);
    return (
        <Group gap="0.75rem">
            <div style={{ position: "relative" }}>
                <Button variant="outline" aria-expanded={open()} onClick={() => open.set(!open())}>
                    Project actions ▾
                </Button>
                <Menu
                    open={open()}
                    onClose={() => open.set(false)}
                    label="Project actions"
                    items={[
                        { type: "label", label: "marketing-site" },
                        { label: "Rename", onSelect: pick("Rename") },
                        { label: "Duplicate", onSelect: pick("Duplicate") },
                        { type: "separator" },
                        { label: "Delete", onSelect: pick("Delete"), danger: true },
                    ]}
                />
            </div>
            <Text muted>{last() ? `Chose “${last()}”` : "Nothing chosen yet"}</Text>
        </Group>
    );
}

Slots: root description group icon item label separator shortcut

PropTypeRequiredDefaultDescription
itemsMenuItem[]yesActions, separators ({ type: "separator" }) and group labels ({ type: "label" }).
openbooleanyesWhether the menu is shown (controlled).
labelstringAccessible name for the menu.
onClose() => voidCalled on outside click / Escape / selection. Prefer with a wrapping .a-menu-host.
placementMenuPlacement"bottom-start"Preferred position relative to the trigger; flips to stay in view.

MenuItem

type MenuItem = MenuAction | { type: "separator" } | { type: "label"; label: unknown };

MenuAction

FieldTypeRequiredDescription
type"item"Marks a regular action (the default for items without type).
labelcontentyesItem text or content.
onSelect() => voidyesCalled when the item is chosen (click, Enter or Space); the menu then closes.
dangerbooleanDestructive action: danger colour.
disabledbooleanShown but can't be chosen; skipped by arrow keys.
iconcontentLeading icon or content.
shortcutstringRight-aligned hint, e.g. ⌘K.
descriptionstringSecondary line under the label.

MenuPlacement

type MenuPlacement = "bottom-start" | "bottom-end" | "top-start" | "top-end";

ContextMenu #

Right-click (or Shift+F10 / ContextMenu key) menu. Clamped to the viewport, focuses the first item, ↑ ↓ Home End navigate, Escape restores focus.

Right-click this file card
Interact with the example: callbacks show up here.
<ContextMenu
    items={[
        { id: "open", label: "Open", onSelect: () => {} },
        { id: "rename", label: "Rename", onSelect: () => {} },
        { id: "delete", label: "Delete", danger: true, onSelect: () => {} },
    ]}
>
    <Paper withBorder>Right-click this file card</Paper>
</ContextMenu>

Slots: root item menu

PropTypeRequiredDefaultDescription
itemsContextMenuItem[]yesMenu items, in order.
childrencontentThe area that opens the menu on right-click, Shift+F10 or the ContextMenu key.

ContextMenuItem

FieldTypeRequiredDescription
idstringyesItem id.
labelstringyesItem text.
dangerbooleanDestructive action: danger colour.
disabledbooleanShown but can't be chosen.
onSelect() => voidyesCalled when the item is chosen; the menu then closes.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Spotlight #

Command palette: the search input is a combobox driving a listbox — ↑ ↓ move the active option, Enter runs it, Escape closes.

function Example() {
    const open = signal(false);
    return (
        <>
            <Button onClick={() => open.set(true)}>Open command palette</Button>
            <Spotlight
                open={open()}
                onClose={() => open.set(false)}
                actions={[
                    { id: "new", label: "New file", onSelect: () => open.set(false) },
                    { id: "open", label: "Open recent", onSelect: () => open.set(false) },
                    { id: "settings", label: "Settings", onSelect: () => open.set(false) },
                ]}
            />
        </>
    );
}

Slots: root backdrop description empty input label list option panel

PropTypeRequiredDefaultDescription
actionsSpotlightAction[]yesActions to search and run.
onClose() => voidyesCalled on Escape, backdrop click or after an action runs; set open to false.
openbooleanyesWhether the palette is shown (controlled).
placeholderstring"Search actions…"Search field hint.

SpotlightAction

FieldTypeRequiredDescription
idstringyesAction id.
labelstringyesAction name; what the search matches.
descriptionstringSecondary line under the label.
onSelect() => voidyesCalled when the action is chosen; the palette then closes.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Fullscreen image viewer: focus trap, Escape, ← → between images.

function Example() {
    const index = signal<number | null>(null);
    const images = [
        { src: "/images/one.jpg", alt: "Blue placeholder" },
        { src: "/images/two.jpg", alt: "Green placeholder" },
        { src: "/images/three.jpg", alt: "Orange placeholder" },
    ];
    return (
        <>
            <Button onClick={() => index.set(0)}>Open gallery</Button>
            {index() !== null ? (
                <Lightbox
                    images={images}
                    index={index() ?? 0}
                    onChange={index.set}
                    onClose={() => index.set(null)}
                />
            ) : null}
        </>
    );
}

Slots: root backdrop caption control controls image stage

PropTypeRequiredDefaultDescription
imagesLightboxImage[]yesThe images, in order.
indexnumberyesIndex of the shown image; null closes the viewer.
onClose() => voidyesCalled on Escape, backdrop click or the close button; set index to null.
onChange(index: number) => voidCalled with the next index when the user moves with ← / → or the arrows.

LightboxImage

FieldTypeRequiredDescription
srcstringyesImage URL.
altstringAlternative text (also the viewer's accessible name).
captionstringCaption under the image.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Overlay #

Dimmed layer over its positioned parent.

Content under the overlay

<Box style={{ position: "relative", "min-height": "6rem" }}>
    <Text>Content under the overlay</Text>
    <Overlay blur />
</Box>
PropTypeRequiredDefaultDescription
blurbooleanBlur what is underneath.
childrencontentContent shown centred on the overlay.
onClick(e: MouseEvent) => voidCalled when the overlay is clicked.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.