Accessible Inline Spoiler

by Kai Kubasta

HTML

<h1>Accessible Inline Spoiler</h1>

<p>I never thought that
  <button
    class="spoiler"
    aria-role="switch"
    aria-pressed="false"
    aria-live="polite"
    aria-label="Spoiler"
    data-showText="Click to reveal text"
    data-hideText="Click to hide text"
  >
    <span
      class="spoiler__text"
      data-text="Gandalf is Harry Potter's father">
    </span>
  </button>.
</p>

CSS

body {
  background-color: white;
  color: black;
  font: 1rem/1.5 sans-serif;
  padding: 2rem;
  margin: 0;
}

h1 {
  margin: 0;
}

a {
  color: blue;
}

button {
  color: black;
}

*:focus {
  outline: 2px solid blue;
}

.spoiler {
  background: transparent;
  font: 1rem/1.5 sans-serif;
	margin: 0;
  padding: 0;
	border: 0;
	appearance: none;
  cursor: pointer;
}

.spoiler:focus {
  outline-offset: 2px;
}

[aria-pressed="false"] .spoiler__text {
  background-color: currentColor;
}

[aria-pressed="false"] .spoiler__text::before {
  content: attr(data-text);
}

[aria-pressed="true"] .spoiler__text {
  text-decoration: underline;
  text-decoration-style: dotted;
  text-decoration-thickness: 2px;
  text-decoration-color: gray;
}

JavaScript

const $spoilers = document.querySelectorAll('.spoiler');

$spoilers.forEach(($spoiler) => {
	const label = $spoiler.getAttribute('aria-label');
  const showText = $spoiler.dataset.showtext;
  const hideText = $spoiler.dataset.hidetext;

  const $text = $spoiler.querySelector('.spoiler__text');
  const spoiler = $text.dataset.text;

  $spoiler.title = showText;

  $spoiler.addEventListener('click', () => {
    if($spoiler.getAttribute('aria-pressed') === 'false') {
      $spoiler.setAttribute('aria-pressed', 'true');
      $spoiler.removeAttribute('aria-label');
      $spoiler.title = hideText;
      $text.innerText = spoiler;
    } else {
      $spoiler.setAttribute('aria-pressed', 'false');
      $spoiler.setAttribute('aria-label', label);
      $spoiler.title = showText;
      $text.innerText = '';
    }
  });
});