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>
- 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.
- Content
- This is the main area for all your wonderful HTML stuff!
- 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
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-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>
High heading
Sub headingThis panel slides from the left and has fixed narrow width.
The panel header and footer are taller to show how they can increase in size based on their content.
<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>
Footer modifiers
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>
Double footer columns
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>
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-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.
Non-Modal Panel Example
Without overlay
Users are free to interact with the elements in the page.
<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
- 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;
- 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;
- 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;
- 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`.