> ## Documentation Index
> Fetch the complete documentation index at: https://learn.nexudus.com/llms.txt
> Use this file to discover all available pages before exploring further.

# useModal

> Global modal system with promise-based API and a controller for fully controlled modals.

# useModal

`useModal` provides a global, promise-based modal API and a controller for building controlled modals. It is backed by a `ModalProvider` that renders two Bootstrap modals: a regular modal and a confirm modal.

Source: `src/states/useModalContext.tsx`

## Provider

Wrap your app with `ModalProvider`. The default `LocationContext` already includes it, so you usually don’t need to add it yourself.

```tsx theme={null}
// src/states/LocationContext.tsx
<LocationByRouteProvider>
  <AuthProvider>
    <LocationSettingsProvider>
      <CSSLoaderComponent url="/api/styles/styles.css">
        <ModalProvider>{children}</ModalProvider>
      </CSSLoaderComponent>
    </LocationSettingsProvider>
  </AuthProvider>
</LocationByRouteProvider>
```

## API

```ts theme={null}
type ModalContent = ReactNode | { render: (controller: ModalController) => ReactNode }

type ModalOptions = Partial<{
  title: string
  confirmText: string
  cancelText: string
  size: 'sm' | 'lg' | 'xl'
  bodyClassName: string
  confirmButtonClass: string // e.g. 'primary', 'danger'
  confirmHandler: () => () => Promise<void>
}>

type ModalContext = {
  showModal: (content: ModalContent, options?: ModalOptions) => Promise<void>
  hideModal: () => void
  confirm: (message: ModalContent, options?: ModalOptions) => Promise<boolean>
  hideAllModals: () => void
  setModalsVisible: (visible: boolean) => void
}

type ModalController = {
  setTitle(text: string): void
  setConfirmButtonText(text: string): void
  setCancelButtonText(text: string): void
  setConfirmButtonLoading(loading: boolean): void
  setCancelButtonLoading(loading: boolean): void
  setConfirmButtonDisabled(disabled: boolean): void
  setCancelButtonDisabled(disabled: boolean): void
  setOnConfirm(fn: () => Promise<void>): void
  close(): void
}
```

## Quick use

```tsx theme={null}
import { useModal } from '@/states/useModalContext'

const Example = () => {
  const { showModal, confirm } = useModal()

  const openInfo = async () => {
    await showModal(<div>Hello world</div>, { title: 'Info' })
  }

  const ask = async () => {
    const ok = await confirm('Delete this item?', { title: 'Confirm', confirmText: 'Delete', confirmButtonClass: 'danger' })
    if (ok) {
      // proceed
    }
  }

  return (
    <>
      <button onClick={openInfo}>Open modal</button>
      <button onClick={ask}>Confirm</button>
    </>
  )
}
```

## Controlled modals (advanced)

Controlled modals let the modal content drive button text, loading state, and confirm behavior using the `ModalController`. To opt in, pass a content object with a `render(controller)` function. Inside, call controller setters to update button state or register an async confirm handler.

```tsx theme={null}
const { showModal } = useModal()

const openControlled = async () => {
  await showModal(
    {
      render: (controller) => (
        <form
          onSubmit={(e) => {
            e.preventDefault()
            controller.setOnConfirm(async () => {
              controller.setConfirmButtonLoading(true)
              try {
                // await API call
              } finally {
                controller.setConfirmButtonLoading(false)
              }
            })
          }}>
          <input placeholder="Name" />
        </form>
      ),
    },
    { title: 'Create', confirmText: 'Save' },
  )
}
```

How it works:

* When you call `showModal`, the provider renders the regular modal.
* If your content is `{ render(controller) }`, the provider calls your render function and injects a `ModalController`.
* You then call `controller.setOnConfirm(fn)` to register the confirm handler; when the user clicks the confirm button (or closes), the provider executes it with error handling:
  * Shows a LoadingSpinner while the promise is pending (`setConfirmButtonLoading(true)`)
  * Displays an error `Alert` inside the modal if your handler throws
  * Closes the modal and resolves the promise when the handler completes successfully

Notes:

* You can dynamically change title and button labels using `setTitle`, `setConfirmButtonText`, and `setCancelButtonText`.
* Disable or enable buttons via `setConfirmButtonDisabled` and `setCancelButtonDisabled`.
* Call `controller.close()` to programmatically close the active modal.

## Confirm modals with custom content

`confirm(content, options)` behaves similarly, but displays a Cancel + Confirm footer. You can also control it via the same controller pattern:

```tsx theme={null}
const ok = await confirm(
  {
    render: (controller) => (
      <div>
        Are you absolutely sure?
        {/* Example: attach extra async work to the confirm action */}
        {controller.setOnConfirm(async () => {
          /* async side-effects */
        })}
      </div>
    ),
  },
  { title: 'Danger zone', confirmText: 'Yes, do it', confirmButtonClass: 'danger' },
)
```

## Error handling and loading

* The provider wraps your confirm handler in a try/catch and surfaces errors via a danger `Alert` in the modal body.
* `LoadingSpinner` is shown automatically on the confirm button while your handler is pending.

## Accessibility and visibility

* `setModalsVisible(false)` hides the modal UI but keeps state. Useful for embedded flows where the host temporarily forbids overlays.
* Modals are React-Bootstrap `Modal` components; ensure surrounding UI remains keyboard navigable.

## See also

* [LocationContext](/developers/state/location-context) – Includes the `ModalProvider` in the app provider stack
