# Why Your Bootstrap 5 Modal Is Not Displaying: Debugging Tailwind CSS Compile-Time Generation

> Bootstrap 5 modal not displaying with Tailwind CSS? Discover how compile-time CSS generation issues prevent modal utilities from rendering. Fix your hidden modals now.

- Repository: [Tailwind Labs/tailwindcss](https://github.com/tailwindlabs/tailwindcss)
- Tags: debugging-guide
- Published: 2026-02-16

---

**If your Bootstrap 5 modal fails to appear when the button is clicked, and you're using Tailwind CSS for styling, the issue likely stems from missing modal utilities in Tailwind's compile-time CSS generation pipeline.**

When migrating from Bootstrap 5 to Tailwind CSS, developers often expect modal components to work immediately after including JavaScript. However, Tailwind operates as a **compile-time CSS generator**, not a runtime JavaScript library like Bootstrap. If your Bootstrap 5 modal is not displaying when clicked, the root cause often traces back to Tailwind's internal pipeline—from AST parsing in [`src/index.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/index.ts) through candidate compilation in [`src/compile.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/compile.ts)—where missing utilities or unregistered variants prevent the modal CSS from ever reaching your browser.

## How Tailwind's Compile-Time Pipeline Affects Modal Display

Tailwind CSS walks your source files, registers **variants**, **utilities**, and **theme values**, then generates the output CSS that your page finally receives. If the compiled CSS never contains the required `.modal-*` rules, the browser cannot display the modal, regardless of any JavaScript you attach to the button.

Below is a high-level walk-through of Tailwind's core pipeline, showing where the CSS for a component such as a modal would be generated (or omitted) and how you can verify each step.

| Step | What Tailwind Does | Relevant Source |
|------|-------------------|-----------------|
| **1. Parse the input CSS** – The `compile` function parses the raw stylesheet into an AST. | `let ast = CSS.parse(css, { from: opts.from })` | [[`src/index.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/index.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/index.ts#L29-L31) |
| **2. Build the design system** – Constructs a `DesignSystem` object that holds the theme, utilities, and variant registries. | `let designSystem = buildDesignSystem(theme, utilitiesNode?.src)` | [[`src/index.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/index.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/index.ts#L98-L100) |
| **3. Register built-in utilities** – `createUtilities` registers every core utility like `bg-…`, `flex`, **and** the `@utility`-based modal helpers (if you added any). | `let utilities = createUtilities(theme)` | [[`src/design-system.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/design-system.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/design-system.ts#L74-L75) |
| **4. Register built-in variants** – `createVariants` registers pseudo-class variants (`hover`, `focus`, …) and compound variants (`group`, `peer`, `aria`). | `let variants = createVariants(theme)` | [[`src/design-system.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/design-system.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/design-system.ts#L75-L76) |
| **5. Parse each candidate** – When you later call `compileCandidates`, Tailwind tokenises class names (`modal-open`, `modal-enter`, …) into **candidates**. If a candidate does not match any registered utility, it is marked *invalid*. | `parseCandidate(candidate, designSystem)` inside `compiledAstNodes` | [[`src/design-system.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/design-system.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/design-system.ts#L78-L80) |
| **6. Generate the CSS AST for a candidate** – `compileAstNodes` creates the actual rule nodes for a valid candidate, applying any needed variants. | `compileAstNodes(candidate, designSystem, flags)` | [[`src/design-system.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/design-system.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/design-system.ts#L84-L86) |
| **7. Apply polyfills & optimisations** – The final AST is run through `optimizeAst`, which removes dead rules and merges selectors. If the modal utility never made it past step 5, there will be nothing to optimise. | `optimizeAst(ast, designSystem, opts.polyfills)` | [[`src/index.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/index.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/index.ts#L76-L78) |
| **8. Emit final CSS** – `toCss` serialises the AST back to a stylesheet that the browser loads. | `return toCss(newAst, !!opts.from)` | [[`src/index.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/index.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/index.ts#L43-L45) |

## Why a Bootstrap 5 Modal Might Not Appear in Tailwind Projects

When integrating Bootstrap 5's modal JavaScript with Tailwind CSS styling, or when recreating modal behavior using Tailwind utilities, several compile-time failures can prevent the modal from displaying:

1. **Utility Not Defined** – Tailwind's core does not ship a `modal` utility. If you are trying to use something like `modal-open` without first defining it (via `@utility` or a plugin), step 5 will treat the class as *invalid*, so no CSS is emitted.

2. **Custom Variant Mis-registered** – If you created a custom variant (e.g., `modal`) but missed the registration call in [`variants.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/variants.ts), the compiler will not apply the selector, resulting again in missing CSS. See the registration flow in [`variants.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/variants.ts) where each static variant is added via `variants.static`. | [[`src/variants.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/variants.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/variants.ts#L54-L66) |

3. **Theme Value Missing** – Many modal utilities depend on CSS custom properties (e.g., `--tw-modal-bg`). If those variables are never defined in an `@theme` block, `Theme.add` will never store them, and the generated rule will reference an undefined var, leading to a no-op in the browser. | [[`src/theme.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/theme.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/theme.ts#L60-L86) |

4. **`@tailwind utilities` Not Inserted** – If the source file never contains `@tailwind utilities`, the compiler never replaces that placeholder with the generated utilities (step 7). Verify that the placeholder exists and isn't stripped by a prior plugin. | [[`src/index.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/index.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/index.ts#L73-L80) |

5. **Build Caching** – Tailwind caches compiled candidates. If you added a new modal utility after an initial build, you must trigger a rebuild (e.g., by restarting the dev server) so the cache invalidates and picks up the new candidate. | [[`src/index.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/index.ts)](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/index.ts#L59-L66) |

## Quick Debug Checklist for Missing Modal CSS

| Checklist Item | How to Verify |
|----------------|---------------|
| **Utility Exists** | Search for `modal` in [`src/utilities.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/utilities.ts). If missing, add it with `@utility` or a plugin. |
| **Variant Registration** | Look for `variants.static('modal', …)` or a custom-variant registration in [`src/variants.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/variants.ts). |
| **Theme Variables** | Inspect any `@theme` blocks or the generated `:root` output (view the compiled CSS) to ensure `--tw-modal-*` variables are present. |
| **`@tailwind utilities` Placeholder** | Confirm your entry CSS contains `@tailwind utilities` and that it is not removed by a preceding plugin. |
| **Cache Invalidation** | Restart the Tailwind dev server or delete `node_modules/.cache/tailwindcss` (if present). |

## Code Examples: Implementing Modal Utilities in Tailwind

### Defining a Simple Modal Utility

If you are migrating from Bootstrap 5 to Tailwind and need to recreate modal functionality, define the utility in your CSS:

```css
/* src/styles.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Define a custom modal utility */
@utility modal {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0,0,0,0.5);
}

/* Define an open-state variant */
@custom-variant modal-open & {
  opacity: 1;
  pointer-events: auto;
}

```

The `@utility modal` block registers the core modal container. The `@custom-variant modal-open` creates a variant that toggles visibility. Tailwind will now generate classes `.modal` and `.modal-open\:&` that you can toggle with JavaScript.

### Using the Modal in HTML

```html
<button id="openBtn" class="px-4 py-2 bg-blue-600 text-white">Open modal</button>

<div class="modal hidden modal-open:&">
  <div class="bg-white p-6 rounded max-w-sm">
    <h2 class="text-lg font-bold mb-4">Modal title</h2>
    <p class="mb-4">Modal content goes here.</p>
    <button id="closeBtn" class="px-3 py-1 bg-gray-200 rounded">Close</button>
  </div>
</div>

<script>
  const modal = document.querySelector('.modal')
  document.getElementById('openBtn').addEventListener('click', () => {
    modal.classList.remove('hidden')
    modal.classList.add('modal-open:&')
  })
  document.getElementById('closeBtn').addEventListener('click', () => {
    modal.classList.add('hidden')
    modal.classList.remove('modal-open:&')
  })
</script>

```

The `modal-open:&` variant is applied only when the class is present, toggling the visibility defined in the custom utility.

### Building Tailwind with a Custom Plugin (JS)

If you prefer to keep modal logic inside a JS plugin instead of `@utility` syntax:

```js
// tailwind-plugin.js
const plugin = require('tailwindcss/plugin')

module.exports = plugin(function({ addUtilities, addVariant }) {
  const modal = {
    '.modal': {
      position: 'fixed',
      inset: '0',
      display: 'flex',
      'align-items': 'center',
      'justify-content': 'center',
      'background-color': 'rgba(0,0,0,0.5)',
    },
  }

  addUtilities(modal)

  // Variant that adds the selector "&" after the class (e.g. .modal-open\:&)
  addVariant('modal-open', ({ modifySelectors, separator }) => {
    modifySelectors(({ className }) => {
      return `.modal-open${separator}${className}`
    })
  })
})

```

In [`tailwind.config.js`](https://github.com/tailwindlabs/tailwindcss/blob/main/tailwind.config.js):

```js
module.exports = {
  content: ['./src/**/*.html'],
  plugins: [require('./tailwind-plugin')],
}

```

Now you can use `<div class="modal modal-open:opacity-100">…</div>` and control the modal via the `modal-open:` prefix.

## Key Files in Tailwind's Compilation Pipeline

| File | Role | Link |
|------|------|------|
| [`src/index.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/index.ts) | Entry point; orchestrates parsing, design-system construction, and final build. | [View](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/index.ts) |
| [`src/design-system.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/design-system.ts) | Creates the central `DesignSystem` object (theme + utilities + variants). | [View](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/design-system.ts) |
| [`src/theme.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/theme.ts) | Manages CSS custom properties, prefixing, and variable resolution. | [View](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/theme.ts) |
| [`src/variants.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/variants.ts) | Registers static, functional, and compound variants; handles `@custom-variant`. | [View](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/variants.ts) |
| [`src/utilities.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/utilities.ts) | Generates the core utility map (e.g., `bg-`, `flex-`). | [View](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/utilities.ts) |
| [`src/candidate.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/candidate.ts) | Parses class-name candidates into utility + variant tokens. | [View](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/candidate.ts) |
| [`src/compile.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/compile.ts) | Turns parsed candidates into AST nodes (`compileCandidates`). | [View](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/compile.ts) |
| [`src/ast.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/ast.ts) | Low-level AST node constructors (`rule`, `atRule`, `decl`, …) and serializer. | [View](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/src/ast.ts) |
| `src/compat/*` | Back-compatibility layer for legacy Tailwind features. | [View](https://github.com/tailwindlabs/tailwindcss/tree/main/packages/tailwindcss/src/compat) |

These files together form the compile-time engine that decides whether a class like `modal-open` ends up as real CSS. If any of the steps above fail—missing utility, unregistered variant, or absent `@tailwind utilities` placeholder—the modal's CSS will never be generated, and the button click will appear to do nothing. Fixing the issue means ensuring the utility/variant is correctly defined, the placeholder exists, and the Tailwind build is refreshed.

## Summary

- **Tailwind CSS generates CSS at compile-time**, not runtime. If your Bootstrap 5 modal is not displaying, the required CSS may never have been emitted.
- **Missing utilities** in [`src/utilities.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/utilities.ts) or undefined `@utility` blocks prevent modal classes from being generated.
- **Unregistered variants** in [`src/variants.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/variants.ts) cause state-based classes (like `modal-open`) to be stripped during `compileCandidates`.
- **Absent placeholders** like `@tailwind utilities` in your entry CSS prevent the compiler from injecting generated rules.
- **Build caching** in [`src/index.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/index.ts) can hide newly added modal utilities until you restart the dev server or clear the cache.

## Frequently Asked Questions

### Why does my modal work in development but not production?

If your Bootstrap 5 modal displays in development but fails in production, **content scanning** is likely the culprit. Tailwind scans your `content` files for class names to generate. If your production build uses a different file glob or tree-shaking removes the modal markup, Tailwind never sees the `modal` or `modal-open` classes during `parseCandidate` in [`src/candidate.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/candidate.ts), so no CSS is generated. Ensure your [`tailwind.config.js`](https://github.com/tailwindlabs/tailwindcss/blob/main/tailwind.config.js) content array includes all files containing modal markup.

### Can I use Bootstrap's modal JavaScript with Tailwind CSS styling?

Yes, but you must **define the modal utilities** that Bootstrap's JavaScript expects. Bootstrap's modal JS toggles classes like `modal` and `show`, but Tailwind doesn't include these by default. Use `@utility` in your CSS or `addUtilities` in a plugin to define the `.modal` fixed positioning and `.show` visibility rules. Without these definitions, `createUtilities` in [`src/design-system.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/design-system.ts) won't register the classes, and `compileAstNodes` will skip them.

### How do I debug which utilities Tailwind is generating?

Run your build with the **Tailwind CLI** and inspect the output CSS file. Search for your modal class names (e.g., `.modal` or `.modal-open`). If they are missing, the issue occurs during candidate parsing in [`src/candidate.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/candidate.ts) or AST compilation in [`src/compile.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/compile.ts). Check that your modal classes appear in your HTML/JSX files that are included in the `content` configuration, and verify you have restarted the dev server to clear the cache managed in [`src/index.ts`](https://github.com/tailwindlabs/tailwindcss/blob/main/src/index.ts).