• CMSnpm version

    • ContentBuilder
    • InviteUserForm
    • LoginForm
    • RenewPasswordForm
  • UInpm version

    • Get started
    • 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
    • ✅ Tabs
    • TextInput
    • TextLink
    • Textarea
    • TimePicker
    • TimePickerInput
    • Toggle
    • Tooltip
    • Typography
  • Formnpm version

    • 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

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

    • Getting started
  • Resizernpm version

    • Readme
  • Routernpm version

    • Readme
  • Corenpm version

    • Readme
  • Core-Reactnpm version

    • Readme
  • Stylesnpm version

    • Readme
  • Localizenpm version

    • Readme
  • Analyticsnpm version

    • Readme
  • Datepickernpm version

    • Readme
  • Icons-generatornpm version

    • Readme
  • Smart-addressnpm version

    • Readme
  • E2Enpm version

    • Readme
View source on GitLab

Modal

CSS dependencies

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

Implementation ModalProvider

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

function App(props: AppProps) {
    return (
        <UiContextProvider value={...}>
            {props.children}
            <ModalProvider ref={getModalStackRef()}/>
        </UiContextProvider>
    );
}

function Component(props: AppProps) {
    return (
        <div>
            <Button
                onClick={() =>
                    openModal({
                        children: (
                            <DialogPanel width="xs">Whatever content inside Modal</DialogPanel>
                        ),
                        onClose: () => console.log("modal closed"),
                    })
                }
            >
                Click to open modal - default
            </Button>
        </div>
    );
}

Implementation Modal

function Component(props: AppProps) {
    const [isOpen, setIsOpen] = useState(false);

    return (
        <div>
            <Button onClick={() => setIsOpen(true)}>Click to open modal</Button>
            <Modal onClose={() => setIsOpen(false)} isOpen={isOpen}>
                <DialogPanel width="xs">Whatever content inside Modal</DialogPanel>
            </Modal>
        </div>
    );
}

Layered Modals

The modal system supports opening multiple modals simultaneously in different layers. Each layer has its own z-index and stacking order.

Default Layer Configuration

By default, two layers are available:

{
  layers: {
    main: { name: "main", zIndex: 100 },
    confirm: { name: "confirm", zIndex: 1000 },
  },
  defaultLayer: "main"
}

Basic Usage with Layers

import { openModal, closeModal, closeModalLayer, closeAllModals } from "@uxf/ui/modal";

// Open modal in default "main" layer
openModal({
    children: <DialogPanel>Main content</DialogPanel>
});

// Open modal in specific layer (fully type-safe!)
openModal({
    children: <DialogPanel>Confirmation dialog</DialogPanel>
}, {
    layer: "confirm" // TypeScript autocompletes: "main" | "confirm"
});

// Close topmost modal (highest z-index)
closeModal();

// Close all modals in a specific layer
closeModalLayer("confirm");

// Close all modals across all layers
closeAllModals();

Stacking Behavior

  • Modals with higher z-index appear on top of modals with lower z-index
  • Only the topmost modal (highest z-index) responds to backdrop clicks and ESC key
  • Clicking inside a higher-layer modal will NOT close lower-layer modals
  • Each layer can have one modal by default (use shouldReplace: false option to allow multiple)

Closing Modals

Service functions (for modals opened via openModal)

| Function | Description | |---|---| | closeModal() | Closes the topmost modal (highest z-index across all layers) | | closeModalLayer(layer) | Closes all modals in the specified layer | | closeAllModals() | Closes all modals across all layers |

import { closeModal, closeModalLayer, closeAllModals } from "@uxf/ui/modal";

closeModal();                  // closes topmost
closeModalLayer("confirm");    // closes all in "confirm" layer
closeAllModals();              // closes everything

onClose callback

When opening a modal via openModal, you can pass an onClose callback that fires whenever the modal is closed (by backdrop click, ESC key, or programmatic close):

openModal({
    children: <DialogPanel>Content</DialogPanel>,
    onClose: () => console.log("modal closed"),
});

Disabling close triggers

You can prevent the modal from being closed by backdrop click or ESC key:

openModal({
    children: <DialogPanel>Content</DialogPanel>,
    isBackdropCloseDisabled: true,    // backdrop click won't close
    isEscapeKeyCloseDisabled: true,   // ESC key won't close
});

Modal component (controlled)

When using the Modal component directly, pass onClose to handle close events:

<Modal onClose={() => setIsOpen(false)} isOpen={isOpen} isBackdropCloseDisabled={false}>
    <DialogPanel>Content</DialogPanel>
</Modal>

Custom Layer Configuration

You can configure custom layers for your project using ModalLayerConfigProvider:

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

// Define custom layers
const customModalLayers: ModalLayersConfiguration = {
    layers: {
        base: { name: "base", zIndex: 1000 },
        form: { name: "form", zIndex: 1100 },
        tooltip: { name: "tooltip", zIndex: 1200 },
        notification: { name: "notification", zIndex: 9000 },
    },
    defaultLayer: "base"
};

// Wrap your app with the config provider
function App({ children }: { children: React.ReactNode }) {
    return (
        <ModalLayerConfigProvider config={customModalLayers}>
            <ModalProvider ref={getModalStackRef()} />
            {children}
        </ModalLayerConfigProvider>
    );
}

// Use your custom layers
function MyComponent() {
    return (
        <Button onClick={() =>
            openModal({
                children: <DialogPanel>Form modal</DialogPanel>
            }, {
                layer: "form"
            })
        }>
            Open Form
        </Button>
    );
}

Custom Configuration Guidelines

  • name: Unique identifier for the layer
  • zIndex: CSS z-index value (higher = on top). This determines both the visual stacking and logical order.
  • defaultLayer: Layer name to use when no layer is specified

TypeScript Support - Custom Layer Names

You can extend the available layer names using TypeScript declaration merging:

Step 1: Extend the interface

// types/modal.d.ts (in your project)
import "@uxf/ui/modal";

declare module "@uxf/ui/modal/theme" {
    interface ModalLayers {
        // Add your custom layer names
        form: true;
        notification: true;
        tooltip: true;
    }
}

Step 2: Configure layers at runtime

// app/layout.tsx or _app.tsx
import { ModalLayerConfigProvider, ModalProvider, getModalStackRef } from "@uxf/ui/modal";

const customModalLayers = {
    layers: {
        // Default layers (keep or override)
        main: { name: "main", zIndex: 1000 },
        confirm: { name: "confirm", zIndex: 1010 },
        // Your custom layers
        form: { name: "form", zIndex: 1100 },
        notification: { name: "notification", zIndex: 9000 },
        tooltip: { name: "tooltip", zIndex: 9999 },
    },
    defaultLayer: "main"
};

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

Step 3: Use with full type safety

import { openModal } from "@uxf/ui/modal";

openModal({
    children: <DialogPanel>Form content</DialogPanel>
}, {
    layer: "form" // ✅ TypeScript autocompletes and validates!
    // Available: "main" | "confirm" | "form" | "notification" | "tooltip"
});

Backwards Compatibility

The layered modal system is fully backwards compatible:

  • Existing code using openModal() without options continues to work
  • getModalRef() is aliased to getModalStackRef()
  • Default "main" layer is used when no layer is specified
  • Single modal behavior is maintained when only one modal is open
Default
Open in new tab
ModalProvider
Open in new tab
ModalProviderWithLayers
Open in new tab