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).
Modifiers
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.
Markup Details
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>
Panel Header
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>
Panel 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>
Panel Footer
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>
Default Example
This is the default panel behavior: it slides from the right and has a fixed narrow width.
<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>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>
Panel Footer Modifiers
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:
--justify-start
- to justify the columns to the left.
--justify-center
- to centralize the columns.
--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">
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. 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">
0 of 1 seats reserved.
</p>
</div>
<div class="flix-panel__footer-column flix-panel__footer-column--narrow">
<button type="button" class="flix-btn flix-btn--primary">
Confirm
</button>
</div>
</div>
Double Columns Example
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.
<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>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">
<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>
Modal Panel (With Overlay)
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;
Modal Panel Example
This panel blocks user interaction with the content underneath.
The user must be able to:
- Have keyboard focus on the first interactive element of the panel;
- Close the panel by pressing "Escape";
- Close the panel by clicking the overlay;
The user must not be able to:
- Tab out of the panel;
- Scroll the content beneath the overlay;
<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 "Escape";</li>
<li class="flix-list__item">Close the panel by clicking the overlay;</li>
</ul>
<p><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;
Non-Modal Panel Example
Users are free to interact with the elements in the page.
<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">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;