# How to Trigger a Bootstrap Modal in React Without npm or React-Bootstrap

> Trigger a Bootstrap modal in React without npm or React Bootstrap. Learn to use ReactDOM createPortal and the global Bootstrap API for seamless integration. Read more now.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: how-to-guide
- Published: 2026-02-20

---

**You can trigger a bootstrap modal react implementation by using ReactDOM.createPortal to render the modal markup outside the component tree and imperatively controlling it via the global bootstrap.Modal API loaded from a CDN.**

This approach leverages React’s portal system—implemented in the facebook/react repository—to mount DOM nodes outside the normal hierarchy while preserving React’s lifecycle management. By combining vanilla Bootstrap’s JavaScript behavior with React portals, you avoid adding npm dependencies like react-bootstrap while maintaining full modal functionality including backdrop handling, keyboard dismissal, and focus management.

## Why Use Bootstrap Modal React Without npm Packages

Bootstrap’s modal requires two specific ingredients to function: the HTML markup structure and the JavaScript behavior that toggles visibility. In standard HTML, you would include Bootstrap’s CSS and JS bundles, then instantiate `new bootstrap.Modal(element).show()`.

In React, the challenge is that modals must live outside the normal component tree to avoid CSS clipping and z-index conflicts from parent containers. The solution is to use **React portals**, which allow components to render into a different part of the DOM while maintaining their place in the React component hierarchy.

## The React Portal Solution for Bootstrap Modals

React implements portals through `ReactDOM.createPortal`, defined in [`packages/react-dom/src/shared/ReactDOM.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/shared/ReactDOM.js). This function forwards to the reconciler implementation in [`packages/react-reconciler/src/ReactPortal.js`](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactPortal.js), which creates a separate fiber tree for the portal’s children while maintaining event bubbling through the React tree.

By rendering your modal markup into `document.body` via a portal, you achieve a **self-contained dialog** that behaves exactly like vanilla Bootstrap, unconstrained by parent component CSS.

## Step-by-Step Implementation

### 1. Load Bootstrap Assets via CDN

Add the Bootstrap CSS and JavaScript bundles to your HTML template. This provides the global `bootstrap` namespace without requiring npm installation.

```html
<!-- public/index.html -->
<link
  href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
  rel="stylesheet"
/>
<script
  src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
  defer
></script>

```

### 2. Create the Portal Component

Build a reusable component that uses `ReactDOM.createPortal` to mount the modal outside the normal React tree. Store a reference to the modal DOM node for imperative control.

```tsx
// ModalPortal.tsx
import { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';

type Props = {
  children: React.ReactNode;
  isOpen: boolean;
  onClose: () => void;
};

export default function ModalPortal({ children, isOpen, onClose }: Props) {
  const modalRef = useRef<HTMLDivElement>(null);
  const bsModalRef = useRef<any>(null);

  useEffect(() => {
    if (modalRef.current && (window as any).bootstrap) {
      const { Modal } = (window as any).bootstrap;
      
      bsModalRef.current = new Modal(modalRef.current, {
        backdrop: true,
        keyboard: true,
      });

      // Sync React state when Bootstrap hides the modal
      modalRef.current.addEventListener('hidden.bs.modal', onClose);
    }

    return () => {
      if (bsModalRef.current) {
        bsModalRef.current.dispose();
      }
    };
  }, []);

  useEffect(() => {
    if (bsModalRef.current) {
      isOpen ? bsModalRef.current.show() : bsModalRef.current.hide();
    }
  }, [isOpen]);

  return ReactDOM.createPortal(
    <div className="modal fade" tabIndex={-1} ref={modalRef}>
      <div className="modal-dialog">
        <div className="modal-content">{children}</div>
      </div>
    </div>,
    document.body
  );
}

```

### 3. Instantiate the Bootstrap Modal API

The `useEffect` hook initializes `new bootstrap.Modal(element)` only after the DOM node exists. This imperative instantiation connects React’s declarative rendering with Bootstrap’s JavaScript behavior, enabling features like backdrop clicking and keyboard dismissal.

### 4. Handle Cleanup and Events

Always dispose of the Bootstrap modal instance when the component unmounts to prevent memory leaks. Listen to Bootstrap’s `hidden.bs.modal` event to synchronize React’s state when the user dismisses the modal via backdrop click or ESC key.

## Complete Working Example

Here is a complete implementation showing how to use the portal component within an application:

```tsx
// App.tsx
import { useState } from 'react';
import ModalPortal from './ModalPortal';

export default function App() {
  const [isModalOpen, setModalOpen] = useState(false);

  return (
    <div className="container mt-5">
      <button 
        className="btn btn-primary" 
        onClick={() => setModalOpen(true)}
      >
        Launch Bootstrap Modal
      </button>

      <ModalPortal
        isOpen={isModalOpen}
        onClose={() => setModalOpen(false)}
      >
        <div className="modal-header">
          <h5 className="modal-title">Bootstrap Modal in React</h5>
          <button 
            type="button" 
            className="btn-close" 
            onClick={() => setModalOpen(false)}
          />
        </div>
        <div className="modal-body">
          <p>This modal uses React portals and vanilla Bootstrap JavaScript without any npm packages.</p>
        </div>
        <div className="modal-footer">
          <button 
            className="btn btn-secondary" 
            onClick={() => setModalOpen(false)}
          >
            Close
          </button>
        </div>
      </ModalPortal>
    </div>
  );
}

```

## How React Portals Work Under the Hood

According to the facebook/react source code, portals are implemented through two key files:

- **[`packages/react-dom/src/shared/ReactDOM.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/shared/ReactDOM.js)** – This file exports the `createPortal` function that developers call. It serves as the entry point for portal creation in the ReactDOM package.

- **[`packages/react-reconciler/src/ReactPortal.js`](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactPortal.js)** – This file contains the reconciler implementation that handles portal logic. It creates a separate fiber tree for the portal’s children while maintaining event bubbling through the original React component tree, ensuring that events from the modal (like button clicks) still propagate to parent components in React’s virtual DOM.

- **[`packages/react-dom/src/client/ReactDOMClient.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/client/ReactDOMClient.js)** – This file demonstrates how React mounts into DOM containers, which is relevant for understanding how the portal target (such as `document.body`) integrates with the overall render tree.

This architecture explains why portals are the ideal solution for bootstrap modal react implementations: they render DOM nodes outside the normal component hierarchy (avoiding CSS constraints) while preserving React’s event system and lifecycle management.

## Summary

- **Use ReactDOM.createPortal** to render modal markup into `document.body`, avoiding CSS clipping from parent containers.
- **Load Bootstrap via CDN** to access the global `bootstrap.Modal` API without npm packages.
- **Instantiate imperatively** using `new bootstrap.Modal(element)` in a `useEffect` hook to connect React’s declarative rendering with Bootstrap’s JavaScript behavior.
- **Handle cleanup** by calling `dispose()` on the Bootstrap instance when the component unmounts to prevent memory leaks.
- **Synchronize state** by listening to Bootstrap’s `hidden.bs.modal` event to update React state when users dismiss the modal via backdrop or keyboard.

## Frequently Asked Questions

### Can I use bootstrap modal react without installing any npm packages?

Yes. By loading Bootstrap’s CSS and JavaScript from a CDN in your HTML template, you gain access to the global `bootstrap` object. You can then use React’s portal system to render modal markup and control it via the vanilla Bootstrap Modal API, eliminating the need for react-bootstrap or any other npm dependency.

### Why use ReactDOM.createPortal instead of rendering the modal inline?

Rendering a modal inline within your component tree often causes CSS issues such as `overflow: hidden` clipping or z-index stacking problems from parent containers. `ReactDOM.createPortal` renders the DOM nodes into a different container (typically `document.body`) while keeping the component in the React tree, ensuring events bubble correctly and the modal displays above all other content.

### How do I handle the Bootstrap modal events in React?

Bootstrap fires custom events like `shown.bs.modal` and `hidden.bs.modal` when the modal opens or closes. In your `useEffect` hook, add event listeners to the modal DOM element to synchronize React state. For example, listen for `hidden.bs.modal` to update your `isOpen` state when the user clicks the backdrop or presses ESC, ensuring React and Bootstrap stay in sync.

### Is this approach compatible with Bootstrap 4 and Bootstrap 5?

Yes, this approach works with both Bootstrap 4 and Bootstrap 5. The main difference is the global namespace: Bootstrap 5 uses `bootstrap.Modal` while Bootstrap 4 uses `jQuery` or the global `Modal` depending on your build. When using the CDN bundle as shown in the examples, Bootstrap 5 attaches to `window.bootstrap`, making the imperative instantiation straightforward for modern React applications.