Skip to main content

Modal

A fully accessible, WCAG-compliant modal/dialog component for blocking user interaction and capturing focused attention during critical workflows.


Usage

Basic usage

'use client';
import { useState } from 'react';
import { Modal } from '@nofinite/nui';

const Page = () => {
const [open, setOpen] = useState(false);

return (
<>
<button onClick={() => setOpen(true)}>Open Modal</button>

<Modal open={open} onClose={() => setOpen(false)} title="Confirm action">
<p>Are you sure you want to continue?</p>
</Modal>
</>
);
};

With confirmation actions

<Modal open={open} onClose={() => setOpen(false)} title="Confirm action">
<p>Are you sure you want to continue?</p>

<div style={{ marginTop: 16 }}>
<button onClick={() => setOpen(false)}>Cancel</button>
<button style={{ marginLeft: 8 }}>Confirm</button>
</div>
</Modal>

Variants

This Modal component does not expose visual variants by design.

Guidelines:

  • Keep modal appearance consistent across the app.
  • Customize visuals using className or theme tokens instead of variants.
  • For destructive or special flows, adjust content—not the modal shell.

Sizes

The modal has a responsive, content-driven size:

  • Width: min(90vw, 720px)
  • Height: up to 90vh with internal scrolling

Guidelines:

  • Avoid forcing sizes unless absolutely necessary.
  • Keep modal content concise and task-focused.

States

The modal supports several behavioral states:

<Modal open />
<Modal open disableEsc />
<Modal open disableClickOutside />

State behavior:

  • open

    • Controls visibility.
    • When false, the modal is not rendered.
  • disableEsc

    • Prevents closing the modal via Esc key.
  • disableClickOutside

    • Prevents closing the modal by clicking outside the dialog.

Native Props / Composition

The modal supports composition via children and standard React props.

<Modal open={open} onClose={handleClose} className="custom-modal">
Custom modal content
</Modal>
  • className is applied to the dialog container.
  • Native focusable elements inside the modal work automatically.

Props

PropTypeDefaultDescription
openbooleanControls whether the modal is visible
onClose() => voidCallback fired when the modal requests to close
titlestringModal title, used for accessible labeling
descriptionstringOptional description for screen readers
labelledByIdstringCustom aria-labelledby ID
describedByIdstringCustom aria-describedby ID
disableClickOutsidebooleanfalseDisables click-outside-to-close behavior
disableEscbooleanfalseDisables closing the modal with Esc
initialFocusRefReact.RefObject<HTMLElement>Element to receive focus on open
classNamestring""Additional class names for styling
childrenReact.ReactNodeModal content

Behavior Notes

  • The modal renders inside a portal attached to <body>.

  • Background content is made inert while the modal is open.

  • Focus is:

    • Trapped inside the modal
    • Restored to the previously focused element on close
  • Page scroll is locked while the modal is open.

  • Animations respect prefers-reduced-motion.

Example:

<Modal open={open} onClose={handleClose} initialFocusRef={confirmButtonRef} />

Accessibility

  • Renders as: <div role="dialog">

  • Uses:

    • aria-modal="true"
    • aria-labelledby
    • aria-describedby
  • Keyboard support:

    • Tab / Shift+Tab for focus trapping
    • Esc to close (unless disabled)
  • Background content is fully inert (not focusable or interactive)

  • Includes a screen-reader friendly close button

Example:

<Modal title="Delete item" description="This action cannot be undone" />

Layout

  • Centered in the viewport
  • Scrolls internally if content exceeds height
  • Overlay blocks interaction with the app
<Modal open style={{ maxWidth: 480 }}>
Compact modal content
</Modal>

Best Practices

Do

  • Use for critical decisions or blocking workflows
  • Keep content concise and action-oriented
  • Always provide a clear way to close the modal
  • Use title for accessible labeling

Don’t

  • Nest modals inside modals
  • Use for non-blocking or lightweight UI
  • Disable Esc or outside click without strong reason
  • Overload the modal with complex layouts