<modal-element>

A custom element to create a modal, using the native <dialog> element under the hood.

Source code & documentation

Examples

Below are examples of different configurations and use cases of the <modal-element> component.

Table of contents
  1. Default configuration
  2. Static backdrop
  3. Without header
  4. Without title
  5. Without default close button
  6. Fullscreen modal
  7. Without footer
  8. Without animations
  9. Scrolling long content
  10. Custom styling
  11. Truncate long title
  12. Custom backdrop
  13. Custom transition (enter/leave)
  14. Prevent modal from closing
  15. Modal placement
  16. Preserve body overflow
  17. Nested modals
  18. Interactive demo
Note: Open the browser's console to see the events fired by the modal element.

Default configuration

This is a modal with the default configurations. It will close by clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Default configuration

This is a modal with the default configuration.

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element>
  <h2 slot="header">Default configuration</h2>
  <p>This is a modal with the default configuration.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Static backdrop

By adding the static-backdrop attribute, the modal will not close by clicking on the backdrop. The modal will still close by clicking the "X" button, or pressing the Esc key.

Static backdrop

This is a modal with static backdrop. Clicking on the backdrop will not close the modal.

Close the modal by either clicking the "X" button, or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element static-backdrop>
  <h2 slot="header">Static backdrop</h2>
  <p>This is a modal with static backdrop.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Without header

By adding the no-header attribute, the modal will not have a header. Note that this will also remove the default close button, therefore you need to provide a way to close the modal.

Without header

This is a modal without header.

Close the modal by either clicking on the backdrop, or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element no-header>
  <h2 slot="header">Without header</h2>
  <p>This is a modal without header.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Without title

If you don't need a title, you can omit it by not providing any content for the header slot.

This is a modal without title. It will still have a close button though.

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element>
  <p>This is a modal without title. It will still have a close button though.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Without default close button

By adding the no-close-button attribute, the modal will not have a default close button.

Without close button

This is a modal without default close button.

Close the modal by either clicking on the backdrop, or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element no-close-button>
  <h2 slot="header">Without close button</h2>
  <p>This is a modal without default close button.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Fullscreen modal

By adding the fullscreen attribute, the modal will be displayed in fullscreen mode.

Fullscreen modal

This is a fullscreen modal.

Note, that --me-width and --me-height CSS Custom Properties are overriden when the modal is in fullscreen mode.

Close the modal by either clicking the "X" button, or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element fullscreen>
  <h2 slot="header">Fullscreen modal</h2>
  <p>This is a fullscreen modal.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

If you don't need a footer, you can omit it by not providing any content for the footer slot.

Without footer

This is a modal without footer.

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element>
  <h2 slot="header">Without footer</h2>
  <p>This is a modal without footer.</p>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Without animations

By adding the no-animations attribute, the modal will not use animations on open and close. It also removes the backdrop click animation if the static-backdrop attribute is present.

Without animations

This is a modal that does not use animations on open and close or backdrop click.

Close the modal by either clicking the "X" button, or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element no-animations static-backdrop>
  <h2 slot="header">Without animations</h2>
  <p>This is a modal that does not use animations on open and close or backdrop click.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Scrolling long content

By design, the modal's height will not exceed that of the viewport. If the content is longer than the viewport, the body will scroll while the header and footer will remain fixed and visible.

Scrolling long content

This is a modal with really long content. The header and footer will remain fixed, while the body will scroll.

Scroll down and see for yourself! 👇

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<style>
  .long-content {
    min-height: 150vh; /* Really long body */
  }
</style>

<button type="button" id="open-modal-button">Open Modal</button>

<modal-element>
  <h2 slot="header">Scrolling long content</h2>
  <div class="long-content">
    <p>
      This is a modal with really long content.
      The header and footer will remain fixed, while the body will scroll.
    </p>
    <p>Scroll down and see for yourself! 👇</p>
  </div>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Custom styling

The modal can be styled and customized as you wish. The following example uses Water.css for styling.

Custom styling

This is a modal with custom styling.

You can style the various elements of the component using the available CSS Parts or by overriding the default CSS Custom Properties.

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<style>
  modal-element {
    --me-width: 35rem;
    --me-border-width: 1px;
    --me-border-color: var(--border);
    --me-border-radius: 0.375rem;
    --me-box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.25);
    --me-header-background-color: var(--background-alt);
    --me-body-background-color: var(--background-body);
    --me-footer-background-color: var(--background-alt);
    --me-backdrop-background: rgba(0, 0, 0, 0.7);
  }

  modal-element::part(header) {
    border-bottom: 1px solid var(--border);
  }

  modal-element::part(footer) {
    border-top: 1px solid var(--border);
  }

  modal-element::part(close) {
    background-color: var(--button-base);
    border: 1px solid var(--border);
    border-radius: 0.25rem;
    color: var(--text-bright);
    transition: background-color 0.2s ease;
  }

  modal-element::part(close):hover {
    background-color: var(--button-hover);
  }
</style>

<button type="button" id="open-modal-button">Open Modal</button>

<modal-element>
  <h2 slot="header">Custom styling</h2>
  <p>This is a modal with custom styling.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Truncate long title

By default, the modal's title will not be truncated if it's too long. You can override this behavior by using some CSS.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Beatae quis eveniet dicta saepe rem impedit odio quisquam ducimus eaque animi laborum quod, iusto autem, nobis cumque, voluptate repellat quo ab?

This is a modal with a really long title.

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<style>
  modal-element::part(title) {
    /* https://dfmcphee.com/flex-items-and-min-width-0 */
    min-width: 0;
  }

  modal-element [slot="header"] {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
</style>

<button type="button" id="open-modal-button">Open Modal</button>

<modal-element>
  <h2 slot="header">Lorem ipsum dolor sit amet consectetur adipisicing elit. Beatae quis eveniet dicta saepe rem impedit odio quisquam ducimus eaque animi laborum quod, iusto autem, nobis cumque, voluptate repellat quo ab?</h2>
  <p>This is a modal with a really long title.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Custom backdrop

You can customize the backdrop using CSS Custom Properties.

It's me, Mario!

...and I'm in a custom backdrop!

If you are wondering how I got here, I used the --me-backdrop-background CSS Custom Property.

If you don't see me though, it's probably because of this bug but fortunately it's already being worked on.

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<style>
  --me-backdrop-background: #0060ad url('./assets/super-mario.jpg') no-repeat bottom center / 90%;
</style>

<button type="button" id="open-modal-button">Open Modal</button>

<modal-element>
  <h2 slot="header">It's me, Mario!</h2>
  <p>...and I'm in a custom backdrop!</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Custom transition (enter/leave)

You can customize the modal's enter/leave transition using CSS. Note, that browser support is limited to those that suport the @starting-style CSS at-rule. Also, the prefers-reduced-motion media query is respected by default and animations are disabled when the user has enabled the "reduce motion" option in their OS settings.

Custom transition

This is a modal with custom transitions.

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<style>
  @media (prefers-reduced-motion: no-preference) {
    modal-element:not([no-animations])::part(base) {
      transition:
        transform 0.35s,
        opacity 0.35s,
        display 0.35s allow-discrete,
        overlay 0.35s allow-discrete;
    }

    /* IS-OPEN STATE */
    modal-element[open]:not([no-animations])::part(base) {
      transform: translateY(0);
      opacity: 1;
    }

    /* EXIT STATE */
    modal-element:not([no-animations])::part(base) {
      transform: translateY(-2rem);
      opacity: 0;
    }

    /* BEFORE-OPEN STATE */
    @starting-style {
      modal-element[open]:not([no-animations])::part(base) {
        transform: translateY(-2rem);
        opacity: 0;
      }
    }
  }
</style>

<button type="button" id="open-modal-button">Open Modal</button>

<modal-element>
  <h2 slot="header">Custom transition</h2>
  <p>This is a modal with custom transitions.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Prevent modal from closing

You can prevent the modal from closing by listening to the me-request-close event and calling event.preventDefault() in the event handler.

Prevent from closing

This modal will not close when clicking the "X" button, but it will close when clicking on the backdrop or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element>
  <h2 slot="header">Prevent from closing</h2>
  <p>
    This modal will not close when clicking the "X" button,
    but it will close when clicking on the backdrop
    or pressing the <kbd>Esc</kbd> key.
  </p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });

  modalElement.addEventListener('me-request-close', evt => {
    if (evt.detail.reason === 'close-button') {
      evt.preventDefault();
    }
  });
</script>

Modal placement

By default, the modal is centered on the screen both vertically and horizontally. You can change the placement of the modal by using the placement attribute.

Placement

Change the placement of the modal by selecting one of the options below.

Modal placement

This is a modal with custom placement.

placement="center"

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element placement="top-start">
  <h2 slot="header">Modal placement</h2>
  <p>This is a modal with custom placement.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<form name="modal-placement-form">
  <label for="placement-select">Placement</label>
  <select name="placement" id="placement-select">
    <option value="top-start">Top Start</option>
    <option value="top-center">Top Center</option>
    <option value="top-end">Top End</option>
    <option value="center-start">Center Start</option>
    <option value="center">Center</option>
    <option value="center-end">Center End</option>
    <option value="bottom-start">Bottom Start</option>
    <option value="bottom-center">Bottom Center</option>
    <option value="bottom-end">Bottom End</option>
  </select>

</form>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');
  const placementForm = document.forms['modal-placement-form'];

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });

  placementForm.addEventListener('change', evt => {
    modalElement.setAttribute('placement', evt.target.value);
  });
</script>

Preserve body overflow

By default, the modal will set the overflow property of the document's body to hidden when the modal is open. This is to prevent the body from scrolling when the modal is open. If you want to preserve the body overflow, you can add the preserve-overflow attribute to the modal.

Preserve body overflow

This is a modal that preserves the body overflow. Try scrolling the document while the modal is open.

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<button type="button" id="open-modal-button">Open Modal</button>

<modal-element preserve-overflow>
  <h2 slot="header">Preserve body overflow</h2>
  <p>This is a modal that preserves the body overflow. Try scrolling the document while the modal is open.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openModalButton = document.getElementById('open-modal-button');
  const modalElement = document.querySelector('modal-element');

  openModalButton.addEventListener('click', () => {
    modalElement.open = true;
  });
</script>

Nested modals

This example demonstrates how to open a modal on top of another modal. Keep in mind though that this is considered a bad practice in terms of user experience and should be avoided if possible.

First modal

This is a modal from which you can open another modal.

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Second modal

This is a modal that was opened on top of another modal.

Close the modal by either clicking the "X" button, clicking on the backdrop, or pressing the Esc key.

Source code
<button type="button" id="open-first-modal-button">Open Modal</button>

<modal-element id="first-modal">
  <h2 slot="header">First modal</h2>
  <p>This is a modal from which you can open another modal.</p>
  <div slot="footer">
    <button type="button" id="open-second-modal-button">Open another modal</button>
    <button type="button" data-me-close>Close</button>
  </div>
</modal-element>

<modal-element id="second-modal" placement="top-center" preserve-overflow>
  <h2 slot="header">Second modal</h2>
  <p>This is a modal that was opened on top of another modal.</p>
  <button slot="footer" type="button" data-me-close>Close</button>
</modal-element>

<script>
  const openFirstModalButton = document.getElementById('open-first-modal-button');
  const openSecondModalButton = document.getElementById('open-second-modal-button');
  const firstModalElement = document.getElementById('first-modal');
  const secondModalElement = document.getElementById('second-modal');

  openFirstModalButton.addEventListener('click', () => {
    firstModalElement.open = true;
  });

  openSecondModalButton.addEventListener('click', () => {
    secondModalElement.open = true;
  });
</script>

Interactive demo

The following example is an interactive demo of the component. You can play around with the various options and see how they affect the modal.

Interactive demo

This is an interactive demo of the component.

Attributes

Play around with the various attributes and see how they affect the modal.

For more info on attributes, check the relevant section in the documentation.

Prevent modal from closing

Play around with the different ways to prevent the modal from closing.

For more info on events, check the relevant section in the documentation.

Custom styling

For more info on styling, check the relevant section in the documentation.

License

Licensed under the The MIT License (MIT)