DocumentationGuidesGetting Started

Introduction to overlay-kit

overlay-kit is a library for declaratively managing overlays like modals, popups, and dialogs in React.

You can efficiently implement overlays without complex state management or unnecessary event handling.

Key Features

Declarative API

  • Define overlay UI and behavior declaratively with simple code.
  • Reduce state management and event handling code for easier development.

Promise-based API

  • Return overlay results as Promises.
  • Handle user input results or combine with asynchronous logic.

Extensible Components

  • Freely define your desired overlay components.
  • Use with various UI libraries.

Using overlay-kit

Let’s learn how to use overlay-kit with a simple example.

Overlays

Any component that renders above the screen can be an overlay.

Let’s create a simple confirmation dialog using MUI’s Dialog component.

confirm-dialog.tsx
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
 
export function ConfirmDialog({ isOpen, close }) {
  return (
    <Dialog open={isOpen} onClose={close}>
      <DialogTitle>Are you sure you want to continue?</DialogTitle>
      <DialogActions>
        <Button onClick={close}>No</Button>
        <Button onClick={close}>Yes</Button>
      </DialogActions>
    </Dialog>
  );
}

Setting Overlay Position

Overlays should be rendered in an appropriate position on the screen. Generally, it’s best to render at the app root.

You can specify the rendering position using OverlayProvider.

Example.tsx
import { OverlayProvider } from 'overlay-kit';
import { createRoot } from 'react-dom/client';
 
export const Example = () => {
  return (
    <OverlayProvider>
      <App />
    </OverlayProvider>
  );
};
 
const root = createRoot(document.getElementById('root'));
root.render(<Example />);

Opening Overlays

You can easily open and close overlays using overlay.open. Let’s open the ConfirmDialog we defined above.


import { OverlayProvider, overlay } from 'overlay-kit';
import Button from '@mui/material/Button';
import { ConfirmDialog } from './confirm-dialog';

function App() {
  return (
    <Button
      onClick={() => {
        overlay.open(({ isOpen, close }) => {
          return <ConfirmDialog isOpen={isOpen} close={close} />;
        });
      }}
    >
      Open Confirm Dialog
    </Button>
  );
}

export const Example = () => {
  return (
    <OverlayProvider>
      <App />
    </OverlayProvider>
  );
};

Opening Asynchronous Overlays

overlay.openAsync returns results as a Promise. Let’s modify it to return a result when the user selects “Yes” in the dialog.

confirm-dialog.tsx
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
 
export function ConfirmDialog({ isOpen, close, confirm }) {
  return (
    <Dialog open={isOpen} onClose={close}>
      <DialogTitle>Are you sure you want to continue?</DialogTitle>
      <DialogActions>
        <Button onClick={close}>No</Button>
        <Button onClick={confirm}>Yes</Button>
      </DialogActions>
    </Dialog>
  );
}

Let’s handle asynchronous operations using overlay.openAsync.


import { useState } from 'react';
import { OverlayProvider, overlay } from 'overlay-kit';
import Button from '@mui/material/Button';
import { ConfirmDialog } from './confirm-dialog';

function App() {
  const [result, setResult] = useState<boolean>();

  return (
    <>
      <p>result: {result ? 'Y' : 'N'}</p>
      <Button
        onClick={async () => {
          const result = await overlay.openAsync(({ isOpen, close }) => {
            return <ConfirmDialog isOpen={isOpen} close={() => close(false)} confirm={() => close(true)} />;
          });

          setResult(result);
        }}
      >
        Open Confirm Dialog
      </Button>
    </>
  );
}

export const Example = () => {
  return (
    <OverlayProvider>
      <App />
    </OverlayProvider>
  );
};

Closing Overlays

overlay.open and overlay.openAsync provide close and unmount functions as arguments to the callback function.

These two functions both close the overlay, but they work differently.

close and unmount difference

Let’s see the difference with an example.


import { OverlayProvider, overlay } from 'overlay-kit';
import Button from '@mui/material/Button';
import { ConfirmDialog } from './confirm-dialog';

function App() {
  return (
    <>
      <Button
        onClick={() => {
          overlay.open(
            ({ isOpen, close }) => {
              return <ConfirmDialog isOpen={isOpen} close={close} />;
            },
            { overlayId: 'close-overlay' }
          );
        }}
      >
        Close overlay using close
      </Button>
      <Button
        onClick={() => {
          overlay.open(
            ({ isOpen, unmount }) => {
              return <ConfirmDialog isOpen={isOpen} close={unmount} />;
            },
            { overlayId: 'unmount-overlay' }
          );
        }}
      >
        Close overlay using unmount
      </Button>
    </>
  );
}

export const Example = () => {
  return (
    <OverlayProvider>
      <App />
    </OverlayProvider>
  );
};
  1. Using close

    • The close animation runs, and the state (count) is retained.
    • When reopened, the previous state is restored.
  2. Using unmount

    • It’s immediately removed from memory, skipping the close animation.
    • When reopened, the state is reset.

The reason for this difference is that close retains the overlay information in memory so that the close animation can be shown. If a close animation is needed, close should be used first, then unmount should be used.

Releasing Overlay Memory

close retains the overlay information in memory after the close animation. For this reason, if many overlays are opened and closed at the same time, memory leaks may occur.

To avoid this, you should call unmount after the close animation ends to release memory. If there’s a callback function that runs after the overlay ends, such as the onExit prop, you can pass the unmount function to release memory.

MUI doesn’t provide the onExit prop, but you can use React’s useEffect cleanup function to automatically release memory when the overlay closes.

confirm-dialog.tsx
import { useEffect } from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
 
export function ConfirmDialog({ isOpen, close, onExit }) {
  useEffect(() => {
    return () => onExit();
  }, []);
 
  return (
    <Dialog open={isOpen} onClose={close}>
      <DialogTitle>Are you sure you want to continue?</DialogTitle>
      <DialogActions>
        <Button onClick={close}>No</Button>
        <Button onClick={close}>Yes</Button>
      </DialogActions>
    </Dialog>
  );
}

import { OverlayProvider, overlay } from 'overlay-kit';
import Button from '@mui/material/Button';
import { ConfirmDialog } from './confirm-dialog';

function App() {
  return (
    <>
      <Button
        onClick={() => {
          overlay.open(({ isOpen, close, unmount }) => {
            return <ConfirmDialog isOpen={isOpen} close={close} onExit={unmount} />;
          });
        }}
      >
        Open Confirm Dialog
      </Button>
    </>
  );
}

export const Example = () => {
  return (
    <OverlayProvider>
      <App />
    </OverlayProvider>
  );
};