Demo
<div class="tpw-widget">...
Options
Semantic
POSH (Plain Old Semantic HTML)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque tempus felis id urna vulputate maximus. Aliquam vitae arcu id nulla convallis aliquam. Vivamus at nisl semper, sagittis lectus eu, fringilla nisl.
This link is here to test keyboard navigation.A11Y (AccessibilitY)
ARIA (Accessible Rich Internet Applications) FTW! (For The Win!)
Nulla a facilisis sapien. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Curabitur a est aliquet nisl consequat blandit. Aliquam consequat faucibus nisi eget molestie.
This link is here to test keyboard navigation.Responsive
From TabPanel to Accordion
Sed vel euismod lectus, in viverra massa. Sed ullamcorper, elit ac aliquam sollicitudin, eros lacus volutpat ipsum, eget elementum sapien enim vel erat. Nunc vel ante ac purus accumsan dapibus. Ut vitae eros efficitur, auctor libero ut, porta lacus.
This link is here to test keyboard navigation.Features
POSH (Plain Old Semantic HTML)
The Widget relies on Plain Old Semantic HTML (no jump-links needed!).
Progressive Enhancement FTW (For The Win)!Accessible
First class support for screen-reader users!
ARIA controls the rendering of their non-visual experience.Markup Agnostic
Authors can use any heading they want to structure their content,
they can even use a Definition List if they wish (dt
/dd
pairs).Adaptive
The TabPanel becomes an Accordion if the tabs cannot "fit".
Note that ARIA attributes will change accordingly.Versatile
Can work as an Accordion out-of-the-box.
Accordion's icons can either be displayed to the right or left of the text.RTL Friendly
Tabs flow according to script direction (
ltr
,rtl
).
Icon's positioning will obey script direction too.Keyboard Friendly
Supports keyboard navigation (see below).
Users can skip the entire Widget or reach the first tab/header.
Assuming the focus is on the Widget
For TabPanel
- tab
When focus moves to the tabs, places focus on the active "tab" element.
When the tabs contain the focus, moves focus to the next element in the page tab sequence outside the tabs, which is typically either the first focusable element inside the tab panel or the tab panel itself.- Left Arrow
Moves focus to the previous tab. If focus is on the first tab, moves focus to the last tab and activates the newly focused tab.
- Right Arrow
Moves focus to the next tab. If focus is on the last tab element, moves focus to the first tab and activates the newly focused tab.
- home
Moves focus to the first tab and activates the newly focused tab.
- end
Moves focus to the last tab and activates the newly focused tab.
- esc
Toggle a skip link that offers to either bypass the Widget or to bring focus back on the first header.
For Accordion
- enter space
When focus is on the Accordion header for a collapsed panel, expands the panel.
When focus is on the Accordion header for an expanded panel, collapses the panel.
- tab
Moves focus to the next focusable element; all focusable elements in the Accordion are included in the page tab sequence.
- shift + tab
Moves focus to the previous focusable element; all focusable elements in the Accordion are included in the page tab sequence.
- Down Arrow
If focus is on an Accordion header, moves focus to the next Accordion header.
If focus is on the last Accordion header, either does nothing or moves focus to the first Accordion header.
- Up Arrow
If focus is on an Accordion header, moves focus to the previous Accordion header.
If focus is on the first Accordion header, either does nothing or moves focus to the last Accordion header.
- home
When focus is on an Accordion header, moves focus to the first Accordion header.
- end
When focus is on an Accordion header, moves focus to the last Accordion header.
- esc
Toggle a skip link that offers to either bypass the Widget or to bring focus back on the first header.
Browser Support
This library relies on ResizeObserver but we have a polyfill for browsers that don't support it—as the table below shows.
Sans Polyfill | With Polyfill | |||||||
---|---|---|---|---|---|---|---|---|
Win | OSX | Android | iOS | Win | OSX | Android | iOS | |
Chrome | 64 | 64 | 85 | 86 | 31 | 31 | 68 | 47 |
Edge | 80 | 80 | 80 | 80 | ||||
Firefox | 69 | 69 | 79 | 42 | 42 | 65 | 7.2 | |
Internet Explorer | x | 10 | ||||||
Opera | 52 | 52 | 17 | 17 | ||||
Safari | x | 13.1 | 13.1 | 6.2 | 6.2 | 7 | ||
Samsung | 9.2 | 4.5 | ||||||
UC Browser | x | 12.1 | 10.4 | |||||
Yandex | x | x | 14.12 | 14.12 |
Notes:
- The polyfill works in Safari 5.1, but in that browser the resizing of the widget is only triggered via
window.resize
. - Accordion icons do not show in IE10. That would require to "dumb down" the styling, going with encodedURI instead of "plain" SVG.
Le Code
There is no wrapper requirement. Anything may go in between your headings. The script wraps that content inside <div>s that become the "panels".
Install
"Old School"
Download the latest release of tabpanelwidget-x.x.x.zip which includes:
- The minified Script
- The minified Polyfill
- A stylesheet (.scss) that contains "variables"
- A minified stylesheet that is the output of the above file
Setup
Wrap your headings and their relevant content inside a div
(or else) to which you apply the class tpw-widget
.
dl
itself.<!-- .tpw-widget -->
<!-- This wrapper can be anything (article/div/dl/whatever) -->
<div class="tpw-widget">
<!--
You CAN use any (and as many) headings (h2/h3/h4/h5/h6)
You MUST use the SAME heading level throughout a Widget
You CAN use a <dl> but it has to be made of dt/dd pairs
"tpw-selected" dictates which panel(s) should be opened
-->
<h3 class="tpw-selected">Lorem</h3>
<!--
Whatever you put in between the headings is wrapped by
the script inside <div>s to become the panels' content
-->
<p>...</p>
<ul>...</ul>
<blockquote>...</blockquote>
<h3>Ipsum</h3>
...
<h3>Dolor</h3>
...
<h3>Sit Amet</h3>
...
</div>
<!-- /.tpw-widget -->
Include the stylesheet in the head
of your document:
<link href="/PATH_TO_FILE/tabpanelwidget.min.css" rel="stylesheet" />
Include this before </body>
:
<script src="/PATH_TO_FILE/tabpanelwidget.min.js"></script>
<script>
// remove this if you do not want to serve the polyfill
if (!window.ResizeObserver) {
const script = document.createElement("script")
script.src = "/PATH_TO_FILE/tabpanelwidget-polyfill.min.js"
document.head.appendChild(src)
}
// Instantiate TabPanelWidget
Tabpanelwidget.autoinstall();
</script>
Or you may choose to load all files from CDN:
<link href="//cdn.jsdelivr.net/npm/tabpanelwidget@1.4.1/dist/tabpanelwidget.min.css" rel="stylesheet" />
<script src="//cdn.jsdelivr.net/npm/tabpanelwidget@1.4.1/dist/tabpanelwidget.min.js"></script>
<script>
// remove this if you do not want to serve the polyfill
if (!window.ResizeObserver) {
const script = document.createElement("script")
script.src = "//cdn.jsdelivr.net/npm/tabpanelwidget@1.4.1/dist/tabpanelwidget-polyfill.min.js"
document.head.appendChild(src)
}
// Instantiate TabPanelWidget
Tabpanelwidget.autoinstall();
</script>
The above will attach the script to all containers with the class tpw-widget
.
Usage
Classes
"Out-of-the-box", the Widget will appear as shown in the Demo section but a few classes give you different options.
All classes are meant to be applied to the Widget (the wrapper) with the exception of tpw-selected
which, applied to a "heading" (or multiple headings in the case of an Accordion), will let you arbitrarily choose which panel(s) to open by default (the one(s) associated with that/those heading(s)).
"Variables"
tabpanelwidget.min.css is the output of tabpanelwidget.scss. The latter contains variables that will let you customize the Widget's tabs, headers, and panels (their color, background, border, border-radius, padding, margin, etc.).
The comments in the SCSS file contain a great deal of information; make sure to check them out!.
"Vanilla"
import * as Tabpanelwidget from "tabpanelwidget"
import "tabpanelwidget/dist/tabpanelwidget.min.css"
// find all .tpw-widget in page and install them
Tabpanelwidget.autoinstall()
// or specify element to install (and uninstall)
const el = document.querySelector('#my-element')
// keep in mind install completes asynchronously so uninstall is returned by promise
const uninstall = await Tabpanelwidget.install(el)
// or worse, can use a callback as second arg (make sure you don't call uninstall before it's set in this case):
// let uninstall; Tabpanelwidget.install(el, _uninstall => (uninstall = _uninstall))
// later...
if (uninstall) uninstall()
"Vue.js"
<script>
import VueTabpanelwidget from "tabpanelwidget/vue"
import "tabpanelwidget/dist/tabpanelwidget.min.css"
Vue.use(VueTabpanelwidget)
// or Vue.component("Tabpanelwidget", VueTabpanelwidget)
// or in component, components: { VueTabpanelwidget, ... }
<Tabpanelwidget :heading="2" :mode="accordion" :selected-idxs="[1]" :tabs="['a', 'b', 'c']" rtl animate skin="pills" icon-style="fancy" centered disconnected icons-at-the-end rounded>
<template v-slot:panel-0="">
override content for panel 0
</template>
</Tabpanelwidget>
React
import ReactTabpanelwidget from "tabpanelwidget/react"
import "tabpanelwidget/dist/tabpanelwidget.min.css"
<ReactTabpanelwidget heading={2} mode={'accordion'} selected-idxs={[1]} rtl animate skin={'pills'} icon-style={'fancy'} centered disconnected icons-at-the-end rounded>
<ReactTabpanelwidget.Heading>heading 1</ReactTabpanelwidget.Heading>
<ReactTabpanelwidget.Panel>panel 1</ReactTabpanelwidget.Panel>
</ReactTabpanelwidget>
Angular
Coming soon! Even sooner if we receive a PR ;)
FAQ
- How come the Headers appear broken in my Widget?
Headers in Accordions are styled with
display:flex
so if you have text nodes and "tags" (i.e.<code>
) as direct children of the "headers" then things may appear very misaligned, whitespace may be missing, etc.To easily fix this, you can simply wrap the content of your headers inside a
<span>
.- How come the Tabs appear broken in my Widget?
If your tabs look broken, it is because there are some CSS rules in your document that are styling the headings in the Widget.
The TabPanelWidget stylesheet tries to prevent such styling by increasing selector specificity and "resetting" common declarations (font-size and margins for example), but things will look broken if other styles target—and overwrite— the styles of the headings used as Tabs/Headers.
See below how you may fix such issues.- How come my Widget appears broken in IE10?
IE 10 does not "support" the
hidden
attribute so if you want to support IE10 and does not include a stylesheet like Normalize, you will need to add the following to your stylesheet:[hidden] {display: none;}
- How can I fix issues due to global styles?
An easy way to prevent your CSS rules from styling the Widget's headings is to rely on the
:not()
pseudo-class. You'd need to attach:not(.tpw-hx)
to the selector in which those headings are the target.
For example, a selector like this for your headings:section h3 {margin: 1rem 0;}
Would become this:
section h3:not(.tpw-hx) {margin: 1rem 0;}
The above will style your level 3 headings inside
section
without styling theh3
used as the Widget's tabs/headers—even if the Widget is inside asection
.Please keep in mind that using
:not()
will bump the specificity of your selector/rule.- Can we style widgets differently on the same page?
Yes, each Widget can be "skinned" individually via various classes. (See the usage section above or the Wiki).
Note that—to improve performance—whatever styles you are not using with your Widget(s) should be deleted from the stylesheet.
- How can I remove the focus ring from the panels?
- The focus ring is meant to help keyboard users navigate through the Widget. If you need to remove that style, you can do one of the following:
:focus .tpw-panel { box-shadow: none !important; outline: none !important; }
:focus:not(:focus-visible), :focus:not(:focus-visible) .tpw-panel { box-shadow: none !important; outline: none !important; }
The former will remove the focus ring for all users, the latter will only remove the focus ring for mouse users (as long as the browser supports
:focus-visible
). - How to prevent reflow below the TabPanel?
Depending on the content of the panels in the Widget you can—to some extent—minimize the reflow (below the Widget) by styling the Widget with a
min-height
, as we do with the TabPanel in the Demo section.- How come I cannot customize the Widget?
You may be trying to style the tabs and/or headers via rules that do not carry enough specificity.
To give a decent "skin" to the Widget "out-of-the-box", we tried to find a balance between unstyling elements that would inherit styles from a document's stylesheets and also make sure to use selectors that would carry enough "weight" (i.e. the specificity of many of our rules is greater than 0,1,1,0 ).
For minor customization, we suggest you use the scss file and edit its variables. For more serious changes, we suggest you edit the rules or declarations directly in the tabpanelwidget.scss stylesheet rather than trying to overwrite styles by writing new rules.
- How come I cannot style the
dt/dd
in the Widget? <dl>
,<dt>
and<dd>
as main elements of a Widget are transformed into<div>
. The reason for this is due to HTML structure. TabPanels need atablist
and it would be malformed HTML to insert an empty<div>
as first-child of the Definition List.Nonetheless, you can style the tabs, headers, and tabpanels using these classes:
.tpw-tab
.tpw-header
.tpw-panel
- How come the Widget does not show on the page?
If the Widget appears to be styled with
visibility:hidden
, it is certainly because your markup is malformed (i.e. if you have a<p>
as direct child of a<dl>
).To prevent this from happening, you can run the markup of your Widget through the validator.
- How can I style the Widget when it is "inactive"?
You can use the selector in the following example to style a Widget (or its children) when it is not displayed as a TabPanel nor an Accordion (when the script has not transformed them):
.tpw-widget:not(.tpw-js) {margin: 0;}
- How can I minimize a FOUC (Flash Of Unstyled Content)?
Obviously, there are a lot of changes in the markup and the styling once the script mounts the Widget(s), but it is possible to reduce the FOUC effect—in modern browsers—by including the following rule in your stylesheet:
html:not(.no-js):not(.tpw-\!fouc) .tpw-widget { visibility: hidden; }
The class
tpw-!fouc
is set by the script while the classno-js
is used as a generic hook to style elements according to script support (if you use a different class to achieve this, then use it in the selector above in-lieu ofno-js
).Note that for the first Widget on this page, we also rely on a
min-height
to minimize the reflow.- How can I change the styling of the tabs, headers, etc.?
You can either edit the value of the variables in the SCSS file (i.e.
$fontSize:1.5rem
) or write new rules. If you choose to do the latter, you can rely on these selectors:.tpw-hx
to target the headings (mostly to reset their styling).tpw-tab
to target the Tabs in the TabPanels.tpw-header
to target the Headers in the Accordions
To style the active tab or header, use these selectors:
.tpw-selected .tpw-tab
.tpw-selected .tpw-header
To target an active panel (or multiple open panels in case of an Accordion), you can use the following:
.tpw-selected + div > .tpw-panel
Note that overriding the styling of headings will require more specificity than overriding the styling of Tabs/Headers via
.tpw-tab
and.tpw-header
. This is because the styling of.tpw-hx
is meant to overwrite styles that could "leak" onto the headings used in the Widget.- Can the Widget interfere with my page's stacking contexts?
The Widget is positioned and styled with
z-index:0
which means the Widget and its children will not show over any other positioned elements in your page that have an explicitz-index
value greater than0
.- How can I "mount" a specific Widget on my page?
- To target a specific Widget, you can do the following:
<script> // TabPanelWidget const uninstall = await Tabpanelwidget.install(document.querySelector('#ThisWidgetOnly')); // or worse, can use a callback as second arg (make sure you don't call uninstall before it's set in this case): // let uninstall; Tabpanelwidget.install(el, _uninstall => (uninstall = _uninstall)) // ... later, if needed uninstall(); </script>
- It's working! What should I do next?
There is a good chance that your Widget uses only a subset of the stylesheet (which includes many different styles for TabPanels and Accordions). So you should trim the stylesheet to keep only the styles you are actually using—to load a much smaller file.