Accordion v2.9.1
Organize content into expandable and collapsible sections, allowing users to reveal or hide information as needed.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.
-
Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nuctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.
-
Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Praesent non odio bibendum magna bibendum accumsan.
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus. Nulla dui metus, placerat vel leo scelerisque, pellentesque malesuada ante. Curabitur vitae arcu non ex lobortis vestibulum et ac enim. Sed feugiat, sapien vel placerat fringilla, dolor orci blandit neque, pretium varius nisi odio vitae quam. Suspendisse id justo ultricies, consequat dui id, vulputate diam. Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Dolor sit amet, nte erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus. Nulla dui metus, placerat vel leo scelerisque, pellentesque malesuada ante.
Note: Please note that this is a basic example of accordion functionality. Adding additional markup, such as headings, can enhance the component's accessibility and make it more understandable to assistive technology users. See the Interactive Headings (FAQ) example for more details.
<div class="accordion" data-arrow-inline-end>
<ul role="list">
<li>
<button class="accordion__toggle" id="default-1">Ante Erat</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="default-2">Consectetur Adipiscing</button>
<div class="accordion__panel">
<p>Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="default-3">Jacus Velit</button>
<div class="accordion__panel">
<p>Consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nuctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="default-4">Ultricies</button>
<div class="accordion__panel">
<p>Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="default-5">Magna</button>
<div class="accordion__panel">
<p>Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus. Nulla dui metus, placerat vel leo scelerisque, pellentesque malesuada ante. Curabitur vitae arcu non ex lobortis vestibulum et ac enim. Sed feugiat, sapien vel placerat fringilla, dolor orci blandit neque, pretium varius nisi odio vitae quam. Suspendisse id justo ultricies, consequat dui id, vulputate diam. Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="default-6">Volutpat Mi</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="default-7">Tortor Pharetra</button>
<div class="accordion__panel">
<p>Dolor sit amet, nte erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus. Nulla dui metus, placerat vel leo scelerisque, pellentesque malesuada ante.</p>
</div>
</li>
</ul>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
-
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan. Consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Donsequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Donsequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
Reminder: Do not confuse a horizontal accordion with a tabbed interface—they are not the same. The key difference lies in their behavior. If you require a content section to always remain open and never be closed, then please use the TabList component.
Experimental: Please note that this example uses the new @starting-style at-rule and transition-behavior: allow-discrete; to produce animation. This technique is supported in most major browsers. If you require specific animations in non-supporting browserss, you will need to come up with a traditional implementation.
<div class="accordion" data-animation>
<ul role="list">
<li>
<button class="accordion__toggle" id="horizontal-animated-1">Ante Erat</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-animated-2">Consectetur Adipiscing</button>
<div class="accordion__panel">
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-animated-3">Jacus Velit</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan. Consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-animated-4">Ultricies</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-animated-5">Magna</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Donsequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-animated-6">Volutpat Mi</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-animated-7">Tortor Pharetra</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-animated-8">Voluptatem</button>
<div class="accordion__panel">
<p>Donsequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.</p>
</div>
</li>
</ul>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Note: Please note that only one accordion may be opened on page load. This content can be closed and should not be confused with the TabList, which may never have collapsed content.
<div class="accordion">
<button class="accordion__toggle" data-open id="default-opened-1">Ante Erat</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
<button class="accordion__toggle" id="default-opened-2">Consectetur Adipiscing</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
<button class="accordion__toggle" id="default-opened-3">Jacus Velit</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
<button class="accordion__toggle" id="default-opened-4">Ultricies</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
<button class="accordion__toggle" id="default-opened-5">Magna</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Note: By default, clicking a button adds an anchor to the URL, but this can be disabled if needed. However, anchors are useful for sharing specific content and tracking, so they should only be removed with a valid reason.
<div class="accordion" data-disable-anchor>
<ul role="list">
<li>
<button class="accordion__toggle" id="disable-anchor-1">Ante Erat</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="disable-anchor-2">Consectetur Adipiscing</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="disable-anchor-3">Jacus Velit</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="disable-anchor-4">Ultricies</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="disable-anchor-5">Magna</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="disable-anchor-6">Volutpat Mi</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
</ul>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();-
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
-
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
-
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
-
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
<div class="accordion" data-fixed-height>
<ul role="list">
<li>
<button class="accordion__toggle" id="fixed-height-1">Ante Erat</button>
<div class="accordion__panel">
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="fixed-height-2">Consectetur Adipiscing</button>
<div class="accordion__panel">
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="fixed-height-3">Jacus Velit</button>
<div class="accordion__panel">
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="fixed-height-4">Ultricies</button>
<div class="accordion__panel">
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
</div>
</li>
</ul>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();Scrollbars & Accessibility Anchor (Scrollbars & Accessibility)
Scrollbars can look and work differently depending on the operating system and browser, which is normal. If you choose to style scrollbars, keep in mind that not all browsers support the same options, so the look and feel may change. For example, Safari may hide scrollbars completely, which is expected. For accessibility, ensure the following:
- The scrubber, track, and background must each have a 3:1 contrast ratio with one another. For example, the scrubber must have a 3:1 contrast with the track, and the track must have a 3:1 contrast with the background beneath it. In some cases, it may be advisable to have the scrubber meet a 4.5:1 ratio with the track, for more solid usability.
- The hover state of the scrubber must meet a 3:1 contrast ratio with the track.
- The scrollbar must always include a 24px by 24px target area size. This isn't an issue for horizontal spacing on horizontal scrollbars, but be mindful of vertical spacing—if the scrollbar is too close to other interactive elements above or below it, it may be harder to use and will fail Target Size (Minimum) (Level AA). The opposite would be true on vertial scrollbars.
- There are no recommended dimensions for scrollbar scrubber height (horizontal) or width (vertical), but larger sizes would obviously be more noticeable and easier to use.
- While customizing scrollbars is permitted it is not generally recommended. Using native scrollbars is usually the most usable and accessible option. See Don't use custom CSS scrollbars for more details on why.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.
-
Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nuctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.
-
Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Praesent non odio bibendum magna bibendum accumsan.
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus. Nulla dui metus, placerat vel leo scelerisque, pellentesque malesuada ante. Curabitur vitae arcu non ex lobortis vestibulum et ac enim. Sed feugiat, sapien vel placerat fringilla, dolor orci blandit neque, pretium varius nisi odio vitae quam. Suspendisse id justo ultricies, consequat dui id, vulputate diam. Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Dolor sit amet, nte erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus. Nulla dui metus, placerat vel leo scelerisque, pellentesque malesuada ante.
Reminder: Do not confuse a horizontal accordion with a tabbed interface—they are not the same. The key difference lies in their behavior. If you require a content section to always remain open and never be closed, then please use the TabList component.
<div class="accordion" data-horizontal>
<ul role="list">
<li>
<button class="accordion__toggle" id="horizontal-1">Ante Erat</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-2">Consectetur Adipiscing</button>
<div class="accordion__panel">
<p>Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-3">Jacus Velit</button>
<div class="accordion__panel">
<p>Consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nuctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-4">Ultricies</button>
<div class="accordion__panel">
<p>Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-5">Magna</button>
<div class="accordion__panel">
<p>Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus. Nulla dui metus, placerat vel leo scelerisque, pellentesque malesuada ante. Curabitur vitae arcu non ex lobortis vestibulum et ac enim. Sed feugiat, sapien vel placerat fringilla, dolor orci blandit neque, pretium varius nisi odio vitae quam. Suspendisse id justo ultricies, consequat dui id, vulputate diam. Pellentesque imperdiet dui et neque pulvinar, nec egestas urna mattis.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-6">Volutpat Mi</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-7">Tortor Pharetra</button>
<div class="accordion__panel">
<p>Dolor sit amet, nte erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Fusce vel quam sollicitudin, semper lacus ac, malesuada mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent dapibus mi dui, imperdiet tempor tortor malesuada a. Mauris elementum nisi at leo molestie finibus. Nulla dui metus, placerat vel leo scelerisque, pellentesque malesuada ante.</p>
</div>
</li>
</ul>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();-
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan. Consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Donsequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Donsequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
Reminder: Do not confuse a horizontal accordion with a tabbed interface—they are not the same. The key difference lies in their behavior. If you require a content section to always remain open and never be closed, then please use the TabList component.
<div class="accordion" data-animation data-horizontal data-pointer>
<ul role="list">
<li>
<button class="accordion__toggle" id="horizontal-pointer-1">Consectetur Adipiscing</button>
<div class="accordion__panel">
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-pointer-2">Ante Erat</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-pointer-3">Ultricies</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-pointer-4">Jacus Velit</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan. Consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-pointer-5">Magna</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Donsequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-pointer-6">Volutpat Mi</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-pointer-7">Tortor Pharetra</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-pointer-8">Voluptatem</button>
<div class="accordion__panel">
<p>Donsequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.</p>
</div>
</li>
</ul>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();My FAQ
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Note: In this example, take note of the headings used to wrap the button elements, creating a more logical, interactive, and navigable structure for assistive technology users. Default list markup is also present to help convey the number of accordion items there are, and wrapping the accordion in a section element connected to the "My FAQ" heading via aria-labelledby creates a landmark region for the grouping. Together, these techniques improve the overall understanding of the component for users with certain disabilities, specifically blind and low vision users. Please watch the demo to see this behavior in action.
<div aria-labelledby="faq-heading" class="accordion" role="region">
<h2 id="faq-heading">My FAQ</h2>
<ul role="list">
<li>
<h3 class="accordion__heading"><button class="accordion__toggle" id="headings-1"><span>What is the airspeed velocity of an unladen swallow?</span></button></h3>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<h3 class="accordion__heading"><button class="accordion__toggle" id="headings-2"><span>Is the glass half empty, half full, or just glad it isn't spilled yet?</span></button></h3>
<div class="accordion__panel">
<p>Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<h3 class="accordion__heading"><button class="accordion__toggle" id="headings-3"><span>If a fly loses its wings, is it now a walk?</span></button></h3>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<h3 class="accordion__heading"><button class="accordion__toggle" id="headings-4"><span>If the universe is infinite, does that mean somewhere out there, a planet exists where socks never go missing?</span></button></h3>
<div class="accordion__panel">
<p>Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<h3 class="accordion__heading"><button class="accordion__toggle" id="headings-5"><span>If a sandwich is cut in half, does each half become a new sandwich, or are they just pieces of the original sandwich in denial?</span></button></h3>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<h3 class="accordion__heading"><button class="accordion__toggle" id="headings-6"><span>If time is money, does that mean ATMs are time machines?</span></button></h3>
<div class="accordion__panel">
<p>Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<h3 class="accordion__heading"><button class="accordion__toggle" id="headings-7"><span>If a tree falls in the forest and no one is around to hear it, does it sigh in relief that no one saw it trip?</span></button></h3>
<div class="accordion__panel">
<p>Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
</ul>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();-
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Note: Ideally, only one accordion section should be open at a time. However, there may be cases where allowing multiple sections to be open simultaneously is desirable, such as when a user wants to compare or search through the content.
<div class="accordion" data-multiple>
<ul role="list">
<li>
<button class="accordion__toggle" id="multiple-open-1">Ante Erat</button>
<div class="accordion__panel">
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="multiple-open-2">Consectetur Adipiscing</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="multiple-open-3">Jacus Velit</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="multiple-open-4">Ultricies</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="multiple-open-5">Magna</button>
<div class="accordion__panel">
<p>Consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="multiple-open-6">Volutpat Mi</button>
<div class="accordion__panel">
<p>Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="multiple-open-7">Tortor Pharetra</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
</ul>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();-
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
<div class="accordion" data-multiple>
<button class="accordion__toggle-all">Show All</button>
<ul role="list">
<li>
<button class="accordion__toggle" id="toggle-all-1">Ante Erat</button>
<div class="accordion__panel">
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="toggle-all-2">Consectetur Adipiscing</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="toggle-all-3">Jacus Velit</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="toggle-all-4">Ultricies</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="toggle-all-5">Magna</button>
<div class="accordion__panel">
<p>Consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="toggle-all-6">Volutpat Mi</button>
<div class="accordion__panel">
<p>Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="toggle-all-7">Tortor Pharetra</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
</ul>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();Note: In some scenarios, we may only want the accordion to be present on smaller viewports. In such cases, it is crucial to ensure that accessibility on larger screens is not compromised by leaving buttons accessible to assistive technologies. To see this solution in action, try pressing or tabbing to the headings in larger viewport sizes. As you'll notice, the headings are not interactive and all content that would normally be collapsed is revealed, which is exactly what we want. However, when you change the viewport to a smaller size, the component then becomes a traditional accordion. This kind of behavior can often be found in the footer of applications or website, where space is more limited on smaller screens.
Warning: This approach should be used sparingly. Try to avoid removing interactive components based on screen size, as it can disrupt usability. If a user zooms in and focuses on a button, they will lose focus when zooming out because the button or button visibility is removed, which can be disorienting.
<footer>
<div class="accordion" data-context>
<ul role="list">
<li>
<h3 aria-labelledby="viewport-context-1" class="accordion__heading"><button class="accordion__toggle" id="viewport-context-1"><span>Heading 1</span></button></h3>
<div class="accordion__panel">
<ul>
<li><a href="#!">Consectetur Adipiscing</a></li>
<li><a href="#!">Nam Euismod</a></li>
<li><a href="#!">Ante Erat Imperdiet</a></li>
<li><a href="#!">Nisl Diam Iaculis</a></li>
</ul>
</div>
</li>
<li>
<h3 aria-labelledby="viewport-context-2" class="accordion__heading"><button class="accordion__toggle" id="viewport-context-2"><span>Heading 2</span></button></h3>
<div class="accordion__panel">
<ul>
<li><a href="#!">Adipiscing</a></li>
<li><a href="#!">Ante Imperdiet</a></li>
<li><a href="#!">Nisl Diam</a></li>
</ul>
</div>
</li>
<li>
<h3 aria-labelledby="viewport-context-3" class="accordion__heading"><button class="accordion__toggle" id="viewport-context-3"><span>Heading 3</span></button></h3>
<div class="accordion__panel">
<ul>
<li><a href="#!">Consectetur Euismod</a></li>
<li><a href="#!">Ante Euismod</a></li>
<li><a href="#!">Ante Erat Imperdiet</a></li>
<li><a href="#!">Euismod</a></li>
<li><a href="#!">Imperdiet Erat Iaculis</a></li>
</ul>
</div>
</li>
<li>
<h3 aria-labelledby="viewport-context-4" class="accordion__heading"><button class="accordion__toggle" id="viewport-context-4"><span>Heading 4</span></button></h3>
<div class="accordion__panel">
<ul>
<li><a href="#!">Consectetur Euismod</a></li>
<li><a href="#!">Ante Euismod</a></li>
<li><a href="#!">Erat</a></li>
<li><a href="#!">Euismod Consectetur</a></li>
<li><a href="#!">Erat Iaculis</a></li>
</ul>
</div>
</li>
</ul>
</div>
</footer>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();-
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.
Experimental: Experimental examples or components may require additional work, further testing, and may not be fully supported or accessible. They should be carefully evaluated before being used in a production environment.
Reminder: Do not confuse a horizontal accordion with a tabbed interface—they are not the same. The key difference lies in their behavior. If you require a content section to always remain open and never be closed, then please use the TabList component.
<div class="accordion" data-close data-horizontal data-overlay>
<ul role="list">
<li>
<button class="accordion__toggle" id="horizontal-overlay-1">Ante Erat</button>
<div class="accordion__panel">
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-overlay-2">Consectetur Adipiscing</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-overlay-3">Jacus Velit</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
<p>Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-overlay-4">Ultricies</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-overlay-5">Magna</button>
<div class="accordion__panel">
<p>Consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-overlay-6">Volutpat Mi</button>
<div class="accordion__panel">
<p>Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
<li>
<button class="accordion__toggle" id="horizontal-overlay-7">Tortor Pharetra</button>
<div class="accordion__panel">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#!">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</div>
</li>
</ul>
</div>/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
Dependencies: Sass
*/
.accordion {
// Component Variables
--accordion-arrow-size: 0.8em; // Toggle Arrow Size
--accordion-arrow-border: calc(2em / 16); // Toggle Arrow Border
--accordion-color-base-1: #000; // Base Color 1
--accordion-color-base-2: #fff; // Base Color 2
--accordion-close-size: calc(30em / 16); // Close Button Size
--accordion-color-active: #a9a9a9; // Active State
--accordion-color-default: #d3d3d3; // Default States
--accordion-color-interactive: #666; // Interactive State
--accordion-color-selected: #6F00EF; // Selected States
--accordion-column-large: 4; // Horizontal Layout (Large Vieport)
--accordion-column-medium: 2; // Horizontal Layout (Medium Vieport)
--accordion-gap: calc(5em / 16); // Accordion Gap
--accordion-panel-border: calc(2em /16) transparent solid; // Sets border on pointer and panel. Not for use with default animation
--accordion-panel-default: #d3d3d3; // Panel State (Default)
--accordion-pointer-size: calc(25em / 16); // Content Pointer Size
--accordion-toggle-border-radius: 0; // Button Border Radius
// Breakpoints (CSS variables are not allowed in media queries - boo!)
$accordion-large: 55em; // Large Viewport
$accordion-medium: 37.5em; // Medium Viewport
display: grid;
gap: var(--accordion-gap);
// Accordion Defaults & Resets: Do not remove!
> ol, > ul, > dl, > ul > li, &__heading {
all: unset;
}
> ol, > ul, > dl, > ul > li{
display: contents;
}
// Horizontal Layout
&[data-horizontal] {
@media (min-width: $accordion-medium) {
grid-auto-flow: dense;
grid-template-columns: repeat(var(--accordion-column-medium), 1fr);
}
@media (min-width: $accordion-large) {
grid-template-columns: repeat(var(--accordion-column-large), 1fr);
}
}
// Accordion Toggle Arrow
&__arrow {
align-self: start;
block-size: var(--accordion-arrow-size);
border-width: var(--accordion-arrow-border);
border-block-color: transparent var(--accordion-color-selected);
border-block-start-width: 0;
border-inline-color: var(--accordion-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--accordion-arrow-size);
margin-block: 0 calc(4em / 16);
transform: rotate(-45deg);
[data-context] & {
@media (min-width: $accordion-medium) {
display: none;
}
}
[data-arrow-inline-end] & {
justify-self: end;
}
}
// Accordion Close Button
&__close {
background-color: transparent;
block-size: var(--accordion-close-size);
border: 0;
cursor: pointer;
display: grid;
float: inline-end;
inline-size: var(--accordion-close-size);
justify-items: center;
margin: 0;
margin-block-end: 1em;
margin-inline-start: 1em;
padding: 0;
&:after, &::before {
background-color: var(--accordion-color-base-1);
block-size: 100%;
content: "";
display: block;
grid-area: 1 / 1;
inline-size: calc(2em / 16);
}
&::after {
transform: rotate(-45deg);
}
&::before {
transform: rotate(45deg);
}
}
// Accordion Toggle & Toggle All Button
&__toggle, &__toggle-all {
align-items: center;
background-color: var(--accordion-color-default);
border-radius: var(--accordion-toggle-border-radius);
border: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
gap: 1em;
grid-template-columns: auto 1fr;
inline-size: 100%;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
text-wrap: balance;
// Multi-purpose Span
span:not(.accordion__arrow) {
line-height: 1.5;
}
// Accordion Animation
[data-animation] & {
@media (prefers-reduced-motion: no-preference) {
transition: 300ms;
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
pointer-events: none;
visibility: hidden;
span {
color: var(--accordion-color-selected);
visibility: visible;
}
}
}
// Horizontal Layout
[data-horizontal] & {
@media (min-width: $accordion-medium) {
padding: 1.5em;
}
}
// Horizontal Layout (Overlay): Hides buttons when overlay is "active"
[data-overlay][data-active] & {
display: none;
}
// Content Pointer
[data-pointer] & {
&::after {
background-color: var(--accordion-panel-default);
block-size: var(--accordion-pointer-size);
border-inline-start: var(--accordion-panel-border);
border-block-start: var(--accordion-panel-border);
content: "";
display: none;
inline-size: var(--accordion-pointer-size);
inset-block-end: calc(var(--accordion-pointer-size) * -1);
inset-inline-end: 0;
inset-inline-start: 50%;
pointer-events: none;
position: absolute;
transform: rotate(45deg) translateX(-50%);
z-index: 1;
// Accordion Animation
[data-animation] & {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
// The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or
// design an alternative focus state. Learn: https://webaim.org/articles/contrast/
&:focus-visible, &:hover {
background-color: var(--accordion-color-active);
}
&:focus-visible {
outline-offset: -5px;
}
// Accordion Toggle Button: Behavior
&[aria-expanded="true"], &[aria-pressed="true"] {
background-color: var(--accordion-color-selected);
color: var(--accordion-color-base-2);
[data-animation][data-pointer] & {
// Content Pointer: Animated
&::after {
translate: 0 .1rem;
@media (prefers-reduced-motion: no-preference) {
transition: translate 800ms, display 0s allow-discrete;
}
@starting-style {
translate: 0 var(--accordion-pointer-size);
}
}
}
// Conent Pointer
[data-pointer] & {
&::after {
display: block;
}
}
// Accordion Toggle Arrow
.accordion__arrow {
border-block-end-color: var(--accordion-color-base-2);
border-inline-start-color: var(--accordion-color-base-2);
transform: rotate(135deg);
translate: 0 calc(8rem/16);
[data-pointer] & {
visibility: hidden;
}
}
}
}
// Accordion Toggle All Button
&__toggle-all {
inline-size: fit-content;
justify-self: end;
margin-block: 0 1em;
}
// Accordion Panel
&__panel {
background-color: var(--accordion-panel-default);
display: none;
margin-block-start: calc(var(--accordion-gap) * -1);
overflow: hidden;
padding: 1em;
position: relative;
// Accordion Animation
[data-animation] & {
max-block-size: 0;
@media (prefers-reduced-motion: no-preference) {
transition: display 0s, max-block-size 2s ease-in-out allow-discrete;
}
}
// Horizontal Layout
[data-horizontal] & {
border: var(--accordion-panel-border);
margin-block-start: auto;
@media (min-width: $accordion-medium) {
grid-column: 1 / span var(--accordion-column-medium);
}
@media (min-width: $accordion-large) {
grid-column: 1 / span var(--accordion-column-large);
}
}
// Fixed Height
[data-fixed-height] & {
max-block-size: 18.75em;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
// Fallback Scrollbar
scrollbar-color: var(--accordion-color-active) var(--accordion-color-default);
scrollbar-width: thin;
@supports #{"\selector(::-webkit-scrollbar)"} {
scrollbar-color: initial;
scrollbar-width: auto;
padding-inline-end: 0.5em;
&::-webkit-scrollbar {
width: calc(18rem / 16);
}
&::-webkit-scrollbar-track {
background-color: var(--accordion-color-default);
margin-block: 0.5em;
}
&::-webkit-scrollbar-thumb {
background-color: var(--accordion-color-active);
border: calc(6rem / 16) var(--accordion-color-default) solid;
border-radius: 100vw;
min-block-size: calc(100rem / 16);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--accordion-color-interactive);
}
}
}
// Viewport Context
[data-context] & {
@media (min-width: $accordion-medium) {
background-color: transparent;
display: block;
}
}
}
// Accordion Panel Reveal
&__toggle[aria-expanded="true"] + &__panel, &__heading:has(&__toggle[aria-expanded="true"]) + &__panel {
display: block;
[data-animation] & {
max-block-size: 5000px;
@starting-style {
max-block-size: 1px;
}
}
}
}/*!
Radancy Component Library: Accordion
Contributor(s):
Michael "Spell" Spellacy
*/
(() => {
"use strict";
const loadLanguagePack = (url, callback) => {
// Install Language Pack.
const getComponentLanguagePack = document.getElementById("component-library-language-pack");
if (!getComponentLanguagePack) {
const componentLanguagePack = document.createElement("script");
componentLanguagePack.setAttribute("src", url);
componentLanguagePack.setAttribute("id", "component-library-language-pack");
componentLanguagePack.addEventListener("load", callback);
document.head.appendChild(componentLanguagePack);
} else {
getComponentLanguagePack.addEventListener("load", callback);
}
};
const initAccordion = () => {
loadLanguagePack("https://services.tmpwebeng.com/component-library/language-pack.js", () => {
// Display which version is in use via console:
console.log("%cAccordion%cv2.9.1", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");
// Classes, data attributes, states, and strings.
const accordionClass = ".accordion";
const accordionCloseClassName = "accordion__close";
const accordionCloseClass = `.${accordionCloseClassName}`;
const accordionToggleClassName = "accordion__toggle";
const accordionArrowClassName = "accordion__arrow";
const accordionToggleClass = `.${accordionToggleClassName}`;
const accordionToggleAllClass = ".accordion__toggle-all";
const accordionHeadingClassName = "accordion__heading";
const accordionPanelClass = ".accordion__panel";
const accordionDataActiveState = "data-active";
const accordionDataDefaultOpen = "data-open";
const accordionDataOverlay = "data-overlay";
const accordionDataCloseButton = "data-close";
const accordionDataDisableAnchor = "data-disable-anchor";
const accordionDataFixedHeight = "data-fixed-height";
const accordionDataMultiOpen = "data-multiple";
const accordionDataRemoveArrow = "data-remove-arrow";
const accordions = document.querySelectorAll(accordionClass);
const URLFragment = location.hash.slice(1);
// Language
const accordionCloseButtonLabel = window.accordionCloseButtonLabel;
// Loop through and set up all accordions on the page.
accordions.forEach((accordion, index) => {
// Set unique ID on all accordions.
accordion.setAttribute("id", `accordion-${index + 1}`);
// Get all buttons within accordion.
const accordionToggles = accordion.querySelectorAll(accordionToggleClass);
// Get all panels within accordion.
const accordionPanels = accordion.querySelectorAll(accordionPanelClass);
// Set variable for selected button target.
let expandedButton = null;
// Get "Toggle All " button.
const btnToggleAll = accordion.querySelector(accordionToggleAllClass);
// Loop through each toggle button.
accordionToggles.forEach(btn => {
// Get button ID. Remember: ID's should always be unique.
const buttonID = btn.getAttribute("id");
// Set up each button.
btn.setAttribute("aria-controls", `accordion-${buttonID}`);
btn.setAttribute("aria-expanded", "false");
// Add Toggle Arrow
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleState = document.createElement("span");
toggleState.setAttribute("aria-hidden", "true");
toggleState.classList.add(accordionArrowClassName);
btn.prepend(toggleState);
}
// Handle button click.
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
if (!accordion.hasAttribute(accordionDataMultiOpen)) {
accordionToggles.forEach(button => {
button.setAttribute("aria-expanded", "false");
});
}
btn.setAttribute("aria-expanded", isExpanded ? "false" : "true");
// Add "data-active" attribute on the parent accordion. Might be useful to achieve interesting UX.
accordion.setAttribute(accordionDataActiveState, "");
// If "Toggle All" button is present, then always set it to false.
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
}
// Place focus on close button if present.
if (accordion.hasAttribute(accordionDataCloseButton)) {
btn.nextElementSibling.querySelector(accordionCloseClass).focus();
}
// Add URL Fragment to URL if not disabled.
if (!accordion.hasAttribute(accordionDataDisableAnchor)) {
history.pushState(null, null, `#${buttonID}`);
}
});
// Default open based on URL fragment or data-open attribute.
if (buttonID === URLFragment || (!expandedButton && btn.hasAttribute(accordionDataDefaultOpen))) {
// Add "data-active" to parent.
accordion.setAttribute(accordionDataActiveState, "");
// Open targeted element.
expandedButton = btn;
}
});
if (expandedButton) {
expandedButton.setAttribute("aria-expanded", "true");
}
// If "Toggle All" button is present...
if (btnToggleAll) {
btnToggleAll.setAttribute("aria-pressed", "false");
if (!accordion.hasAttribute(accordionDataRemoveArrow)) {
const toggleAllState = document.createElement("span");
toggleAllState.setAttribute("aria-hidden", "true");
toggleAllState.classList.add(accordionArrowClassName);
btnToggleAll.append(toggleAllState);
}
// Toggle All Event
btnToggleAll.addEventListener("click", function () {
const isPressed = this.getAttribute("aria-pressed");
this.setAttribute("aria-pressed", isPressed === "true" ? "false" : "true");
// Get all accordion buttons and handle their state based on toggle button state.
accordionToggles.forEach(btn => {
btn.setAttribute("aria-expanded", isPressed === "true" ? "false" : "true");
});
});
}
// Loop through each panel.
accordionPanels.forEach(panel => {
// Set up each disclosure.
const currentPanel = panel.previousElementSibling;
let panelID;
if (currentPanel.classList.contains(accordionHeadingClassName)) {
panelID = currentPanel.querySelector(accordionToggleClass).getAttribute("id");
} else {
panelID = currentPanel.getAttribute("id");
}
panel.setAttribute("id", `accordion-${panelID}`);
// To better support scrolling and repositioned focus, the panels should have a proper role and accName.
if (accordion.hasAttribute(accordionDataFixedHeight) || accordion.hasAttribute(accordionDataOverlay)) {
panel.setAttribute("role", "region");
panel.setAttribute("aria-labelledby", panelID);
}
// To better support inner-scrolling, the panels must be focusable.
if (accordion.hasAttribute(accordionDataFixedHeight)) {
panel.setAttribute("tabindex", "0");
}
// Add close button
if (accordion.hasAttribute(accordionDataCloseButton)) {
const closeButton = document.createElement("button");
closeButton.setAttribute("aria-label", accordionCloseButtonLabel);
closeButton.classList.add(accordionCloseClassName);
closeButton.addEventListener("click", () => {
const thisButton = panel.previousElementSibling;
// Remove "data-active" attribute.
accordion.removeAttribute(accordionDataActiveState);
// Reset button state, move focus to button that initiated click.
thisButton.setAttribute("aria-expanded", "false");
thisButton.focus();
});
panel.prepend(closeButton);
}
});
});
// Check for duplicate IDs and log a warning
const buttonIDs = Array.from(document.querySelectorAll(accordionToggleClass)).map(btn => btn.id);
buttonIDs.forEach((id, index) => {
if (buttonIDs.indexOf(id, index + 1) !== -1) {
console.warn(`%c Warning: Duplicate Accordion ID found: #${id}. Accordions must have unique ID values.`, "background: #ff0000; color: #fff");
}
});
});
};
initAccordion();
})();Properties Anchor (Properties)
Use the following properties to configure this component.
| Element | Note | Description |
|---|---|---|
button[.accordion__toggle-btn] |
Optional | Required as a direct child of .accordion when a "Show All" button is desired. Must also be used with data-multiple and may not be used with data-horizontal. |
| Attribute | Applies To | Note | Description |
|---|---|---|---|
data-active |
.accordion |
Component | Applied by the script. Can be used as a hook for various styling possibilities. |
data-animation |
.accordion |
Boolean, Optional | Used to enable content to be animated when opened. Please note that the default animation leverages @starting-style at-rule. |
data-close |
.accordion |
Boolean, Optional | Specifies that each accordion should have its own close button. By default, no close buttons are provided. Should only be used with data-overlay. |
data-context |
.accordion |
Boolean, Optional | Used to allow the accordion to become non-interactive on larger viewport sizes, while maintaining interactivity on mobile. Often used in mobile footers. |
data-disable-anchor |
.accordion |
Boolean, Optional | Used to disable the anchor that appears in the address bar when a button is pressed. Do note that including an anchor can be useful for tracking and sharing specific content, so we should only remove when we have a good reason. |
data-fixed-height |
.accordion |
Boolean, Optional | Used to fix the panel content to a specific height. The height may be adusted in the CSS. |
data-horizontal |
.accordion |
Boolean, Optional | Used to apply a horizontal layout to the accordion, which may be adjusted in the CSS. |
data-multiple |
.accordion |
Boolean, Optional | Used to allow multiple accordion disclosures to be opened instead of one at a time. |
data-open |
.accordion__toggle |
Boolean, Optional | Specifies which accordion panel should be open on page load. If not specified, no panels will be open by default. Only one accordion may be opened at a time. |
data-overlay |
.accordion |
Boolean, Optional | Used to allow content to overlay the accordion buttons. This is currently an experimental feature and could, at any time, be removed. Should only be used with data-horizontal. |
data-pointer |
.accordion |
Boolean, Optional | Used to apply a visual pointer that connects the button to the content. In this state, the down arrow, which is usually present on the button, will not be visible as it is unnecessary. The content pointer can be applied to both horizontal and vertical accordions. |
data-remove-arrow |
.accordion |
Boolean, Optional | By default, all buttons include a span element for icons. If this is not desirable, you can remove it by adding this attribute and using your own inline or CSS icon. For custom inline icons, ensure they are hidden from assistive technologies by using aria-hidden="true". |
id |
.accordion__toggle |
String, Mandatory | Each accordion button must have a unique ID with no spaces. The console will display an error if this requirement is not met. Allowing a developer to handle the ID, allows for custom anchoring. |
| Class | Applies To | Note | Description |
|---|---|---|---|
.accordion |
div, section |
Mandatory | Required on the parent element that wraps your content. |
.accordion__toggle |
button |
Mandatory | Required on your accordion button. |
.accordion__panel |
div |
Mandatory | Required on your accordion panel. |
Release Notes Anchor (Release Notes)
Keep up to date on major changes to this component.
- Bug
- June 17, 2026: The property,
grid-auto-columns: max-contentwas breaking the layout. This is likely a longstanding issue that went unnoticed because the button text is typically shorter. When longer text is present, it causes the content to overflow the viewport. Changing the layout togrid-template-columns: auto 1frappears to resolve the issue and provides more consistent behavior. Version increased to 2.9.1. - Maintenance
- December 12, 2025: Removed conditional syntax and restoted
min-widthdue to the admin tool not being able to process it. Boo! Thanks, Paul! - Maintenance
- December 08, 2025: Introduced new base color variables,
--accordion-color-base-1and--accordion-color-base-2, representing black and white values. Refactored existingmin-widthmedia queries to use conditional syntax (width > $accordion-large) for cleaner, more expressive breakpoint logic. - Bug
- February 21, 2025: Removed
display: contentsfrom.accordion__heading. - Bug
- January 28, 2025: Incorrect Sass nesting was permitting CSS to target all span elements on a page without a specifc class. Changed from
span:not(&__arrow)tospan:not(.accordion__arrow). - Note
- April 24, 2025: Upgraded component to ES6.
- Note
- May 13, 2025: Changed
remtoemunits in variables to better support context within web components.
- Feature
- Add dynamic IDs, possibly as default. Developers often forget to change the default ID's provided in these demos and Josh W brought up a good point about accordions used in CMS scenarios (High).
- Behavior
- Add Escape Key functionality to Overlay example (High).
- Behavior
- Add back button support. Currently, the URL fragment changes, but not the content (High).
- Behavior
- Add video support for YouTube, Vimeo, and HTML5. Video needs to reset when accordion is closed. Delievery team to workaround for now (High).
- Feature
- Reinstate callback functionality. While it is no longer needed in this component, it may still be useful to have (Medium).
- Feature
- Consider the possibility of introducing arrow key support. While optional, it might be a nice inclusion (Low).
- Feature
- Consider the possibility of allowing for nested accordions (Medium).
Report Issues
Find a bug? Want a new feature? Report it on JIRA. You're so awesome!