• CMSnpm version

    • Overview
    • ContentBuilder
    • InviteUserForm
    • LoginForm
    • RenewPasswordForm
    • WysiwygInput
  • UInpm version

    • Overview
    • Accordion
    • AlertBubble
    • AnnouncementBar
    • Avatar
    • AvatarFileInput
    • Badge
    • Button
    • ButtonGroup
    • ButtonList
    • Calendar
    • Checkbox
    • CheckboxButton
    • CheckboxInput
    • CheckboxList
    • Chip
    • ColorRadio
    • ColorRadioGroup
    • Combobox
    • DatePicker
    • DatePickerInput
    • DateRangePicker
    • DateRangePickerInput
    • DatetimePicker
    • DatetimePickerInput
    • Dialog
    • Dropdown
    • Dropzone
    • ErrorMessage
    • FileInput
    • FlashMessages
    • FormComponent
    • Icon
    • IconButton
    • ImageGallery
    • InfoBox
    • Input
    • Label
    • Layout
    • Lightbox
    • ListItem
    • Loader
    • Lozenge
    • Menu
    • Message
    • Modal
    • ✅ ModalDialog
    • ✅ ModalHeader
    • MultiCombobox
    • MultiSelect
    • Pagination
    • Paper
    • Popover
    • Radio
    • RadioGroup
    • RasterImage
    • Select
    • Switch
    • ✅ Tabs
    • TextInput
    • TextLink
    • Textarea
    • TimePicker
    • TimePickerInput
    • Toggle
    • Tooltip
    • Typography
  • Formnpm version

    • Overview
    • AvatarFileInput
    • CheckboxButton
    • CheckboxInput
    • CheckboxList
    • ColorRadioGroup
    • Combobox
    • DatePickerInput
    • DateRangePickerInput
    • DatetimePickerInput
    • Dropzone
    • FileInput
    • Form
    • FormRenderer
    • GpsInput
    • MoneyInput
    • MultiCombobox
    • MultiSelect
    • NumberInput
    • PasswordInput
    • RadioGroup
    • Select
    • TextInput
    • Textarea
    • TimePickerInput
    • Toggle
  • DataGridnpm version

    • Overview
    • DataGrid
    • DataGridCustomExample
    • ExportButton
    • FilterList
    • Filters
    • FiltersButton
    • FulltextInput
    • HiddenColumns
    • HiddenColumnsButton
    • Pagination
    • RowCounts
    • RowsPerPageSelect
    • SelectedRowsToolbar
    • TableV2
    • ToolbarControl
    • ToolbarCustoms
    • ToolbarTabs
  • Wysiwygnpm version

    • Overview
  • Resizernpm version

    • Overview
  • Routernpm version

    • Overview
  • Corenpm version

    • Overview
  • Core-Reactnpm version

    • Overview
  • DnDnpm version

    • Overview
  • Stylesnpm version

    • Overview
  • Localizenpm version

    • Overview
  • Analyticsnpm version

    • Overview
  • Datepickernpm version

    • Overview
  • Icons-generatornpm version

    • Overview
  • Smart-addressnpm version

    • Overview
  • E2Enpm version

    • Overview
  • E2E-Playwrightnpm version

    • Overview
View source on GitLab

Modal

Overlay / modal system for @uxf/ui: a controlled Modal component plus an imperative modal stack (ModalProvider + openModal / closeModal) with named layers.

When to use

  • Use the controlled Modal component when you own the open state locally (isOpen / onClose).
  • Use the imperative openModal API (backed by a single ModalProvider) to open modals from anywhere — event handlers, services — without threading state, and to stack modals in named layers.
  • For content, wrap children in a DialogPanel (@uxf/ui/dialog) for the standard panel, use ModalDialog for a ready-made title / body / footer layout, or ModalHeader for just the header row.

Modal wraps the lower-level Dialog and supplies its Floating UI plumbing, so you rarely need Dialog directly. There is no @uxf/form twin.

Usage

Controlled Modal

import { Button } from "@uxf/ui/button";
import { DialogPanel } from "@uxf/ui/dialog";
import { Modal } from "@uxf/ui/modal";
import { useState } from "react";

function Example() {
    const [isOpen, setIsOpen] = useState(false);

    return (
        <>
            <Button onClick={() => setIsOpen(true)}>Open</Button>
            <Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
                <DialogPanel width="xs">Modal content</DialogPanel>
            </Modal>
        </>
    );
}

Imperative stack (openModal)

Mount a single ModalProvider once at the app root, wired to the shared ref:

import { getModalStackRef, ModalProvider } from "@uxf/ui/modal";

<ModalProvider ref={getModalStackRef()} />;

Then open modals from anywhere:

import { Button } from "@uxf/ui/button";
import { DialogPanel } from "@uxf/ui/dialog";
import { closeModal, openModal } from "@uxf/ui/modal";

openModal({
    children: (
        <DialogPanel width="xs">
            <Button onClick={() => closeModal()}>Close</Button>
        </DialogPanel>
    ),
    onClose: handleClose,
});

Props

Modal

PropTypeDefaultDescription
isOpenboolean— (required)Whether the modal is rendered.
onClose() => void— (required)Called when the modal requests to close (backdrop click or ESC, unless disabled).
childrenReactNode— (required)Modal content, typically a DialogPanel.
variantModalVariant"default"Visual variant (default, drawer-right).
isBackdropCloseDisabledbooleanfalseDisable closing on outside / backdrop press.
isEscKeyCloseDisabledbooleanfalseDisable closing on the ESC key.
classNamestring—Extra class on the overlay.
styleCSSProperties—Inline style on the overlay.

openModal(modal, options?)

modal is a ModalProviderProps descriptor; returns the created modal instance id.

FieldTypeDefaultDescription
childrenReactNode— (required)Modal content.
onClose() => void—Called after the modal closes (backdrop, ESC, or programmatic).
variantModalVariant"default"Visual variant.
isBackdropCloseDisabledbooleanfalseDisable backdrop close.
isEscapeKeyCloseDisabledbooleanfalseDisable ESC close.
classNamestring—Extra class on the overlay.

Note the different spelling: the Modal component uses isEscKeyCloseDisabled, while openModal / ModalProviderProps uses isEscapeKeyCloseDisabled.

options is OpenModalOptions:

FieldTypeDefaultDescription
layerModalLayerNamedefaultLayer ("main")Layer to open in.
shouldReplacebooleantrueReplace an existing modal in the same layer instead of stacking.

Variants & states

  • Variant: default or drawer-right (right-anchored drawer on sm+). Passed through to the underlying Dialog.
  • Close triggers: by default a modal closes on backdrop press and ESC. Set isBackdropCloseDisabled / isEscKeyCloseDisabled (component) or isBackdropCloseDisabled / isEscapeKeyCloseDisabled (openModal) to disable them. In the stack, only the topmost modal responds to backdrop / ESC, so clicking inside a higher modal never closes ones underneath.

Modal stack & layers

The imperative API renders modals through the mounted ModalProvider. Service functions (from @uxf/ui/modal):

FunctionDescription
openModal(modal, options?)Opens a modal in the given layer (or the default layer); returns its instance id.
closeModal()Closes the topmost modal (highest z-index).
closeModalLayer(layer)Closes all modals in the given layer.
closeAllModals()Closes every modal across all layers.
getModalStackRef()Returns the ref to pass to ModalProvider.
getModalRef()Deprecated alias of getModalStackRef().

By default two layers are configured (from DEFAULT_MODAL_LAYERS):

{
    layers: {
        main: { name: "main", zIndex: 100 },
        confirm: { name: "confirm", zIndex: 1000 },
    },
    defaultLayer: "main",
}
import { closeModalLayer, openModal } from "@uxf/ui/modal";
import { DialogPanel } from "@uxf/ui/dialog";

openModal({ children: <DialogPanel>Confirmation</DialogPanel> }, { layer: "confirm" });
closeModalLayer("confirm");

Known limitation: the shipped ModalProvider assigns every opened modal the default layer's z-index regardless of the layer option, so custom per-layer z-index values do not currently affect stacking (modals paint in the order they were opened). Layer names still work for targeted closing via closeModalLayer.

Custom layers

Provide a custom configuration through ModalLayerConfigProvider (optional — the default config is used when absent):

import { ModalLayerConfigProvider, ModalLayersConfiguration, ModalProvider, getModalStackRef } from "@uxf/ui/modal";

const customModalLayers: ModalLayersConfiguration = {
    layers: {
        main: { name: "main", zIndex: 100 },
        confirm: { name: "confirm", zIndex: 1000 },
        notification: { name: "notification", zIndex: 9000 },
    },
    defaultLayer: "main",
};

<ModalLayerConfigProvider config={customModalLayers}>
    <ModalProvider ref={getModalStackRef()} />
</ModalLayerConfigProvider>;

Add custom layer names to the type system with declaration merging on ModalLayers (@uxf/ui/modal/theme):

// modal.d.ts (in your project)
declare module "@uxf/ui/modal/theme" {
    interface ModalLayers {
        notification: true;
    }
}

ModalLayerName (keyof ModalLayers) then autocompletes the new name in openModal(..., { layer }) and closeModalLayer.

Requirements

Modal renders through Dialog, so import the dialog stylesheet once in your global CSS:

@import url("@uxf/ui/css/dialog.css");

Also requires the global @uxf/ui token layer, set up once per app — see @uxf/ui setup.

For the imperative API, mount exactly one <ModalProvider ref={getModalStackRef()} /> at the app root.

Default
Open in new tab
MobileTest
Open in new tab
ModalProvider
Open in new tab
ModalProviderWithLayers
Open in new tab