---
title: Backdrop
description: A fullscreen overlay component with smooth transitions and Portal rendering for modals, dialogs, and overlays.
---

```tsx
'use client';

import { useState } from 'react';
import { Backdrop } from '@/ui/Backdrop';
import { Button } from '@/ui/buttons/Button';

export function BackdropDemo() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <>
      <Button onClick={() => setIsVisible(true)}>Open Backdrop</Button>
      <Backdrop
        isVisible={isVisible}
        onClose={() => setIsVisible(false)}
        className="flex items-center justify-center"
      >
        <div className="bg-white rounded-lg p-6 shadow-xl max-w-md">
          <h2 className="text-xl font-semibold mb-2">Modal Content</h2>
          <p className="text-gray-600 mb-4">
            This content is displayed on top of the backdrop. Click outside to close.
          </p>
          <Button onClick={() => setIsVisible(false)}>Close</Button>
        </div>
      </Backdrop>
    </>
  );
}
```

## Installation

Using the CLI:

```bash
npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/backdrop.json
```

Or manually, copy the following code into `/ui/backdrop.tsx`:

```tsx
import {
  type ComponentProps,
  type FunctionComponent,
  type MouseEvent,
  type PropsWithChildren,
  useEffect,
} from 'react';
import clsx from 'clsx';
import { useTransitionState } from 'react-transition-state';
import { Portal } from './Portal';

export interface BackdropProps extends ComponentProps<'div'> {
  /**
   * On close
   * @param event
   */
  onClose?: (event: MouseEvent<HTMLButtonElement>) => void;
  /**
   * Is visible
   */
  isVisible?: boolean;
  /**
   * Sweep Mode™
   * (makes it appear from the sides instead of from everywhere)
   */
  sweep?: boolean;
  /**
   * Target ID for the Portal component
   */
  target?: ComponentProps<typeof Portal>['target'];
  /**
   * Whether backdrop click should trigger onClose
   * @default true
   */
  dismissable?: boolean;
}

export const Backdrop: FunctionComponent<PropsWithChildren<BackdropProps>> = ({
  children,
  className = 'flex justify-center',
  onClose,
  isVisible,
  target = 'backdrop',
  sweep = false,
  dismissable = true,
  ...attrs
}) => {
  const [state, toggle] = useTransitionState({
    timeout: { enter: 0, exit: 500 },
    mountOnEnter: true,
    unmountOnExit: true,
    preEnter: true,
  });

  const { status, isMounted } = state;

  useEffect(() => {
    toggle(isVisible);
  }, [isVisible, toggle]);

  useEffect(() => {
    if (!isMounted || !dismissable || !onClose) return;

    const handleEscape = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        onClose(event as unknown as MouseEvent<HTMLButtonElement>);
      }
    };

    document.addEventListener('keydown', handleEscape);
    return () => document.removeEventListener('keydown', handleEscape);
  }, [isMounted, dismissable, onClose]);

  const animated = status === 'preEnter' || status === 'exiting';

  return (
    <Portal target={target}>
      {isMounted && (
        <div
          {...attrs}
          role="presentation"
          className={clsx(className, 'fixed inset-0 isolate items-center')}
        >
          <button
            aria-hidden="true"
            className={clsx(
              'absolute inset-0 -z-1 transition-all duration-500 outline-none focus-visible:ring-4 focus-visible:ring-black focus-visible:ring-offset-2',
              {
                'bg-white/80 backdrop-blur': !animated,
                'bg-white/0 backdrop-blur-none': animated,
                '-translate-x-full': sweep && (status === 'preEnter' || status === 'entering'),
                'translate-x-full': sweep && status === 'exiting',
              },
            )}
            onClick={dismissable ? onClose : undefined}
            type="button"
          />
          {children}
        </div>
      )}
    </Portal>
  );
};
```

## Usage

```tsx showLineNumbers
'use client';

import { useState } from 'react';
import { Backdrop } from '@/docs/lib/ui/backdrop';
import { Button } from '@/docs/lib/ui/Button';

export function Component() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <>
      <Button onClick={() => setIsVisible(true)}>Open Modal</Button>
      <Backdrop
        isVisible={isVisible}
        onClose={() => setIsVisible(false)}
        className="flex items-center justify-center"
      >
        <div className="bg-white rounded-lg p-6 shadow-xl">
          <h2 className="text-xl font-semibold mb-2">Modal Title</h2>
          <p className="mb-4">Your modal content goes here.</p>
          <Button onClick={() => setIsVisible(false)}>Close</Button>
        </div>
      </Backdrop>
    </>
  );
}
```

## API Reference

### Backdrop

| Prop        | Type                                             | Default                 | Description                                                                                                                                           |
| ----------- | ------------------------------------------------ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `isVisible` | `boolean`                                        | `undefined`             | Controls the visibility of the backdrop. When `true`, the backdrop appears with a smooth fade-in transition. When `false`, it fades out and unmounts. |
| `onClose`   | `(event: MouseEvent<HTMLButtonElement>) => void` | `undefined`             | Callback function triggered when the backdrop overlay is clicked. Typically used to close the modal or dialog.                                        |
| `sweep`     | `boolean`                                        | `false`                 | Enables "Sweep Mode" which makes the backdrop appear from the sides with a horizontal slide animation instead of a fade effect.                       |
| `target`    | `string`                                         | `"backdrop"`            | The target ID for the Portal component. The backdrop will be rendered into the DOM element with this ID.                                              |
| `dismissable` | `boolean`                                        | `true`                  | When `false`, prevents the backdrop click and ESC key from triggering `onClose`. Useful for modal dialogs that require explicit user action.         |
| `className` | `string`                                         | `"flex justify-center"` | Additional CSS classes for the backdrop container. The default centers content horizontally.                                                          |
| `children`  | `ReactNode`                                      | `undefined`             | The content to display on top of the backdrop, typically a modal, dialog, or other overlay content.                                                   |

The Backdrop component also accepts all standard HTML `div` attributes through `ComponentProps<"div">`.

## Notes

- The Backdrop uses the Portal component to render outside the normal DOM hierarchy, preventing z-index and overflow issues.
- Transitions are powered by `react-transition-state` with a 500ms exit duration for smooth animations.
- The backdrop has a semi-transparent white background (`bg-white/80`) with a blur effect (`backdrop-blur`) when visible.
- The component handles mount/unmount logic automatically based on the `isVisible` prop, with `mountOnEnter` and `unmountOnExit` behavior.
- The overlay button has `aria-hidden="true"` since it's a decorative click target, and the wrapper has `role="presentation"` for accessibility.
- **Dismissable Control**: When `dismissable={false}`, backdrop clicks and ESC key presses do not trigger `onClose`. This is useful for critical dialogs that require explicit user confirmation before closing.
- When sweep mode is enabled, the backdrop slides in from the left and slides out to the right with a full horizontal translation.
- The component uses `isolate` and `-z-1` positioning to ensure proper layering of the backdrop and content.
