TABPANEL WIDGET (original) (raw)

Demo

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" horizontally.
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.

Bookmark Friendly

The state of the panels is carried through the URL.
Saving or sharing a URL will reflect that state.

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

Browser Support

This library relies on ResizeObserver but we have a polyfill for browsers that don't support it—as the table below shows.

Browser Support

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:

Le Code

Try it on CodePen First!

There is no wrapper requirement. Anything may go in between your headings. The script wraps that content inside

s that become the "panels".

Install

FAQ

How come a bookmarked page opens the default panel?

To be able to preserve a panel state via a URL you need to add the following attribute to the widget: data-tpw-id giving it a unique value (i.e. data-tpw-id="myWidget").

Another reason for this feature to "fail" is if your page is in an iframe or a frameset.

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 the h3 used as the Widget's tabs/headers—even if the Widget is inside a section.

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 a tablist 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:

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 class no-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 of no-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:

To style the active tab or header, use these selectors:

To target an active panel (or multiple open panels in case of an Accordion), you can use the following:

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 explicit z-index value greater than 0.

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>

How come the script makes the anchors in my page fail?

You may encounter this issue when using the bookmark/share feature (data-tpw-id) which is something we do not recommend to use if the page contains anchors.

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.

Don't see your question? Ask on GitHub!