Oihana Next UI - 0.9.0

Application logo

Modal Component

Modal Examples with useModal Hook

Simple Modal

Localized labels

None of the three modals below is given an agree, a disagree or a closeTitle prop. Every label — the two buttons and the accessible name of the header close button — is read from the components.modal bundle. Switch the language of the lab and reopen them : the labels follow.

Popover mode — light, non-blocking (usePopover)

Opt-in usePopover: the modal renders through the browser's native Popover API instead of <dialog>. It can be opened declaratively (a button with popovertarget, no JS) or via useModal. Escape and a backdrop click close it. It does not block the page — use it for light panels, not for blocking confirmations.

Responsive Fullscreen (Breakpoint)

Toggle Modal (with State Tracking)

Closed

Alert Modals (Single Button)

Confirmation Modals (Two Buttons)

Fullscreen Modal

Responsive & Placement

Custom Width

Behavior Options

Custom Footer

Custom Footer Node — sticky footer + scrollable content

When to use footerNode

Use the footerNode prop when the standardagree / disagree footer is too rigid — typically for forms with a status text, custom buttons, or any layout that does not fit the default modal-action row.

✅ What it gives you
  • Footer always pinned at the bottom of the modal-box
  • Content area scrolls on its own (smooth, internal)
  • Header stays at the top
  • No need for !important overrides
  • No need for modalBoxClassName="flex flex-col" boilerplate
⚠️ Precedence rules

When footerNode is set, these props are ignored:

agree, disagree, agreeColor, disagreeColor, agreeIcon, disagreeIcon, showAgree, showDisagree, showFooter, footerReverse, footerClassName, footerOptions, onAgree, onCancel

A console.warn is emitted in dev if any of them are passed alongside.

Standard mode (without footerNode) is unchanged: the existingshowFooter behaviour with sticky agree/disagree row still works exactly as before.
Before / After

❌ Before (manual recipe — 8 lines, 5 ! markers)

<Modal
  contentClassName ="!overflow-hidden !p-0 flex flex-col flex-1 min-h-0"
  modalBoxClassName="!overflow-hidden flex flex-col"
  showFooter={false}>
  <div className="flex-1 min-h-0 overflow-y-auto ...">
    {form fields}
  </div>
  <div className="shrink-0 flex border-t bg-base-100 ...">
    {status + cancel + save}
  </div>
</Modal>

✅ After — 1 prop, no overrides

<Modal
  title="Edit profile"
  footerNode={<FormFooter ... />}
>
  <form className="flex flex-col gap-4">
    {form fields}
  </form>
</Modal>
// modal-box auto: flex flex-col
// content auto:   flex-1 min-h-0 overflow-y-auto

Form in Modal

useModal Hook Usage

const { modalRef, open, close, toggle, isOpen } = useModal() ;
// With callbacks
const { modalRef, open } = useModal({
  onOpen: () => console.log('Opened'),
  onClose: () => console.log('Closed'),
}) ;
<Button onClick={ open }>Open</Button>
<Modal ref={ modalRef }>Content</Modal>

Stacked Modals (Nesting)

You can open multiple modals on top of each other. The browser handles the stacking order.

Toast over Modal

Click to verify that toasts appear above the modal backdrop. Test case: native <dialog> top layer vs ToastProvider popover.

Vertical
Horizontal
CurrentvAlign = bottomhAlign = end
Stress test : toast under stacked modals

These buttons fire a toast first, then open a modal a few hundred milliseconds later. Without the MutationObserver in the provider, the modal would steal the top of the top-layer stack and hide the toast. With it, the toast popover is re-promoted as soon as the new <dialog> opens.

Try every combination (3 × 3 = 9 positions): top / middle / bottom × start / center / end. Each toast should anchor at the chosen corner, edge or center — independently of where the modal sits.

CRUD Hooks Demo

useAddModal, useEditModal, useRemoveModal

Total Users
4
Developers
2
Designers
1
NameEmailPhoneRoleActions
AJ
Alice Johnson
alice@example.com555-0101Developer
BS
Bob Smith
bob@example.com555-0102Designer
CB
Charlie Brown
charlie@example.com555-0103Manager
DP
Diana Prince
diana@example.com555-0104Developer

useAddModal State

valid:

useEditModal State

hasChanges:
valid:

useRemoveModal State

ready: ✓

InputModal - Open on Focus Demo

Click on the input or use the button to open the modal

With openOnFocus

Click the input field or the button to open

#3b82f6

Without openOnFocus

Only the button opens the modal

Try clicking the input field - nothing happens. You must use the "Choose" button.

When to use openOnFocus?

✅ Good for:

  • Date pickers
  • Time pickers
  • Color pickers
  • Single-click selections
  • Read-only inputs

❌ Avoid for:

  • Complex forms
  • Multi-step selections
  • When typing is needed
  • File uploads
  • Editable inputs
// ✅ Pattern correct avec état temporaire
const [value, setValue] = useState(initial) ;
const [tempValue, setTempValue] = useState(initial) ;
<InputModal
  value={value}  // Affiche finale
  onModalOpen={() => setTempValue(value)}
  onAgree={() => setValue(tempValue)}
>
  <input value={tempValue} onChange={setTempValue} />
</InputModal>

Additional Options

Hide Action Button

Use showActionButton={false} to hide the button

Custom onFocus Handler

Combine with your own focus handler