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 examples bellow use the Popup plugin to handle the toggling of panels. It will set up all the required accessibility features as well as toggling the class names and keyboard navigation.

Modifiers

The three modifiers bellow can 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--center
Panel is displayed in the center of the screen, similar to a popup.
On smaller screens it sticks to the bottom of the page basically copying behaviour of the `flix-panel--bottom` variation.
flix-panel--full
Makes the panel cover the entire visible window.

Structure

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

<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>
  1. Header
  • The header contains the back button, the title and a closing control.
  • The back button is optional and can be omitted, but the close button must always be present.
  • Both the home and close buttons must be properly labelled with a screen reader only text or aria-label attribute.
  • The panel title is required and you can use it to label the panel by giving it an id and passing it as aria-labelledby attribute to the panel element.
  1. Content
  • This is the main area for all your wonderful HTML stuff!
  1. Footer
  • The footer contains the main actions such as "Confirm" or "Cancel".
  • It can have up to two columns on which you can add buttons or other custom elements.
  • You can use regular buttons or block buttons depending on your needs.
  • 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.

Default example

<button type="button" class="flix-btn flix-btn--primary" data-popup="default-panel">
  Default example
</button>

<div id="default-panel" class="flix-panel" hidden="" aria-labelledby="default-example-title">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <div id="default-example-title" class="flix-panel__title">
        <h3 class="flix-h3 flix-h3--space-flush">
          Default Example
        </h3>
      </div>
      <button type="button" class="flix-panel__close flix-btn flix-btn--square flix-btn--link" aria-label="Close panel" data-closepopup="true"></button>
    </div>
    <div class="flix-panel__content">
      <p class="flix-text">
        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-closepopup="true">
          Confirm
        </button>
      </div>
    </div>
  </div>
  <div class="flix-panel__overlay flix-overlay"></div>
</div>

Header customization

You can provide any custom content as the header title as long as you provide the correct styles and properly associate it as the panel title.

For example, to show the panel header with title and subtitle in the next example we can use the following markup:

<div class="flix-panel" aria-labelledby="panel-title">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <button class="flix-panel__back flix-btn flix-btn--square flix-btn--md flix-btn--link" aria-label="Navigate back">
        <flix-icon name="home" solid="" aria-hidden="true"></flix-icon>
      </button>
      <div id="panel-title" class="flix-panel__title flix-has-text-centered">
        <h3 class="flix-h3 flix-h3--space-flush">
          Title
        </h3>
        <strong class="flix-h4 flix-h4--space-flush">
          Subtitle
        </strong>
      </div>
      <button class="flix-panel__close flix-btn flix-btn--square flix-btn--md flix-btn--link" aria-label="Close panel"></button>
    </div>
    // ... rest of the panel content
  </div>
</div>
<button type="button" class="flix-btn flix-btn--primary" data-popup="custom-header-example">
  Custom header example
</button>

<div id="custom-header-example" class="flix-panel flix-panel--left" hidden="" aria-labelledby="custom-header-title">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <button class="flix-panel__back flix-btn flix-btn--square flix-btn--md flix-btn--link" aria-label="Navigate back" data-closepopup="true">
        <flix-icon name="home" solid="" aria-hidden="true"></flix-icon>
      </button>
      <div id="custom-header-title" class="flix-panel__title flix-has-text-centered">
        <h3 class="flix-h3 flix-h3--space-flush">
          High heading
        </h3>
        <strong class="flix-h4 flix-h4--space-flush">
          Sub heading
        </strong>
      </div>
      <button class="flix-panel__close flix-btn flix-btn--square flix-btn--md flix-btn--link" aria-label="Close panel" data-closepopup="true"></button>
    </div>
    <div class="flix-panel__content">
      <p class="flix-text">
        This panel slides from the left and has fixed narrow width.
      </p>
      <p class="flix-text">
        The panel header and footer are taller to show how they can increase in size based on their content.
      </p>
    </div>
    <div class="flix-panel__footer">
      <div class="flix-panel__footer-column">
        <div class="flix-btn-group flix-btn-group--vertical">
          <button class="flix-btn flix-btn--block" data-closepopup="true">Close</button>
          <button class="flix-btn flix-btn--block flix-btn--primary">Confirm</button>
        </div>
      </div>
    </div>
  </div>
  <div class="flix-panel__overlay flix-overlay"></div>
</div>

The footer container will initially justify the columns with equal space between them, ans have the columns fill the space available to them.

If you want to change this behavior you can use the following modifiers:

flix-panel__footer--justify-start
flix-panel__footer--justify-center
flix-panel__footer--justify-end
When applied to the footer will control where the coolumns will be placed.
flix-panel__footer-column--narrow
When applied to a footer column makes that column size 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-popup="footer-example-panel">
  Button to the right example
</button>

<div id="footer-example-panel" class="flix-panel flix-panel--bottom" hidden="" aria-labelledby="button-right-example-title">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <div id="button-right-example-title" class="flix-panel__title">
        <h3 class="flix-h3 flix-h3--space-flush">
          Button to the Right Example
        </h3>
      </div>
      <button type="button" class="flix-panel__close flix-btn flix-btn--square flix-btn--link" aria-label="Close panel" data-closepopup="true"></button>
    </div>
    <div class="flix-panel__content">
      <p class="flix-text">
        You can place the call to action buttons in the corners by creating one narrow column and 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-closepopup="true">
          Confirm
        </button>
      </div>
    </div>
  </div>
  <div class="flix-panel__overlay flix-overlay"></div>
</div>

If you must show 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 columns inside of the panel footer. In the example bellow we added some text and a call to action, each column is --narrow and they are aligned with space in between, which is the default footer alignment.

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

<div class="flix-panel__footer">
  <div class="flix-panel__footer-column flix-panel__footer-column--narrow">
    <p class="flix-text">
      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">
        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-popup="double-columns-example">
  Double footer columns example
</button>

<div id="double-columns-example" class="flix-panel flix-panel--bottom" hidden="" aria-labelledby="double-columns-example">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <button type="button" aria-label="Navigate back" class="flix-panel__back flix-btn flix-btn--square flix-btn--link" data-closepopup="true">
        <flix-icon name="home" solid="" aria-hidden="true"></flix-icon>
      </button>
      <div id="double-columns-example" class="flix-panel__title">
        <h3 class="flix-h3 flix-h3--space-flush">
          Double Columns Example
        </h3>
      </div>
      <button type="button" class="flix-panel__close flix-btn flix-btn--square flix-btn--link" aria-label="Close panel" data-closepopup="true"></button>
    </div>
    <div class="flix-panel__content">
      <p class="flix-text">
        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">
          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" data-closepopup="true">
            Close
          </button>
          <button type="button" class="flix-btn flix-btn--primary">
            Confirm
          </button>
        </div>
      </div>
    </div>
  </div>
  <div class="flix-panel__overlay flix-overlay"></div>
</div>

Non-modal panel

On non-modal panels users can still operate the application while the panel is open and scroll the page.

To make a non-modal panel simply do not add the overlay element to the component, the plugin will then handle the proper aria attributes and will not create a tab trap for the component.

<button type="button" class="flix-btn flix-btn--primary" data-popup="non-modal-panel-example">
  Non-Modal Panel
</button>

<div id="non-modal-panel-example" class="flix-panel" hidden="" aria-labelledby="non-modal-panel-title">
  <div class="flix-panel__body">
    <div class="flix-panel__header">
      <div id="non-modal-panel-title" class="flix-panel__title flix-has-text-centered">
        <h3 class="flix-h3 flix-h3--space-flush-bottom">Non-Modal Panel Example</h3>
        <p class="flix-text flix-space-2-bottom">Without overlay</p>
      </div>
      <button type="button" class="flix-panel__close flix-btn flix-btn--square flix-btn--link" aria-label="Close panel" data-closepopup="true"></button>
    </div>
    <div class="flix-panel__content">
      <p class="flix-text">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-closepopup="true">
        I understand
      </button>
    </div>
  </div>
</div>

Making it work without the plugin

Modal panels require you to provide an overlay element that will cover the page and block the user from interacting with elements outside of the panel. You must implement a "tab trap" and also disable the window scroll when the panel is open.
  • Add `role="dialog"` and `aria-modal-"true"` to the panel element for accessibility;
  • Add an `id` to the title and pass it as `aria-labelledby` value to the panel for proper accessibility;
  • Add the `flix-overlay` element at the end of the panel;
  • Adding `tabindex="0"` to the content if it has a lot of content and can show a scroll bar to enable keyboard users to also scroll the content with arrow keys;
When a panel is opened:
  • Move focus to the first element of the panel, usually the close button;
  • Create a "tab trap" so when users tab on the last element it returns focus to the first, and vice versa;
  • Block users from scrolling the page outside by adding `overflow: hidden;` CSS rule to the body element;
When the panel is closed:
  • Allow users to close the panel by clicking the overlay or pressing ESC;
  • Move focus back to the element that triggered the panel;
  • Remove the `overflow: hidden'` CSS rule from the body;
When a panel is non-modal, the following features should not be added:
  • Do not add `aria-modal="true"` to the panel, but do add the `role="dialog"` and label it accordingly;
  • The `flix-overlay` element is not needed to cover the page;
  • A tab trap shouldn't be created, but you are encouraged to add keyboard shortcuts to navigate in and out of the panel if needed, including setting the focus to the first interactive element when the panel is opened;
  • You shouldn't disable the page scroll with `overflow: hidden`.