Skip to content

Popover13.10.5

Popovers provide additional information upon interaction.


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

This is the general documentation for the popover, and all of it's shapes and sizes. For specific documentation regarding components using the popover, please see the relevant section:

Edit this section

Overview

Popovers provide either related additional content on interaction.

Edit this section

Usage

Principles

  • The arrow on the popover should always point to the element triggering the popover
  • The arrow could be placed off center horizontally/vertically to match the initiating element and screen sizes
  • The popover should always have a close button, but that can be omitted if the use case is good enough
  • The popover is always closed if the user clicks the initiating element again or by pressing Close
  • The popover is not a tooltip, if you want a tooltip, see the Tooltip component
  • Clicking outside of the popover should close the popover
  • Pressing Escape should close the popover
  • A popover can be initialized on hover, focus or on click. Help - and Info Tooltips always open on click/touch

Positioning

The Popover component should preferably be positioned on top of the initiator, but there are some use cases where this is not applicable:

  • A popover on hover for help and guidance in left aligned (aligned to the viewport) items or top aligned items
  • When the initiator is above a certain threshold and instead of forcing the user to scroll to see the popover, the popover should be positioned under the initiator
  • If there's not enough room for a popover to the left, align it to bottom
  • If there's not enough room for a popover on the right, align to top
  • On mobile, the popover, regardless of usage and form, should be aligned to the top. If there is no room for it (scroll), align it to the bottom
  • And when used as a red validation feedback popover, it should always be aligned to the bottom. If there is no room for it (scroll), align it to top
Edit this section

Behaviours

Interaction

While it is not desired to have interaction elements in popovers, it is possible for very narrow use cases, like in a walktrough/guide setup with popovers.

A close button should always be present if the popover is initiated on click, and close the popover on click.

Interactive elements can NOT be used in the Info Tooltip component or the Help Tooltip component. These components are specialized for displaying more information for certain cases.

The use case for interactive elements should be limited to for example in a series of popovers like in a guide.

Modifiers

Position

The popover can be placed where it best fit based on viewport position. If the popover can't be placed centered too the initiator, the popover arrow can be realigned.

Vilket år blev du ägare? Villaförsäkringen gäller för dig, de du bor med, de saker du äger, hyr eller lånar, byggnaderna och tomten. Försäkringen är en trygghet för dig som äger hus.
Right position

The popover is positioned to the right

Vilket år blev du ägare? Villaförsäkringen gäller för dig, de du bor med, de saker du äger, hyr eller lånar, byggnaderna och tomten. Försäkringen är en trygghet för dig som äger hus.
Top position

The popover is positioned to the top

Vilket år blev du ägare? Villaförsäkringen gäller för dig, de du bor med, de saker du äger, hyr eller lånar, byggnaderna och tomten. Försäkringen är en trygghet för dig som äger hus.
Left position

The popover is positioned to the left

Vilket år blev du ägare? Villaförsäkringen gäller för dig, de du bor med, de saker du äger, hyr eller lånar, byggnaderna och tomten. Försäkringen är en trygghet för dig som äger hus.
Bottom position

The popover is positioned to the bottom

As validation message

Villaförsäkringen gäller för dig, de du bor med, de saker du äger, hyr eller lånar, byggnaderna och tomten. Försäkringen är en trygghet för dig som äger hus.
As validation message
<div class="if popover is-open bottom red">
  <button type="button" class="if close" aria-label="Close"></button>
  <span class="if text">
    Villaförsäkringen gäller för dig, de du bor med, de saker du äger, hyr eller lånar, byggnaderna och tomten.
    Försäkringen är en trygghet för dig som äger hus.
  </span>
</div>

Small

Vilket år blev du ägare?
Right position

The popover is positioned to the right

Vilket år blev du ägare?
Top position

The popover is positioned to the top

Vilket år blev du ägare?
Left position

The popover is positioned to the left

Vilket år blev du ägare?
Bottom position

The popover is positioned to the bottom

Edit this section

Anatomy

Vilket år blev du ägare? Villaförsäkringen gäller för dig, de du bor med, de saker du äger, hyr eller lånar, byggnaderna och tomten. Försäkringen är en trygghet för dig som äger hus.
Popover
  1. Popover
  2. Close
  3. Title (optional)
  4. Text
Style
  1. The fill color is BR 1, BROWN
  2. The stroke color is BE 5, LIGHTEST BEIGE
  3. The text color is BE 5, LIGHTEST BEIGE
  4. The text color is BE 5, LIGHTEST BEIGE
Edit this section

Specs

Vilket år blev du ägare? Villaförsäkringen gäller för dig, de du bor med, de saker du äger, hyr eller lånar, byggnaderna och tomten. Försäkringen är en trygghet för dig som äger hus.
Popover
Vilket år blev du ägare? Villaförsäkringen gäller för dig, de du bor med, de saker du äger, hyr eller lånar, byggnaderna och tomten. Försäkringen är en trygghet för dig som äger hus.
Popover
Edit this section

Accessibility

  • If you have space, don't use popovers. Just provide clear labels and sufficient body text.
  • If it's a tooltip you are looking to use, decide whether the tip's content should be provided as the label or description and choose ARIA properties accordingly.
  • Don't rely on title attributes. They are not keyboard accessible and are not supported in many screen reader setups.
  • Don't describe popovers with aria-describedby. It makes the subject button non-functional to screen reader users.
<input class="if input-field" type="text" title="Your name" />

Do not use a title-attribute to provide information for input fields

<label class="if" or="tooltip-example-input">
  Your Name
</label>
<input class="if input-field" type="text" id="tooltip-example-input" />

Use an element that fits the component

Length

Due to its disruptive nature, popover content should be kept to an absolute minimum. For larger sets of content or data, consider using a Passive Modal. The Modal will be triggered when the user clicks the information icon.

Interactive elements

The primary purpose of a popover is to provide additional help or context to an item. Therefore, they should contain read-only text. The use of interactive elements, such as Buttons or Links, is discouraged.

Edit this section

Implementation

<div
  class="if popover [small] [red] [top|left|right|bottom] [is-open]"
  aria-hidden="true"
  aria-labelledby="popover-title-1601904511145"
  aria-describedby="popover-text-1601904511145"
  role="dialog"
  style="visibility: visible; left: 118px;"
>
  <button class="if close" type="button" aria-label="Close"></button>
  <span class="if title" id="popover-title-1601904511145">Vilket år blev du ägare?</span>
  <span class="if text" id="popover-text-1601904511145">
    Villaförsäkringen gäller för dig, de du bor med, de saker du äger, hyr eller lånar, byggnaderna och tomten.
    Försäkringen är en trygghet för dig som äger hus.
  </span>
</div>

Position of popover

Use this function to create your own positioning.

const _setPosition = () => {
  const _button_rect = _button_el.getBoundingClientRect();
  const _popover_rect = _popover_el.getBoundingClientRect();
  const _arrow_left_if_popover_overflow = _button_el.offsetLeft + _button_rect.width / 2 - 12;

  const VH = window.innerHeight;
  if (!_is_medium_mq) {
    const _mobile_popover_rect = _popover_el.getBoundingClientRect();

    _popover_el.style.left = `${_popover_el.getBoundingClientRect().left - _popover_el.offsetLeft - 24}px`;

    if (VH / 2 - _mobile_popover_rect.height < _button_rect.top) {
      _popover_el.style.top = `${_button_el.offsetTop - _mobile_popover_rect.height - _button_rect.height}px`;
      _popover_el.classList.remove('bottom');
    } else {
      _popover_el.style.top = `${_button_rect.height + 24}px`;
      _popover_el.classList.add('bottom');
    }

    _popover_el.style.setProperty('--ids-popover-arrow-vertical-position', `${_button_el.offsetTop}px`);
    _popover_el.style.setProperty(
      '--ids-popover-arrow-horizontal-position',
      `${_arrow_left_if_popover_overflow < 0 ? 0 : _arrow_left_if_popover_overflow}px`
    );
  } else {
    _popover_el.style.setProperty('--ids-popover-arrow-vertical-position', 'calc(50% - 12px)');
    _popover_el.style.setProperty('--ids-popover-arrow-horizontal-position', 'calc(50% - 12px)');
    _popover_el.style.left = _button_el.offsetLeft + _button_rect.width / 2 - _popover_rect.width / 2 + 'px';

    if (_popover_el.getBoundingClientRect().left < 24) {
      _popover_el.style.left = '0px';
      _popover_el.style.setProperty('--ids-popover-arrow-vertical-position', `${_button_el.offsetTop}px`);
      _popover_el.style.setProperty(
        '--ids-popover-arrow-horizontal-position',
        `${_arrow_left_if_popover_overflow < 0 ? 0 : _arrow_left_if_popover_overflow}px`
      );
    }

    if (VH / 2 - _popover_el.getBoundingClientRect().height < _button_rect.top) {
      _popover_el.style.top = `${_button_el.offsetTop -
        _popover_el.getBoundingClientRect().height -
        _button_rect.height}px`;
      _popover_el.classList.remove('bottom');
    } else {
      _popover_el.style.top = `${_button_rect.height + 24}px`;
      _popover_el.classList.add('bottom');
    }
  }
};

Auto-generated

Any element:

<element
  data-popover="[top|left|right|bottom]"
  data-popover-title=""
  data-popover-text=""
  class="if …"
  aria-label=""
  
></element>
const _medium_mq = window.matchMedia('screen and (min-width: 400px)');
let _is_medium_mq = true;

const _handleMedium = mql => {
  if (mql.matches) {
    _is_medium_mq = true;
  } else {
    _is_medium_mq = false;
  }
};

_medium_mq.addListener(_handleMedium);

_handleMedium(_medium_mq);

const popoverInitiators = document.querySelectorAll('[data-popover]:not(abbr)');

const initPopover = popoverInitiator => {
  const title = popoverInitiator.getAttribute('data-popover-title');
  const text = popoverInitiator.getAttribute('data-popover-text');
  const position = popoverInitiator.getAttribute('data-popover');
  const size = popoverInitiator.getAttribute('data-popover-size');
  const isOpen = popoverInitiator.getAttribute('data-popover-is-open');

  const createPopover = (title, text, position = 'top', size = 'normal') => {
    const uuid = Date.now();
    const popoverEl = document.createElement('div');
    const closeEl = document.createElement('button');
    const titleEl = document.createElement('span');
    const textEl = document.createElement('span');

    popoverEl.classList.add('if');
    popoverEl.classList.add('popover');
    popoverEl.classList.add(size);
    popoverEl.classList.add(position);
    popoverEl.setAttribute('aria-hidden', true);
    popoverEl.setAttribute('aria-labelledby', `popover-title-${uuid}`);
    popoverEl.setAttribute('aria-describedby', `popover-text-${uuid}`);
    popoverEl.setAttribute('role', 'dialog');

    closeEl.classList.add('if');
    closeEl.classList.add('close');
    closeEl.setAttribute('type', 'button');
    closeEl.setAttribute('aria-label', 'Close');

    closeEl.addEventListener('click', () => {
      _closePopover(popoverEl);
    });

    titleEl.classList.add('if');
    titleEl.classList.add('title');
    titleEl.setAttribute('id', `popover-title-${uuid}`);
    const titleText = document.createTextNode(title);
    titleEl.appendChild(titleText);

    textEl.classList.add('if');
    textEl.classList.add('text');
    textEl.setAttribute('id', `popover-text-${uuid}`);
    const textText = document.createTextNode(text);
    textEl.appendChild(textText);

    popoverEl.appendChild(closeEl);
    popoverEl.appendChild(titleEl);
    popoverEl.appendChild(textEl);

    return popoverEl;
  };

  const insertedPopover = popoverInitiator.parentElement.insertBefore(
    createPopover(title, text, position, size),
    popoverInitiator
  );

  insertedPopover.style.visibility = 'hidden';

  const setPosition = (initiator, popover) => {
    const _button_rect = initiator.getBoundingClientRect();
    const _popover_rect = popover.getBoundingClientRect();
    const _arrow_left_if_popover_overflow = initiator.offsetLeft + _button_rect.width / 2 - 12 - 24;
    const VH = window.innerHeight;
    const VW = window.innerWidth;

    if (!_is_medium_mq) {
      const _mobile_popover_rect = popover.getBoundingClientRect();

      popover.style.left = `${popover.getBoundingClientRect().left - popover.offsetLeft + 24}px`;

      popover.classList.remove('bottom');
      popover.classList.remove('right');
      popover.classList.remove('left');

      if (VH / 2 - _mobile_popover_rect.height < _button_rect.top) {
        popover.style.top = `calc(${initiator.offsetTop}px - ${popover.getBoundingClientRect().height}px - 24px)`;
      } else {
        popover.style.top = `${initiator.offsetTop + _button_rect.height + 24}px`;
        popover.classList.add('bottom');
      }

      popover.style.setProperty('--ids-popover-arrow-vertical-position', `${initiator.offsetTop}px`);
      popover.style.setProperty(
        '--ids-popover-arrow-horizontal-position',
        `${_arrow_left_if_popover_overflow < 0 ? 0 : _arrow_left_if_popover_overflow}px`
      );
    } else {
      popover.style.setProperty('--ids-popover-arrow-vertical-position', 'calc(50% - 12px)');
      popover.style.setProperty('--ids-popover-arrow-horizontal-position', 'calc(50% - 12px)');

      if (position && position === 'bottom') {
        popover.style.left = initiator.offsetLeft + _button_rect.width / 2 - _popover_rect.width / 2 + 'px';

        if (popover.getBoundingClientRect().left < 24) {
          popover.style.left = '0px';
          popover.style.setProperty('--ids-popover-arrow-vertical-position', `${initiator.offsetTop}px`);
          popover.style.setProperty(
            '--ids-popover-arrow-horizontal-position',
            `${_arrow_left_if_popover_overflow < 0 ? 0 : _arrow_left_if_popover_overflow}px`
          );
        }
        popover.classList.add('bottom');
        popover.style.top = `${initiator.offsetTop + _button_rect.height + 24}px`;
      } else if (position && position === 'right') {
        popover.style.left = `${initiator.offsetLeft + _button_rect.width + 24}px`;
        popover.style.top = `${initiator.offsetTop + _button_rect.height / 2 - _popover_rect.height / 2}px`;
        popover.classList.add('right');
        popover.classList.remove('top');
        if (VW / 2 - popover.getBoundingClientRect().width < _button_rect.left) {
          popover.classList.remove('right');
          popover.classList.add('top');
          popover.style.left = `${initiator.offsetLeft +
            _button_rect.width / 2 -
            popover.getBoundingClientRect().width / 2}px`;

          if (popover.getBoundingClientRect().left < 24) {
            popover.style.left = '24px';

            popover.style.setProperty('--ids-popover-arrow-vertical-position', `${initiator.offsetTop}px`);
            popover.style.setProperty(
              '--ids-popover-arrow-horizontal-position',
              `${_arrow_left_if_popover_overflow < 0 ? 0 : _arrow_left_if_popover_overflow}px`
            );
          }
          popover.style.top = `calc(${initiator.offsetTop}px - ${popover.getBoundingClientRect().height}px - 24px)`;
          popover.style.setProperty('--ids-popover-arrow-vertical-position', 'calc(50% - 12px)');
          popover.style.setProperty('--ids-popover-arrow-horizontal-position', 'calc(50% - 12px)');
        }
      } else if (position && position === 'left') {
        popover.style.left = `${initiator.offsetLeft - popover.getBoundingClientRect().width - 24}px`;
        popover.style.top = `${initiator.offsetTop + _button_rect.height / 2 - _popover_rect.height / 2}px`;
        popover.classList.add('left');
        popover.classList.remove('bottom');
        if (popover.getBoundingClientRect().width > _button_rect.left + 24) {
          popover.classList.remove('left');
          popover.classList.add('bottom');
          popover.style.left = `${initiator.offsetLeft +
            _button_rect.width / 2 -
            popover.getBoundingClientRect().width / 2}px`;

          if (popover.getBoundingClientRect().left < 24) {
            popover.style.left = '24px';

            popover.style.setProperty('--ids-popover-arrow-vertical-position', `${initiator.offsetTop}px`);
            popover.style.setProperty(
              '--ids-popover-arrow-horizontal-position',
              `${_arrow_left_if_popover_overflow < 0 ? 0 : _arrow_left_if_popover_overflow}px`
            );
          }
          popover.style.top = `${initiator.offsetTop + _button_rect.height + 24}px`;
          popover.style.setProperty('--ids-popover-arrow-vertical-position', 'calc(50% - 12px)');
          popover.style.setProperty('--ids-popover-arrow-horizontal-position', 'calc(50% - 12px)');
        }
      } else {
        popover.style.left = `${initiator.offsetLeft +
          _button_rect.width / 2 -
          popover.getBoundingClientRect().width / 2}px`;

        if (popover.getBoundingClientRect().left < 24) {
          popover.style.left = '24px';

          popover.style.setProperty('--ids-popover-arrow-vertical-position', `${initiator.offsetTop}px`);
          popover.style.setProperty(
            '--ids-popover-arrow-horizontal-position',
            `${_arrow_left_if_popover_overflow < 0 ? 0 : _arrow_left_if_popover_overflow}px`
          );
        }
        popover.classList.remove('bottom');
        popover.classList.remove('right');
        popover.classList.remove('left');
        popover.style.top = `calc(${initiator.offsetTop}px - ${popover.getBoundingClientRect().height}px - 24px)`;
      }
    }
  };

  const _togglePopover = popover => {
    const _is_open = popover.classList.contains('is-open');
    if (_is_open) {
      _closePopover(popover);
    } else {
      _openPopover(popover);
    }
  };
  const toggleAriaHidden = popover => {
    popover.setAttribute('aria-hidden', popover.getAttribute('aria-hidden') != 'true' ? 'true' : 'false');
  };

  const _closePopover = popover => {
    popover.classList.remove('is-open');
    popover.style.visibility = 'hidden';
    toggleAriaHidden(popover);
  };

  const _openPopover = popover => {
    popover.classList.add('is-open');
    window.requestAnimationFrame(function() {
      setPosition(popoverInitiator, popover);
    });

    popover.style.visibility = 'visible';
    toggleAriaHidden(popover);
  };

  if (isOpen === '') {
    _openPopover(insertedPopover);
  }

  popoverInitiator.addEventListener('click', () => {
    _togglePopover(insertedPopover);
  });

  window.addEventListener('resize', function() {
    requestAnimationFrame(function() {
      setPosition(popoverInitiator, insertedPopover);
    });
  });
};

const initPopovers = () => {
  popoverInitiators.forEach(initPopover);
};
export default initPopovers;
Edit this section

Contact us