Panel

The panel serves as a container for various UI elements that have to be shown (on-demand) on the same page.

The panel component is a dialog window that separates content from the rest of the page.

The panel can be non-modal (without overlay and users can interact with the elements outside of the panel) or modal (has an overlay and the user can only interact with what is inside the panel).

The three modifiers bellow should be added to the flix-panel wrapper element to control the appearance of the panel:

flix-panel--left
Makes the element slide from the left side of the window.
The default width of panels sliding from the sides is 380px.
flix-panel--bottom
Panel slides from the bottom of the window.
By default, the height of panels sliding from the bottom depends on the content.
flix-panel--full
Makes the panel cover the entire visible window.

The panel markup consists of 3 main areas that are enclosed within the panel body element: header, content and footer.

The panel must always have a label, this is usually the title declared inside of the table header, connect them via the aria-labelledby attribute.

<div class="flix-panel" role="dialog" aria-labelledby="the-panel-title-id">
  <div class="flix-panel__body">
    <div class="flix-panel__header">{header code}</div>
    <div class="flix-panel__content">{content code}</div>
    <div class="flix-panel__footer">{footer code}</div>
  </div>
</div>

The header holds the back button, the title and a closing control.

The back button is optional and can be omitted, but the close button should always be present as a good UX practice. You should also provide a keyboard shortcut for closing the panel (on ESC press).

For proper accessibility, the title should have an id, and you should pass this id as aria-labelledby attribute to the panel and the back and close buttons must also have a plain text aria-label.

<div class="flix-panel__header">
  <button aria-label="Return to page" class="flix-panel__back">
    <i class="flix-icon flix-icon-home-solid" aria-hidden="true"></i>
  </button>
  <h3 class="flix-h3 flix-panel__title" id="the-panel-title-id">
    Panel Title
  </h3>
  <button aria-label="Close panel" class="flix-panel__close flix-btn flix-btn--square flix-btn--link"></button>
</div>

Content

This is the main area for all your wonderful HTML stuff!

<div class="flix-panel__content">
  Panel content goes here. Whatever you want. Really.
</div>

The footer holds main actions like "Confirm" or "Cancel". It can have up to two columns on which you can add buttons or other custom elements.

The simplest panel footer markup accepts regular buttons and block buttons gracefully. If you have only one call to action with a side sliding panel (left or right), try to stick to block buttons for a better visual result. Here is the simples footer used in the "default example" bellow:

<div class="flix-panel__footer">
  <div class="flix-panel__footer-column">
    <button type="button" class="flix-btn flix-btn--primary flix-btn--block">
      Confirm
    </button>
  </div>
</div>
<button type="button" class="flix-btn flix-btn--primary" data-toggle="js-panel-default" data-toggle-class="flix-panel--active">
  Default example
</button>

<div class="flix-panel js-panel-default" role="dialog" aria-labelledby="default-example-title">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <h3 class="flix-h3 flix-panel__title" id="default-example-title">
        Default Example
      </h3>
      <button type="button" class="flix-panel__close flix-btn flix-btn--square flix-btn--link" aria-label="Close panel" data-toggle="js-panel-default" data-toggle-class="flix-panel--active"></button>
    </div>
    <div class="flix-panel__content">
      <p class="flix-text flix-space-flush-bottom">
        This is the default panel behavior: it slides from the right and has a fixed narrow width.
      </p>
    </div>
    <div class="flix-panel__footer">
      <div class="flix-panel__footer-column">
        <button type="button" class="flix-btn flix-btn--primary flix-btn--block" data-toggle="js-panel-default" data-toggle-class="flix-panel--active">
          Confirm
        </button>
      </div>
    </div>
  </div>
</div>

The flix-panel__footer container, by default, will justify the columns with equal space between them, if you want to change this behavior it accepts the following modifiers:

flix-panel__footer--justify-start
to justify the columns to the left.
flix-panel__footer--justify-center
to centralize the columns.
flix-panel__footer--justify-end
to justify the columns to the right.

By default, the footer columns width is as wide as it can be, but you can use the --narrow modifier on a column to make them content aware. This is how we achieve the "Button to the right" layout on the example bellow:

<div class="flix-panel__footer flix-panel__footer--justify-end">
  <div class="flix-panel__footer-column flix-panel__footer-column--narrow">
    <button type="button" class="flix-btn flix-btn--primary">Confirm</button>
  </div>
</div>
<button type="button" class="flix-btn flix-btn--primary" data-toggle="js-button-right-example" data-toggle-class="flix-panel--active">
  Button to the right example
</button>

<div class="flix-panel flix-panel--bottom js-button-right-example" role="dialog" aria-labelledby="button-right-example-title">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <h3 class="flix-h3 flix-panel__title" id="button-right-example-title">
        Button to the Right Example
      </h3>
      <button type="button" class="flix-panel__close flix-btn flix-btn--square flix-btn--link" aria-label="Close panel" data-toggle="js-button-right-example" data-toggle-class="flix-panel--active"></button>
    </div>
    <div class="flix-panel__content">
      <p class="flix-text flix-space-flush-bottom">
        You can place the call to action buttons in the corners by creating one narrow column ans justifying the footer according to your own needs.
      </p>
    </div>
    <div class="flix-panel__footer flix-panel__footer--justify-end">
      <div class="flix-panel__footer-column flix-panel__footer-column--narrow">
        <button type="button" class="flix-btn flix-btn--primary" data-toggle="js-button-right-example" data-toggle-class="flix-panel--active">
          Confirm
        </button>
      </div>
    </div>
  </div>
</div>

Multiple calls to action

Only one call to action should be enough for the vast majority of the cases, but if you need to have more than one button in the panel__footer area be aware of the screen width constraints. On smaller screens the side variations don't offer enough space for two or more buttons, so use either the bottom or the full panel variation in order to get enough horizontal space.

You can have up to two flix-panel__footer-column on the footer container, so in the example bellow we added text and a call to action, each column is --narrow and they are aligned with space in between, which is the default footer alignment.

Deprecated since 8.0: button siblings adding side spacing between each other. This behavior is deprecated and should be removed in the next major version, meaning buttons won't add margins around themselves anymore. You should wrap button groups with "btn-group" wrapper to control how button siblings behave around each other.

This is how we achieved the panel footer layout in the "double columns example" bellow:

<div class="flix-panel__footer">
  <div class="flix-panel__footer-column flix-panel__footer-column--narrow">
    <p class="flix-text flix-space-flush-bottom">
      0 of 1 seats reserved.
    </p>
  </div>
  <div class="flix-panel__footer-column flix-panel__footer-column--narrow">
    <div class="flix-btn-group flix-btn-group--align-end">
      <button type="button" class="flix-btn flix-btn--tertiary">
        Close
      </button>
      <button type="button" class="flix-btn flix-btn--primary">
        Confirm
      </button>
    </div>
  </div>
</div>
<button type="button" class="flix-btn flix-btn--primary" data-toggle="js-double-columns-example" data-toggle-class="flix-panel--active">
  Double columns example
</button>

<div class="flix-panel flix-panel--bottom js-double-columns-example" role="dialog" aria-labelledby="double-column-example-title">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <button aria-label="Navigate back" class="flix-panel__back flix-btn flix-btn--square flix-btn--link" data-toggle="js-double-columns-example" data-toggle-class="flix-panel--active">
        <i class="flix-icon flix-icon-home-solid" aria-hidden="true"></i>
      </button>
      <h3 class="flix-h3 flix-panel__title" id="double-column-example-title">
        Double Columns Example
      </h3>
      <button type="button" class="flix-panel__close flix-btn flix-btn--square flix-btn--link" aria-label="Close panel" data-toggle="js-double-columns-example" data-toggle-class="flix-panel--active"></button>
    </div>
    <div class="flix-panel__content">
      <p class="flix-text flix-space-flush-bottom">
        This panel slides from the bottom, this means it has enough space to display two columns and two calls to action even on mobile screens. Always be mindful of the space available for your content.
      </p>
    </div>
    <div class="flix-panel__footer">
      <div class="flix-panel__footer-column flix-panel__footer-column--narrow">
        <p class="flix-text flix-space-flush-bottom">
          0 of 1 seats reserved.
        </p>
      </div>
      <div class="flix-panel__footer-column flix-panel__footer-column--narrow">
        <div class="flix-btn-group flix-btn-group--align-center">
          <button type="button" class="flix-btn flix-btn--tertiary" data-toggle="js-double-columns-example" data-toggle-class="flix-panel--active">
            Close
          </button>
          <button type="button" class="flix-btn flix-btn--primary" data-toggle="js-double-columns-example" data-toggle-class="flix-panel--active">
            Confirm
          </button>
        </div>
      </div>
    </div>
  </div>
</div>

For modal panels you must add an overlay element that will cover the page and block the user from interacting with elements outside of the panel by implementing a "tab trap". You must also disable the window scroll when the panel is open.

  • Add the flix-panel__overlay element at the end of the panel;
  • Add aria-modal-"true" to the panel element for accessibility;
  • When panel is opened:
    • Move focus to the first element of the panel;
    • Create a "tab trap" inside of the panel, so when users tab on the last element it returns focus to the first, and vice versa;
    • Block users from scrolling the page outside of the panel by adding overflow: hidden; CSS rule to the body element;
  • When the panel is closed:
    • Allow users to close the panel by pressing ESC;
    • Allow users to close the panel by clicking the overlay;
    • Move focus back to the element that triggered the panel;
    • Remove the overflow: hidden' CSS rule from the body;
<button type="button" class="flix-btn flix-btn--primary" data-toggle="js-modal-panel-example" data-toggle-class="flix-panel--active">
  Modal Panel Example
</button>

<div class="flix-panel js-modal-panel-example" id="modal-panel-example" role="dialog" aria-modal="true" aria-labelledby="modal-panel-example-title">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <h3 class="flix-h3 flix-panel__title" id="modal-panel-example-title">
        Modal Panel Example
      </h3>
      <button type="button" class="flix-panel__close flix-btn flix-btn--square flix-btn--link" aria-label="Close panel" data-toggle="js-modal-panel-example" data-toggle-class="flix-panel--active"></button>
    </div>
    <div class="flix-panel__content">
      <p class="flix-text">This panel blocks user interaction with the content underneath.</p>
      <p class="flix-text"><strong>The user must be able to:</strong></p>
      <ul class="flix-list">
        <li class="flix-list__item">Have keyboard focus on the first interactive element of the panel;</li>
        <li class="flix-list__item">Close the panel by pressing &quot;Escape&quot;;</li>
        <li class="flix-list__item">Close the panel by clicking the overlay;</li>
      </ul>
      <p class="flix-text"><strong>The user must not be able to:</strong></p>
      <ul class="flix-list">
        <li class="flix-list__item">Tab out of the panel;</li>
        <li class="flix-list__item">Scroll the content beneath the overlay;</li>
      </ul>
    </div>
    <div class="flix-panel__footer">
      <button type="button" class="flix-btn flix-btn--primary flix-btn--block" data-toggle="js-modal-panel-example" data-toggle-class="flix-panel--active">
        I understand
      </button>
    </div>
  </div>
  <div class="flix-panel__overlay flix-overlay" data-toggle="js-modal-panel-example" data-toggle-class="flix-panel--active"></div>
</div>

Non-modal panel

On non-modal panels users can still operate the application while the panel is open and scroll the page, so you shouldn't add the overlay element and the overflow css rule. The "tab trap" is also not necessary but depending on the complexity of the page and the panel you should consider adding tab shortcuts or skip links to go back and forth the panel and the page.

  • Do not add the flix-panel__overlay element;
  • Do not add aria-modal-"true" to the panel;
  • When panel is opened:
    • Move focus to the first element of the panel;
  • When the panel is closed:
    • Allow users to close the panel by pressing ESC;
    • Move focus back to the element that triggered the panel;
<button type="button" class="flix-btn flix-btn--primary" data-toggle="js-nonmodal-panel-example" data-toggle-class="flix-panel--active">
  Non-Modal Panel
</button>

<div class="flix-panel js-nonmodal-panel-example" role="dialog" aria-labelledby="nonmodal-panel-example-title">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <h3 class="flix-h3 flix-panel__title" id="nonmodal-panel-example-title">
        Non-Modal Panel Example
      </h3>
      <button type="button" class="flix-panel__close flix-btn flix-btn--square flix-btn--link" aria-label="Close panel" data-toggle="js-nonmodal-panel-example" data-toggle-class="flix-panel--active"></button>
    </div>
    <div class="flix-panel__content">
      <p class="flix-text flix-space-flush-bottom">Users are free to interact with the elements in the page.</p>
    </div>
    <div class="flix-panel__footer">
      <button type="button" class="flix-btn flix-btn--primary flix-btn--block" data-toggle="js-nonmodal-panel-example" data-toggle-class="flix-panel--active">
        I understand
      </button>
    </div>
  </div>
</div>

Making it work

Examples use Honeycomb classToggler plugin that makes data attribute based class toggling possible.

To make the panel visible:

  • Add flix-panel--active modifier class to the panel wrapper;
  • Remove hidden attribute from the panel wrapper;
  • Move focus to the first interactive element of the panel;

To hide the panel:

  • Remove flix-panel--active modifier class from the panel;
  • Add hidden attribute to the panel wrapper for assistive technologies;
  • Move focus back to the element that triggered the panel;