Disclosure widgets in Vue.js

A disclosure is a foundational widget pattern for accessibility. But how exactly is it structured and how can it be implemented in Vue.js?

In general, a disclosure widget consists of two parts. A container element, starting hidden, and a button above it. When the button is interacted with (that can be a click or touch, Space or Enter keypress), this interaction changes the perceptibility of the container. Or in the words of the WAI ARIA practices:

A disclosure is a button that controls visibility of a section of content. When the controlled content is hidden, it is often styled as a typical push button with a right-pointing arrow or triangle to hint that activating the button will display additional content. When the content is visible, the arrow or triangle typically points down.

Classic use cases for a disclosure widget are accordions, other "content fold-out" sections of pages that only get visible after the user interacted with a trigger, or a type of widget that is often called "navigation menu" – a click on a button reveals a container with navigation items in it.

Regardless of its styling and purpose, and whether it is standalone or part of a construct the way a disclosure widget is built is always the same:

  • As mentioned above, the triggering element has to be a button
  • The disclosed content must come right after the the trigger in the DOM
  • When the disclosed content is hidden, it is hidden from everyone, via applying hidden attribute on it or CSS (display: none): visual users and users of assistive technology alike cannot perceive it before it becomes revealed.
  • The button (not the disclosed content!) has an attribute called aria-expanded, which can take the values of false (the controlled content is not perceivable) and true(the controlled content is perceivable). This state has to be kept up to date.

Here's a minimal example how a disclosure widget could look like in Vue:

<template>
  <div>
    <button @click="open = !open" aria-expanded="open.toString()">
        Trigger
    </button>
    <div :hidden="!open">Content</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      open: false
    };
  }
};
</script>

If you heard about focus management before you may wonder why there is no programmatic change of focus when the content is revealed. It is this way because:

  • aria-expanded indicates a disclosure widget to screen reader users.
  • The controlled content must follow the trigger directly in the DOM.
  • Once the container is disclosed, it and its contents become part of the accessibility tree, therefore perceivable for screen reader users. Just as for visual users, there is content appearing in close proximity to the button right after they interacted with it.

These three bullet points alone make up the disclosure widget concept. It holds valid in general, but in particular with regard to accessibility: You should not complicate things without need.