UI Patterns

Expandable section with view/hide allStatusdevelopment

An example showing how to view or hide all sections of an accordion at the same time.

Guidance

This pattern is very similar to the expandable section pattern, but with the addition of a button to show and hide all of the sections at once. This is a useful shortcut for keyboard and screen reader users and follows a pattern currently in use in the GOV.UK design system.

Example

Open in a new tab

Dependencies and installation

PackageInstallation
@stormid/toggle
npm i -S @stormid/toggle

Code

<div class="expand-all js-expand-all"><button type="button" class="expandable-section__btn-all js-expandable-section__btn-all" aria-expanded="false"><span class="expandable-section__btn-all-view">View all <span class="visually-hidden">sections about Lorem Ipsum</span></span><span class="expandable-section__btn-all-hide">Hide all <span class="visually-hidden">sections about Lorem Ipsum</span></span></button><div class="expandable-section"><h2 class="expandable-section__heading"><button type="button" class="expandable-section__btn js-expandable-section__btn-1" aria-expanded="false" aria-controls="section-1">Section title</button></h2><div id="section-1" class="expandable-section__bd js-expandable-section-all" data-toggle="js-expandable-section__btn-1"><p>Aenean id posuere nunc. Donec diam nisl, rhoncus vel faucibus sed, porttitor sit amet ipsum. Donec at venenatis augue. Phasellus consequat lectus non augue vestibulum, at varius diam sagittis. Morbi nec purus augue. Etiam rutrum ullamcorper arcu vitae sollicitudin. Aliquam bibendum suscipit risus, at lacinia tortor efficitur ac.</p><p>Praesent vitae mi nec mauris vehicula ultricies nec ut felis. Nam vel nisi id nunc efficitur fermentum. Vestibulum mollis enim nec ultrices mattis. Curabitur placerat sed nisl lobortis iaculis. Fusce porttitor massa augue, sit amet faucibus enim finibus nec. Maecenas hendrerit metus in justo commodo viverra.</p></div></div><div class="expandable-section"><h2 class="expandable-section__heading"><button type="button" class="expandable-section__btn js-expandable-section__btn-2" aria-expanded="false" aria-controls="section-2">Section title</button></h2><div id="section-2" class="expandable-section__bd js-expandable-section-all" data-toggle="js-expandable-section__btn-2"><p>Aenean id posuere nunc. Donec diam nisl, rhoncus vel faucibus sed, porttitor sit amet ipsum. Donec at venenatis augue. Phasellus consequat lectus non augue vestibulum, at varius diam sagittis. Morbi nec purus augue. Etiam rutrum ullamcorper arcu vitae sollicitudin. Aliquam bibendum suscipit risus, at lacinia tortor efficitur ac.</p><p>Praesent vitae mi nec mauris vehicula ultricies nec ut felis. Nam vel nisi id nunc efficitur fermentum. Vestibulum mollis enim nec ultrices mattis. Curabitur placerat sed nisl lobortis iaculis. Fusce porttitor massa augue, sit amet faucibus enim finibus nec. Maecenas hendrerit metus in justo commodo viverra.</p></div></div><div class="expandable-section"><h2 class="expandable-section__heading"><button type="button" class="expandable-section__btn js-expandable-section__btn-3" aria-expanded="false" aria-controls="section-3">Section title</button></h2><div id="section-3" class="expandable-section__bd js-expandable-section-all" data-toggle="js-expandable-section__btn-3"><p>Aenean id posuere nunc. Donec diam nisl, rhoncus vel faucibus sed, porttitor sit amet ipsum. Donec at venenatis augue. Phasellus consequat lectus non augue vestibulum, at varius diam sagittis. Morbi nec purus augue. Etiam rutrum ullamcorper arcu vitae sollicitudin. Aliquam bibendum suscipit risus, at lacinia tortor efficitur ac.</p><p>Praesent vitae mi nec mauris vehicula ultricies nec ut felis. Nam vel nisi id nunc efficitur fermentum. Vestibulum mollis enim nec ultrices mattis. Curabitur placerat sed nisl lobortis iaculis. Fusce porttitor massa augue, sit amet faucibus enim finibus nec. Maecenas hendrerit metus in justo commodo viverra.</p></div></div><div class="expandable-section"><h2 class="expandable-section__heading"><button type="button" class="expandable-section__btn js-expandable-section__btn-4" aria-expanded="false" aria-controls="section-4">Section title</button></h2><div id="section-4" class="expandable-section__bd js-expandable-section-all" data-toggle="js-expandable-section__btn-4"><p>Aenean id posuere nunc. Donec diam nisl, rhoncus vel faucibus sed, porttitor sit amet ipsum. Donec at venenatis augue. Phasellus consequat lectus non augue vestibulum, at varius diam sagittis. Morbi nec purus augue. Etiam rutrum ullamcorper arcu vitae sollicitudin. Aliquam bibendum suscipit risus, at lacinia tortor efficitur ac.</p><p>Praesent vitae mi nec mauris vehicula ultricies nec ut felis. Nam vel nisi id nunc efficitur fermentum. Vestibulum mollis enim nec ultrices mattis. Curabitur placerat sed nisl lobortis iaculis. Fusce porttitor massa augue, sit amet faucibus enim finibus nec. Maecenas hendrerit metus in justo commodo viverra.</p></div></div></div>
import toggle from '@stormid/toggle';
const SELECTORS = {
    NODE: '.js-expand-all',
    TOGGLES: '.js-expandable-section-all',
    BTN: '.js-expandable-section__btn-all'
}
const CLASSES = {
    OPEN: 'is--open'
}

const checkAllToggles = (toggles) => {
    return toggles.reduce((acc, tog) =>{
        return acc && tog.getState().isOpen;
    }, true);
}

const updateVewButton = (btns, allOpen) => {
    btns.forEach((btn) => {
        btn.setAttribute('aria-expanded', allOpen);
        btn.classList.toggle(CLASSES.OPEN);
    })
}

export const init = () => {

    const nodes = Array.from(document.querySelectorAll(SELECTORS.NODE));
    const initialised = []

    nodes.forEach((node) => {
        if(!node.querySelector(SELECTORS.TOGGLES)) return;
        const state = {
            allOpen: false,
            toggles: toggle(Array.from(node.querySelectorAll(SELECTORS.TOGGLES)), { 
                focus: false, 
                local: true, 
                callback: () => {
                    const newOpenState = checkAllToggles(state.toggles);
                    if(newOpenState !== state.allOpen) {
                        updateVewButton(state.btns, newOpenState);
                        state.allOpen = newOpenState;
                    }
                } }),
            btns: Array.from(node.querySelectorAll(SELECTORS.BTN))
        }

        if(state.toggles.length) {
            state.btns.forEach((btn) => {
                btn.addEventListener('click', () => {
                    state.allOpen = !state.allOpen;
                    updateVewButton(state.btns, state.allOpen);
                    state.toggles.forEach((tog) => {
                        if(tog.getState().isOpen !== state.allOpen) tog.toggle();
                    })
                })
            })
        };  

        initialised.push({
            node: node,
            getState: () => {
                return state;
            }
        })
    })

    return initialised;
};

init();

Acceptance criteria

The following is a list of example acceptance criteria to test against when using this pattern. These critera should test that the specific markup requirements are met, and that the expanding section behaves visually and functionally as expected.

For validation in developer tools / web inspector

For visual validation

For functional validation

References