Skip to content

Modal13.10.5

Modals communicate information via a secondary window and allow the user to maintain the context of a particular task.


$ npm i @if-design-system/modal@13.10.5

Edit this section

Usage

Modals inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks.

Modals contain text and UI controls. They retain focus until dismissed or a required action has been taken. Use dialogs sparingly because they are interruptive.

Modals should never be obscured, either by other elements or the screen edge. Modals always retain focus until dismissed or a required action has been taken.

Modals

Modals may be dismissed in 3 ways:

  • Using the “x” in the upper right-hand corner of the Modal
  • Pressing the ESC key
  • Clicking / touching outside of the Modal area

Dialogs

Dialogs can only be dismissed by taking one of the actions presented.

Types

Modal
Dialog
Responsive modal
Responsive dialog
Wider modal or dialog is possible
Do not present too many actions for the user in a dialog
Prefer as few actions as possible
A dialog cannot be dismissed by pressing esc, clicking outside the dialog or by pressing "x"
A dialog can only be dissmissed by taking one of the actions presented
Edit this section

Anatomy

Header (optional)

Include a heading within the Modal that mirrors the action or button that was clicked by the user.

Body

The body content within a Modal should be as minimal as possible. Components that may be used in Modals include: Form fields, Text Area, Select, and Radio Buttons.

The footer area of a Modal typically contains a set of actions. Refer to Button guidelines for usage.

Modal

  1. Dismiss button
  2. Title (optional)
  3. Body
  4. Footer (optional)

Modal

  1. Title (optional)
  2. Footer
  3. Actions
Edit this section

Specs

Edit this section

Code

<div class="if backdrop"></div>
<div
  aria-modal="true"
  role="dialog"
  aria-labelledby="modal-title"
  aria-describedby="modal-description"
  class="if modal"
>
  <div class="if title" id="modal-title">
    Title<button type="button" class="if close" aria-label="Close modal"></button>
  </div>
  <span class="if axe sr-only" id="modal-description">This is a modal that..</span>
  <div class="if content">
    <p class="if">
      Yeah, but John, if The Pirates of the Caribbean breaks down, the pirates don’t eat the tourists. God creates
      dinosaurs. God destroys dinosaurs. God creates Man. Man destroys God. Man creates Dinosaurs. What do they got in
      there? King Kong? Forget the fat lady! You're obsessed with the fat lady! Drive us out of here!
    </p>

    <p class="if">
      Hey, you know how I'm, like, always trying to save the planet? Here's my chance. They're using our own satellites
      against us. And the clock is ticking. God help us, we're in the hands of engineers. They're using our own
      satellites against us. And the clock is ticking.
    </p>
    <a class="if external-link" target="_blank" rel="noopener noreferrer" href="https://google.com">An external link</a>
    <a class="if external-link" target="_blank" rel="noopener noreferrer" href="https://google.com">An external link</a>
  </div>
</div>

JavaScript implementation example

const openModalButton = document.getElementById('openModal');
const backdrop = document.querySelector('.if.backdrop');
const modal = document.querySelector('.if.modal');
const close = modal.querySelector('.if.close');

let isModalOpen = false;
let focusedElementBeforeOpen;
let firstFocusableEl;
let focusableEls;

const cancelScrollLock = () => {
  const body = document.body;

  body.style.position = '';
  body.style.top = '';
  body.style.width = '';
};

const scrollLock = () => {
  setTimeout(function() {
    const scrollY = document.documentElement.style.getPropertyValue('--scroll-y');
    const body = document.body;
    body.style.position = 'fixed';
    body.style.width = '100vw';
    body.style.top = `-${scrollY}`;
  }, 0);
};

const toggleModal = isOpen => {
  isModalOpen = isOpen;
  if (isOpen) {
    focusedElementBeforeOpen = document.activeElement;

    backdrop.classList.add('is-open');
    modal.removeAttribute('aria-hidden');
    scrollLock();
    backdrop.removeAttribute('aria-hidden');
    focusableEls = modal.querySelectorAll(
      'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]'
    );
    firstFocusableEl = focusableEls[0];
    firstFocusableEl.focus();
  } else {
    focusedElementBeforeOpen.focus();
    backdrop.classList.remove('is-open');
    modal.setAttribute('aria-hidden', true);
    cancelScrollLock();
    backdrop.setAttribute('aria-hidden', true);
  }
};

close.addEventListener('click', e => {
  toggleModal(!isModalOpen);
});

backdrop.addEventListener('click', e => {
  toggleModal(!isModalOpen);
});

modal.addEventListener('keyup', e => {
  if (e.key && e.key === 'Escape') {
    toggleModal(!isModalOpen);
  }
});

openModalButton.addEventListener('click', e => {
  toggleModal(!isModalOpen);
});

window.addEventListener('scroll', () => {
  window.requestAnimationFrame(function() {
    document.documentElement.style.setProperty('--scroll-y', `${window.scrollY}px`);
  });
});

Dialog

<div class="if backdrop"></div>
<div
  aria-modal="true"
  role="dialog"
  aria-labelledby="modal-title"
  aria-describedby="modal-description"
  class="if modal"
>
  <div class="if title" id="dialog-title">
    Title
  </div>
  <span class="if axe sr-only" id="dialog-description">This is a dialog that..</span>
  <div class="if content">
    <p class="if">
      Yeah, but John, if The Pirates of the Caribbean breaks down, the pirates don’t eat the tourists. God creates
      dinosaurs. God destroys dinosaurs. God creates Man. Man destroys God. Man creates Dinosaurs. What do they got in
      there? King Kong? Forget the fat lady! You're obsessed with the fat lady! Drive us out of here!
    </p>

    <p class="if">
      Hey, you know how I'm, like, always trying to save the planet? Here's my chance. They're using our own satellites
      against us. And the clock is ticking. God help us, we're in the hands of engineers. They're using our own
      satellites against us. And the clock is ticking.
    </p>
  </div>
  <div class="if global-footer">
    <button id="cancelButton" type="button" class="if button">Cancel</button>
    <button id="confirmButton" type="button" class="if button primary">Yes</button>
  </div>
</div>

JavaScript implementation example {#modaldialogimplementation_js}

const openModalButton = document.getElementById('openModal');
const backdrop = document.querySelector('.if.backdrop');
const modal = document.querySelector('.if.modal');
const confirmButton = document.getElementById('confirmButton');
const cancelButton = document.getElementById('cancelButton');

let isModalOpen = false;
let focusedElementBeforeOpen;
let firstFocusableEl;
let focusableEls;

const cancelScrollLock = () => {
  const body = document.body;

  body.style.position = '';
  body.style.top = '';
  body.style.width = '';
};

const scrollLock = () => {
  setTimeout(function() {
    const scrollY = document.documentElement.style.getPropertyValue('--scroll-y');
    const body = document.body;
    body.style.position = 'fixed';
    body.style.width = '100vw';
    body.style.top = `-${scrollY}`;
  }, 0);
};

const toggleModal = isOpen => {
  isModalOpen = isOpen;
  if (isOpen) {
    focusedElementBeforeOpen = document.activeElement;

    backdrop.classList.add('is-open');
    modal.removeAttribute('aria-hidden');
    scrollLock();
    backdrop.removeAttribute('aria-hidden');
    focusableEls = modal.querySelectorAll(
      'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]'
    );
    firstFocusableEl = focusableEls[0];
    firstFocusableEl.focus();
  } else {
    focusedElementBeforeOpen.focus();
    backdrop.classList.remove('is-open');
    modal.setAttribute('aria-hidden', true);
    cancelScrollLock();
    backdrop.setAttribute('aria-hidden', true);
  }
};

confirmButton.addEventListener('click', e => {
  toggleModal(!isModalOpen);
});

cancelButton.addEventListener('click', e => {
  toggleModal(!isModalOpen);
});

openModalButton.addEventListener('click', e => {
  toggleModal(!isModalOpen);
});

window.addEventListener('scroll', () => {
  window.requestAnimationFrame(function() {
    document.documentElement.style.setProperty('--scroll-y', `${window.scrollY}px`);
  });
});

Webcomponent

The webcomponent is fully scoped, and uses shadow DOM.

Install

$ npm install @if-design-system/modal-webcomponent

Or for Yarn:

$ yarn add @if-design-system/modal-webcomponent

Usage

<ids-modal data-title="Title">
  <p class="if">
    Yeah, but John, if The Pirates of the Caribbean breaks down, the pirates don’t eat the tourists. God creates
    dinosaurs. God destroys dinosaurs. God creates Man. Man destroys God. Man creates Dinosaurs. What do they got in
    there? King Kong? Forget the fat lady! You're obsessed with the fat lady! Drive us out of here!
  </p>
</ids-modal>
<script src="…modal-webcomponent.umd.js"></script>

Or:

<ids-modal
  data-title="Title"
  data-text="Yeah, but John, if The Pirates of the Caribbean breaks down, the pirates don’t eat the tourists. God creates dinosaurs. God destroys dinosaurs. God creates Man. Man destroys God. Man creates Dinosaurs. What do they got in there? King Kong? Forget the fat lady! You're obsessed with the fat lady! Drive us out of here!"
  data-id="modal-test"
  data-is-open="true"
  data-description="A modal with quotes from characters played by Jeff Goldblum"
>
</ids-modal>
<script src="…modal-webcomponent.umd.js"></script>
Open on init

If you want the modal to open on init without an external indicator, you can pass the data-is-open="true" attribute. This will open up the modal on init.

Properties

Property Type Description Required
data-text String Use this attribute if you only want to use text No
data-title String Modal title Yes
data-id String The id of the modal Yes
data-is-open String/Boolean Should this modal open on render? No
data-description String If you want to use aria-describedby, add description here No

Events

The webcomponent fires a CustomEvent for opening and closing of the component.

The webcomponent also listenes to a CustomEvent for toggling of the modal.

Toggle

This event is ment to be fired of the initiator element for the modal. It is implemented by the developer to be able to display the modal.

See implementation example below.

The data-id attribute on the webcomponent needs to be set for this.

<button type="button" class="if primary button" id="open">Open modal</button>
<ids-modal data-title="Title" data-id="modal-test">
  <p class="if text body">
    Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your
    card.
  </p>
  <a href="#" class="if">This is a link</a>
</ids-modal>
<script>
  const _dispatchEvent = id => {
    const _event = new CustomEvent(`ids:send:modal:toggle:${id}`, {
      bubbles: !0,
      detail: {
        source: 'IDS_MODAL',
        type: 'MODAL_TOGGLE',
        payload: {
          id: id
        }
      }
    });
    document.dispatchEvent(_event);
  };

  document.getElementById('open').addEventListener('click', e => {
    _dispatchEvent('modal-test');
  });
</script>

And the component will listen to this event:

document.addEventListener(`ids:send:modal:toggle:${_id}`, e => {
  _external_initiator_el = e.target;
  _toggleModal();
});

Open/Close

document.addEventListener('ids:send:modal', e => {
  console.log(e);
});
Event Type Description
ids:send:modal CustomEvent Event fired on open and close
ids:send:modal:toggle:<id> CustomEvent Event fired on toggling of modal

The payload of the event looks something like this:

// For opened event
detail: { source: "IDS_MODAL", type: "MODAL_OPENED", payload: { id: "<id>" } }
// For closed event
detail: { source: "IDS_MODAL", type: "MODAL_CLOSED", payload: { id: "<id>" } }
Edit this section

Contact us