An accordion is a very popular UI component used to organise and hide content to avoid overwhelming the user, but is it that easy to create one? Let's find out.
According to the ARIA design pattern for accordions, you should provide keyboard control, set roles, assign unique IDs, and link them through aria-attributes, sounds complicated, doesn't it?
What if I tell you that you don't have to do anything, all you need to do is create a structure using data-attributes.
Getting Started
By implementing data-attributes, let's go through a basic way to create an accordion that doesn't require additional settings.
<div data-ui-tablist>
<div data-ui-tablist-item>
<button data-ui-tablist-tab>Accordion button #1 <span>↓</span></button>
<div data-ui-tablist-tabpanel hidden>Accordion content #1</div>
</div>
<div data-ui-tablist-item>
<button data-ui-tablist-tab>Accordion button #2 <span>↓</span></button>
<div data-ui-tablist-tabpanel hidden>Accordion content #2</div>
</div>
</div>
After that, install the Jolty and call the static method initAll()
on the Tablist
class.
import { Tablist } from "jolty";
Tablist.initAll();
Now, when clicking on the <button>
, our content will open and close.
Transition
For creating transitions, it uses CSS-Based Transitions, as in Vue.js., This means special CSS classes, responsible for a specific transition phase, are added during opening and closing.
Also, Jolty generates special CSS variables --ui-transition-width
and --ui-transition-height
. This allows us to decide ourselves how to animate our content.
<div data-ui-tablist-tabpanel class="accordion-panel" hidden>...</div>
We can use @media (prefers-reduced-motion: no-preference)
to apply transitions only if the user has not enabled the setting on their device for reduced motion
@media (prefers-reduced-motion: no-preference) {
.accordion-panel.ui-enter-active,
.accordion-panel.ui-leave-active {
transition-duration: 200ms;
transition-property: height, opacity;
transition-timing-function: ease-in-out;
overflow: hidden;
}
.accordion-panel.ui-enter-to,
.accordion-panel.ui-leave-from {
height: var(--ui-transition-height);
}
.accordion-panel.ui-enter-from,
.accordion-panel.ui-leave-to {
height: 0;
opacity: 0;
}
}
If we want to use the padding
CSS property, this element must be placed inside the tabpanel
.
<div data-ui-tablist-tabpanel class="accordion-panel" hidden>
<div class="accordion-panel-inner">...</div>
</div>
.accordion-panel-inner {
padding: 1.25rem;
}
Nested Accordion
To create nested accordions, you don't need to set additional configurations, just ensure that each level contains its own tablist
wrapper.
<div data-ui-tablist>
<div data-ui-tablist-item>
<button data-ui-tablist-tab>Accordion button level 1 <span>↓</span></button>
<div data-ui-tablist-tabpanel hidden>
Accordion content level 1
<div data-ui-tablist>
<div data-ui-tablist-item>
<button data-ui-tablist-tab>Accordion button level 2<span>↓</span></button>
<div data-ui-tablist-tabpanel hidden>Accordion content level 2</div>
</div>
...
</div>
</div>
</div>
...
</div>
Hash navigation
If you want the tabpanel
to open when the URL contains a hash with its id
, you can assign it a unique id
, and for tab
set the data-ui-tablist-tab="{id}"
attribute.
<div data-ui-tablist>
<div data-ui-tablist-item>
<button data-ui-tablist-tab="my-tabpanel">Accordion button #1 <span>↓</span></button>
<div id="my-tabpanel" hidden>Accordion content #1</div>
</div>
...
</div>
Custom structure
If you do not want to use data-attributes, you can define your own selectors by modifying the tab
and tabpanel
options.
<div class="faq-list" id="faq">
<button class="faq-tab">How do I track my order?</button>
<div class="faq-tabpanel" hidden>...</div>
<button class="faq-tab">How do I reset my password</button>
<div class="faq-tabpanel" hidden>...</div>
<button class="faq-tab">Can I change my order after placing it?</button>
<div class="faq-tabpanel" hidden>...</div>
</div>
new Tablist('#faq', {
tab: ".faq-tab",
tabpanel: ".faq-tabpanel",
});
Settings Groups
But what if we do not want to specify unique ids or want to use the same settings for other accordions? There is a solution for this - use the static method Tablist.data()
.
Let's rewrite our example above to get rid of the id
.
<div class="faq-list" data-ui-tablist="faq">...</div>
Tablist.data('faq', {
tab: ".faq-tab",
tabpanel: ".faq-tabpanel",
});
Tablist.initAll();
SEO-friendly structure and @media scripting: none
As you may have noticed, we used a <button>
for the tab in the example above, which is unsuitable for search bots. Let's change the <button>
to an <h3>
tag.
<div class="faq-list" id="faq">
<h3 class="faq-tab">How do I track my order?</h3>
<div class="faq-tabpanel">...</div>
<h3 class="faq-tab">How do I reset my password</h3>
<div class="faq-tabpanel">...</div>
<h3 class="faq-tab">Can I change my order after placing it?</h3>
<div class="faq-tabpanel">...</div>
</div>
We can also make use of the new @media scripting now.
Browser support.
As you may have noticed above, we removed the hidden
attribute. Instead, we will use the option hideMode: 'class-shown'
. In this case, Jolty will check whether the tabpanel
contains the .ui-shown
class. If not, it will be considered closed.
Tablist.data('faq', {
tab: ".faq-tab",
tabpanel: ".faq-tabpanel",
hideMode: "class-shown",
});
Tablist.initAll();
Let's define styles for the .ui-shown
class.
.faq-tabpanel:where(:not(.ui-shown)) {
display: none;
/* If JavaScript is not enabled in the browser, don't hide it */
@media (scripting: none) {
display: initial;
}
}
Since @media (scripting: none)
still doesn't have broad support, we utilise progressive enhancement, applying styles only when it is supported, therefore we use display: initial
, but you can use any other value.
Breakpoints
Jolty allows us to set settings for a specific screen size through the breakpoints
option, or destroy the element at the needed breakpoint. Let's do this for screens larger than 640px.
To do this, we will create a new settings group 'faq-mobile'
and inherit settings from the 'faq'
group.
Tablist.data('faq-mobile', {
data: 'faq'
breakpoints: {
640: {
destroy: true,
},
},
});
Tablist.initAll();
Multi-expanded Option
By default, when a user opens a new tab, the previous one will automatically close. Enabling the multiExpanded
option will keep the previous tabs open.
<div data-ui-tablist data-ui-multi-expanded>...</div>
Always-expanded Option
By default, a user can close all tabs, but if you want always to have at least one tab remain open, you should set the alwaysExpanded: true
option.
<div data-ui-tablist data-ui-always-expanded>...</div>
Using hidden=until-found attribute
The following example works only in browsers on the Blink engine.
Browser support
If the content is hidden, it will not be accessible using a find-in-page search. To remedy this, we can use the hidden=until-found
attribute for tabpanel
.
<div class="faq-list" data-ui-tablist>
<button data-ui-tablist-tab>How do I track my order?</button>
<div data-ui-tablist-tabpanel hidden="until-found">...</div>
<button data-ui-tablist-tab>How do I reset my password</button>
<div data-ui-tablist-tabpanel hidden="until-found">...</div>
<button data-ui-tablist-tab class="faq-tab">Can I change my order after placing it?</button>
<div data-ui-tablist-tabpanel hidden="until-found">...</div>
</div>
To Be Continued
This article explored an example of creating an accordion using the Tablist
class. Next, we will discuss creating tabs using the same component.
Follow us to ensure you don't miss the second part; meanwhile, you can read the documentation on our website.
Top comments (0)