Skip to content

Panel13.10.5

Expandable panels contain creation flows and allow lightweight editing of an element.


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

Edit this section

Overview

An expandable panel is a lightweight container that may either stand alone or be connected to a larger surface, such as a card.

Edit this section

Variations

Type Purpose
Expandable panel Default expandable panel
Selectable expandable panel A panel that is both selectable and expandable
Edit this section

Usage

Column 1
Column 2
Column 3
Column 1
Column 3
Panel with columns

A panel can have columns, with the usage of grid.

<div class="if panel">
  <div class="if title">
    <div class="if grid across">
      <div class="if row">
        <div class="if col-12--xs col-4--md">Column 1</div>
        <div class="if col-12--xs col-4--md">Column 2</div>
        <div class="if col-12--xs col-4--md">Column 3</div>
      </div>
    </div>
  </div>
</div>
<div class="if panel">
  <div class="if title">
    <div class="if grid across">
      <div class="if row">
        <div class="if col-12--xs col-8--md">Column 1</div>
        <div class="if col-12--xs col-4--md">Column 3</div>
      </div>
    </div>
  </div>
</div>
Column 1
Column 2
Column 3
Column 1
Column 3
Column 1
Column 3
Grouped panels

Panels can be grouped.

<div class="if panels">
  <div class="if panel">
    <div class="if title">
      <div class="if grid across">
        <div class="if row">
          <div class="if col-12--xs col-4--md">Column 1</div>
          <div class="if col-12--xs col-4--md">Column 2</div>
          <div class="if col-12--xs col-4--md">Column 3</div>
        </div>
      </div>
    </div>
  </div>
  <div class="if panel">
    <div class="if title">
      <div class="if grid across">
        <div class="if row">
          <div class="if col-12--xs col-8--md">Column 1</div>
          <div class="if col-12--xs col-4--md">Column 3</div>
        </div>
      </div>
    </div>
  </div>
  <div class="if panel">
    <div class="if title">
      <div class="if grid across">
        <div class="if row">
          <div class="if col-12--xs col-8--md">Column 1</div>
          <div class="if col-12--xs col-4--md">Column 3</div>
        </div>
      </div>
    </div>
  </div>
</div>
Expandable panel

A panel can be expandable.

<div class="if panel is-expandable is-open">
  <div aria-controls="exp-usage-1" aria-expanded="true" class="if title" tabindex="0">
    Tips och råd
  </div>
  <div class="if content" id="exp-usage-1" role="region">
    <button class="if button primary" type="button">Submit</button>
  </div>
</div>
Grouped expandable panels

Expandable panels can be grouped.

<div class="if panels">
  <div class="if panel is-expandable is-open">
    <div aria-controls="exp-usage-1" aria-expanded="true" class="if title" tabindex="0">
      Tips och råd
    </div>
    <div class="if content" id="exp-usage-1" role="region">
      <button class="if button primary" type="button">Submit</button>
    </div>
  </div>
  <div class="if panel is-expandable">
    <div aria-controls="exp-usage-2" aria-expanded="false" class="if title" tabindex="0">
      Tips och råd
    </div>
    <div class="if content" id="exp-usage-2" role="region">
      <button class="if button primary" type="button">Submit</button>
    </div>
  </div>
  <div class="if panel is-expandable">
    <div aria-controls="exp-usage-3" aria-expanded="false" class="if title" tabindex="0">
      Tips och råd
    </div>
    <div class="if content" id="exp-usage-3" role="region">
      <button class="if button primary" type="button">Submit</button>
    </div>
  </div>
</div>
Selectable

A panel can also be selectable.

<div class="if panel is-expandable is-selectable is-open">
  <input checked type="radio" id="exp-states-8" class="if radio-button" name="radios" />
  <label for="exp-states-8" class="if title" aria-controls="exp8" aria-expanded="false">Select me</label>
  <div class="if content" role="region" id="exp8">
    <button class="if button primary" type="button">Submit</button>
  </div>
</div>
Edit this section

Behaviours

Interactions

Static

Hovered

Focused

Open

Edit this section

Accessibility

To make the component accessible, you want to use the correct aria-*-attributes:

  • Use aria-controls to dictate which content the panel controls
  • Update aria-expanded when the panel is expanded
  • Use tabindex=0 if the whole row is clickable
<div class="if panel is-expandable is-open">
  <div aria-controls="exp-usage-1" aria-expanded="true" class="if title" tabindex="0">
    Tips och råd
  </div>
  <div class="if content" id="exp-usage-1" role="region">
    <button class="if button primary" type="button">Submit</button>
  </div>
</div>
Edit this section

Anatomy

Expandable panel
  1. Open expandable panel
  2. Title
  3. Content
Panel
  1. Panel
  2. Title
Tips och råd
Tips och råd
Panel with content
  1. Content
  2. Panel
  3. Title
Edit this section

Specs

Title
Panel
Title
Panel
Expandable panel
Selectable expandable panel
Selectable panel
Edit this section

Implementation

Remember to use the correct aria-tags/roles, I.E. aria-expanded, aria-controls, tabindex and role="region". Read more about it here.
<div class="if panel is-expandable is-open">
  <div class="if title" aria-expanded="true" aria-controls="exp1" tabindex="0">Tips och råd</div>
  <div class="if content" role="region" id="exp1" tabindex="0">
    <button class="if button primary" type="button">A button</button>
  </div>
</div>

<div class="if panel is-expandable">
  <div class="if title" aria-expanded="false" aria-controls="exp2" tabindex="0">Tips och råd</div>
  <div class="if content" role="region" id="exp2" tabindex="-1">
    <button class="if button primary" type="button">A button</button>
  </div>
</div>

Selectable expandable

<div class="if panel is-expandable is-selectable">
  <input checked type="radio" id="selectable-expandables-radio-1" class="if radio-button" name="radios" />
  <label for="selectable-expandables-radio-1" class="if title" aria-controls="exp1" aria-expanded="false" tabindex="0"
    >Select me</label
  >
  <div class="if content" role="region" id="exp1">
    <button class="if button primary" type="button">A duc</button>
  </div>
</div>
<div class="if panel is-expandable is-selectable">
  <input checked type="radio" id="selectable-expandables-radio-2" class="if radio-button" name="radios" />
  <label for="selectable-expandables-radio-2" class="if title" aria-controls="exp2" aria-expanded="false" tabindex="0"
    >Select me</label
  >
  <div class="if content" role="region" id="exp2">
    <button class="if button primary" type="button">A duc</button>
  </div>
</div>
<div class="if panel is-expandable is-selectable">
  <input checked type="radio" id="selectable-expandables-radio-3" class="if radio-button" name="radios" />
  <label for="selectable-expandables-radio-3" class="if title" aria-controls="exp3" aria-expanded="false" tabindex="0"
    >Select me</label
  >
  <div class="if content" role="region" id="exp3">
    <button class="if button primary" type="button">A duc</button>
  </div>
</div>

Grouped panels/expandable

If you want to group the panels/expandables (bring them closer together), wrap them with .if.panels:

<div class="if panels">
  <div class="if panel is-expandable light">
    <div class="if title" aria-expanded="false" aria-controls="exp-main-1" tabindex="0">
      Expelliarmus
    </div>
    <div class="if content" role="region" id="exp-main-1" tabindex="-1">
      <p class="if text lead center">
        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.
      </p>
      <p class="if text body center">
        En husägare kan råka ut för olika händelser. Det kan vara inbrott, brand- eller vattenskada eller att
        värmepannan går sönder. I villaförsäkringen ingår reseskydd och försäkring för de personliga saker du och
        familjen äger, hyr eller lånar. Villaförsäkringen kan du köpa till villa, radhus eller kedjehus.
      </p>
      <div class="if input-wrapper center">
        <button type="button" class="if button primary large">Jämför våra villaförsäkringar</button>
      </div>
    </div>
  </div>
  <div class="if panel is-expandable light">
    <div class="if title" aria-expanded="false" aria-controls="exp-main-2" tabindex="0">
      Expelliarmus
    </div>
    <div class="if content" role="region" id="exp-main-2" tabindex="-1">
      <p class="if text lead center">
        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.
      </p>
      <p class="if text body center">
        En husägare kan råka ut för olika händelser. Det kan vara inbrott, brand- eller vattenskada eller att
        värmepannan går sönder. I villaförsäkringen ingår reseskydd och försäkring för de personliga saker du och
        familjen äger, hyr eller lånar. Villaförsäkringen kan du köpa till villa, radhus eller kedjehus.
      </p>
      <div class="if input-wrapper center">
        <button type="button" class="if button primary large">Jämför våra villaförsäkringar</button>
      </div>
    </div>
  </div>
  <div class="if panel is-expandable light">
    <div class="if title" aria-expanded="false" aria-controls="exp-main-3" tabindex="0">
      Expelliarmus
    </div>
    <div class="if content" role="region" id="exp-main-3" tabindex="-1">
      <p class="if text lead center">
        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.
      </p>
      <p class="if text body center">
        En husägare kan råka ut för olika händelser. Det kan vara inbrott, brand- eller vattenskada eller att
        värmepannan går sönder. I villaförsäkringen ingår reseskydd och försäkring för de personliga saker du och
        familjen äger, hyr eller lånar. Villaförsäkringen kan du köpa till villa, radhus eller kedjehus.
      </p>
      <div class="if input-wrapper center">
        <button type="button" class="if button primary large">Jämför våra villaförsäkringar</button>
      </div>
    </div>
  </div>
</div>

Example JavaScript implementation

const getSiblingsOfSameNode = (currentNode, className) => {
  var result = [];
  var node = currentNode.parentNode.firstChild;

  while (node) {
    if (node !== currentNode && node.nodeType === Node.ELEMENT_NODE) result.push(node);
    node = node.nextElementSibling || node.nextSibling;
  }

  if (className) {
    return result.filter(node => node.classList.contains(className));
  }

  return result;
};
const expandables = document.querySelectorAll('.if.panel.is-expandable');

const closeAllExpandables = expandable => {
  // get sibling expandables
  const expandables = getSiblingsOfSameNode(expandable, 'is-expandable');
  expandables.forEach(expandable => {
    const expandableButton = expandable.querySelector('.if.title');
    const expandableContent = expandable.querySelector('.if.content');
    expandableContent.setAttribute('tabindex', '-1');
    expandable.classList.remove('is-open');
    expandableButton.setAttribute('aria-expanded', false);
  });
};

expandables.forEach(expandable => {
  const expandableButton = expandable.querySelector('.if.title');
  const expandableContent = expandable.querySelector('.if.content');

  expandableButton.setAttribute('tabindex', '0');
  if (!expandable.classList.contains('is-open')) {
    expandableContent.setAttribute('tabindex', '-1');
  } else {
    expandableContent.removeAttribute('tabindex');
  }
  expandableButton.addEventListener('click', e => {
    if (!expandable.classList.contains('is-open')) {
      closeAllExpandables(expandable);
      expandableContent.removeAttribute('tabindex');
    } else {
      expandableContent.setAttribute('tabindex', '-1');
    }

    expandable.classList.toggle('is-open');
    expandableButton.setAttribute(
      'aria-expanded',
      expandableButton.getAttribute('aria-expanded') == 'true' ? false : true
    );
  });
  expandableButton.addEventListener('keydown', e => {
    if (!e.repeat) {
      if (e.key == 'Enter') {
        if (!expandable.classList.contains('is-open')) {
          closeAllExpandables(expandable);
          expandableContent.removeAttribute('tabindex');
        } else {
          expandableContent.setAttribute('tabindex', '-1');
        }

        if (expandable.classList.contains('is-selectable')) {
          const radioButton = expandableButton.parentElement.querySelector('input[type=radio]');

          radioButton.checked = radioButton.checked ? false : true;
        }

        expandable.classList.toggle('is-open');
        expandableButton.setAttribute(
          'aria-expanded',
          expandableButton.getAttribute('aria-expanded') == 'true' ? false : true
        );
      }
    }
  });
});
Edit this section

Contact us