Live Region Playground

Live Region Playground

Turn on a screen reader to experience this example in action.

Page Contents:

Configuration Options

How do you want to configure your live region?
(values for aria-live, aria-atomic and aria-relevant will be auto-populated)
Trigger content change:
Content change will alternate between additions and modifications with the exception of content removal every 10th iteration.

Live Region Output:

Static Content

The changing content will take place below.

Reference:

Mapping of Roles to Live Region Attributes
Role aria-live aria-atomic aria-relevant
Alert assertive true Not dictated by spec (defaults to "text additions").
log polite Not dictated by spec (defaults to "false"). Not dictated by spec (logically it should be "additions").
marquee off Not dictated by spec (defaults to "false"). Not dictated by spec (defaults to "text additions").
status polite true Not dictated by spec (defaults to "text additions").
timer off Not dictated by spec (defaults to "false"). Not dictated by spec (defaults to "text additions").

HTML Source Code

<div class="liveRegionPlayground">
    <div id="loading" tabindex="-1" style="display: none;">Please wait while options are configured...</div>
    <fieldset id="live-config">
      <legend>How do you want to configure your live region?</legend>
      <div class="form">
        <div>
          <input type="radio" id="default" value="default" name="cus-def" aria-describedby="role-help">
          <label for="default">Use role defaults</label>
          <span id="role-help" class="helper">(values for aria-live, aria-atomic and aria-relevant will be auto-populated)</span>
        </div>
        <div>
          <input type="radio" id="custom" value="custom" name="cus-def" checked="checked">
          <label for="custom">Create custom live region</label>
        </div>
      </div>
      <div class="form">
        <label for="role">role:</label>
        <select id="role">
          <option selected="selected">status</option>
          <option>alert</option>
          <option>log</option>
          <option>marquee</option>
        </select>
      </div>
      <div class="field">
        <label for="aria-live">aria-live value:</label>
        <select id="aria-live">
          <option selected="selected">polite</option>
          <option>assertive</option>
          <option>off</option>
        </select>
      </div>
      <div class="field">
        <label for="aria-atomic">aria-atomic value:</label>
        <select id="aria-atomic">
          <option selected="selected">true</option>
          <option>false</option>
        </select>
      </div>
      <div class="field">
        <label for="aria-relevant">aria-relevant value:</label>
        <select id="aria-relevant">
          <option selected="selected">text</option>
          <option>additions</option>
          <option>removals</option>
          <option>all</option>
        </select>
      </div>
      <div class="field">
        <div id="trigger">Trigger content change:</div>
        <ul role="list" aria-labelledby="trigger">
          <li role="listitem">
            <input type="radio" name="trigger-type" aria-describedby="content-change-help" id="once" value="once" checked="checked">
            <label for="once">Once</label>
          </li>
          <li role="listitem">
            <input type="radio" name="trigger-type" aria-describedby="content-change-help" id="five" value="five">
            <label for="five">Every 5 seconds</label>
          </li>
          <li role="listitem">
            <input type="radio" name="trigger-type" aria-describedby="content-change-help" id="ten" value="ten">
            <label for="ten">Every 10 seconds</label>
          </li>
        </ul>
        <div id="content-change-help" class="helper">
          Content change will alternate between additions and modifications with the exception of content removal every 10th iteration.
        </div>
      </div>
      <button type="button" id="submit">Submit</button>
      <button type="button" id="clear">Clear added content</button>
      <button type="button" disabled="disabled" id="stop">Stop adding content</button>
    </fieldset>
    <h2 id="liveRegionOutput">Live Region Output:</h2>
    <div id="fixture">
      <div>
        <div id="static">
          <h3>Static Content</h3>
          <div>The changing content will take place below.</div>
        </div>
      </div>
      <div id="update"></div>
    </div>
</div>

JavaScript Source Code

Dependencies:

  • JQuery
'use strict';

/**
 * Live region playground
 *
 * @author Harris Schneiderman
 */

/* global $*/
var i = 1;
var contentChanges = 1;
var interval;
var $role = $('#role');
var $stop = $('#stop');
var $update = $('#update');
var $fixture = $('#fixture');
var $ariaLive = $('#aria-live');
var $ariaAtomic = $('#aria-atomic');
var $ariaRelevant = $('#aria-relevant');

configureRegion();

// apply attrs based on fields
$('#submit').on('click', configureRegion);

// clear the live region div (except for the "static content")
$('#clear').on('click', function () {
  $update.empty();
  i = 1;
  contentChanges = 1;
});

$('input[name="cus-def"]').on('change', function () {
  if ($('#default').is(':checked')) {
    $ariaLive.attr('disabled', 'disabled').attr('aria-disabled', 'true');
    $ariaAtomic.attr('disabled', 'disabled').attr('aria-disabled', 'true');
    $ariaRelevant.attr('disabled', 'disabled').attr('aria-disabled', 'true');
  } else {
    $ariaLive.removeAttr('disabled').removeAttr('aria-disabled', 'true');
    $ariaAtomic.removeAttr('disabled').removeAttr('aria-disabled', 'true');
    $ariaRelevant.removeAttr('disabled').removeAttr('aria-disabled', 'true');
  }
});

// stop adding content
$stop.on('click', function () {
  if (interval) {
    clearInterval(interval);
  }
});

// configure vals of others based on newly selected role
$role.on('change', onroleChange);

function configureRegion(e) {
  if (interval) {
    clearInterval(interval);
  }

  // configure attributes
  $fixture
    .attr({
      'role': $role.val(),
      'aria-live': $ariaLive.val(),
      'aria-atomic': $ariaAtomic.val(),
      'aria-relevant': $ariaRelevant.val()
    });

  // configure content insertion (if submit was clicked)
  if (e) {
    configureInsertion();
  }
}

function configureInsertion() {
  var freq = $('input[name="trigger-type"]:checked').val();

  // call `insertContent` based on frequency chosen
  if (freq === 'once') {
    $stop.attr('disabled', 'disabled');
    insertContent();
  } else {
    $stop.removeAttr('disabled');
    freq = (freq === 'five') ? 5 : 10;
    interval = setInterval(insertContent, freq * 1000);
  }
}

function insertContent() {
  if (contentChanges < 10) {
    if (isOdd(contentChanges)) {
      $update.append('<div><span class="added">Added Content</span> #' + i + '</div>');
      if (contentChanges === 9) {
        $update.append('<div>Also, more <span class="added">added content</span>! Unfortunately, I will be removed next...</div>');
      }
      i++;
    } else {
      $update.find('.added').last().html('Modified Content');
    }
  } else {
    $update.children().last().remove();
    contentChanges = 1; // reset it.
  }
  contentChanges++;
}

function onroleChange() {
  if ($('#custom').is(':checked')) {
    return;
  }

  var role = $role.val();

  // update <select /> vals based on role
  if (role == 'alert') {
    $ariaLive.val('assertive');
    $ariaAtomic.val('true');
    $ariaRelevant.val('text');
  } else if (role == 'log') {
    $ariaLive.val('polite');
    $ariaAtomic.val('false');
    $ariaRelevant.val('text');
  } else if (role == 'status') {
    $ariaLive.val('polite');
    $ariaAtomic.val('true');
    $ariaRelevant.val('text');
  } else if (role == 'marquee') {
    $ariaLive.val('off');
    $ariaAtomic.val('false');
    $ariaRelevant.val('text');
  }

  // update the attributes right away
  configureRegion();
}

function isOdd(n) {
  return Math.abs(n) % 2 == 1;
}

CSS Source Code

/*
  Live Region Playground — Restyled
  Deque University ARIA Component
*/

:root {
  --dqu-interactive: #2e5f7a;
  --dqu-interactive-hover: #3a7a9a;
  --dqu-interactive-light: rgba(46, 95, 122, 0.08);
  --dqu-bg-primary: #fcfaf8;
  --dqu-bg-secondary: #f6f3ed;
  --dqu-border-secondary: #8c827d;
  --dqu-text-primary: #21201e;
  --dqu-font-family: "Noto Sans", sans-serif;
}

.liveRegionPlayground {
  font-family: var(--dqu-font-family);
  color: var(--dqu-text-primary);
}

/* Fieldset — card-style container */
.liveRegionPlayground fieldset {
  border: none;
  padding: 24px;
  margin-bottom: 24px;
}

.liveRegionPlayground legend {
  font-weight: 600;
  font-size: 1.1rem;
  color: var(--dqu-interactive);
  padding: 0 8px;
}

/* Form groups — each in a card with left accent border */
.liveRegionPlayground .form,
.liveRegionPlayground .field {
  background: #f8f6f1;
  border-left: 4px solid var(--dqu-interactive);
  border-radius: 0px;
  padding: 16px 20px;
  margin-bottom: 16px;
}

.liveRegionPlayground label,
.liveRegionPlayground #trigger,
.liveRegionPlayground #custom-default {
  font-size: 1em;
  color: var(--dqu-text-primary);
}

.liveRegionPlayground .helper {
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--dqu-border-secondary);
  display: block;
  margin-top: 4px;
}

/* Selects */
.liveRegionPlayground select {
  background-color: #ffffff;
  border: 1px solid var(--dqu-border-secondary);
  border-radius: 6px;
  padding: 8px 12px;
  font-family: var(--dqu-font-family);
  font-size: 1em;
  color: var(--dqu-text-primary);
}

.liveRegionPlayground select:focus {
  outline: 2px solid var(--dqu-interactive) !important;
  outline-offset: 2px;
  border-color: var(--dqu-interactive);
}

/* Lists */
.liveRegionPlayground ul {
  margin: 8px 0;
  padding-left: 8px;
}

.liveRegionPlayground li {
  list-style-type: none;
  margin-bottom: 8px;
  font-size: 0.9375rem;
}

.liveRegionPlayground .inner-field {
  font-size: 1em;
  margin-left: 20px;
}

/* Native radio inputs — custom-styled to match the Radio example
   (teal circle, teal dot when checked, round focus ring). */
.liveRegionPlayground input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  border: 2px solid var(--dqu-interactive);
  border-radius: 50%;
  background: #ffffff;
  vertical-align: middle;
  margin: 0 6px 0 0;
  padding: 0;
  cursor: pointer;
  position: relative;
}

.liveRegionPlayground input[type="radio"]:checked {
  border-color: var(--dqu-interactive);
}

.liveRegionPlayground input[type="radio"]:checked::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--dqu-interactive);
}

.liveRegionPlayground input[type="radio"]:focus-visible {
  outline: 3px solid var(--dqu-interactive) !important;
  outline-offset: 2px;
}

/* Buttons — prominent filled pills */
.liveRegionPlayground button {
  padding: 10px 20px;
  font-family: var(--dqu-font-family);
  font-size: 0.9375rem;
  font-weight: 600;
  margin: 4px 8px 4px 0;
  border-radius: 9999px;
  cursor: pointer;
  border: none;
  transition: background-color 0.15s ease;
}

.liveRegionPlayground button#submit {
  background: var(--dqu-interactive);
  color: #ffffff;
}

.liveRegionPlayground button#submit:hover {
  background: var(--dqu-interactive-hover);
}

.liveRegionPlayground button#clear {
  background: #ffffff;
  color: var(--dqu-interactive);
  border: 1px solid var(--dqu-interactive);
}

.liveRegionPlayground button#clear:hover {
  background: var(--dqu-interactive-light);
}

.liveRegionPlayground button#stop {
  background: var(--dqu-bg-secondary);
  color: var(--dqu-text-primary);
  border: 1px solid var(--dqu-border-secondary);
}

.liveRegionPlayground button#stop:hover {
  background: #e8e3d9;
}

.liveRegionPlayground button:focus {
  outline: 3px solid var(--dqu-interactive) !important;
  outline-offset: 2px;
}

.liveRegionPlayground button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Live region output box */
.liveRegionPlayground h2 {
  font-family: var(--dqu-font-family);
  color: var(--dqu-text-primary);
  margin-top: 32px;
}

footer[role="contentinfo"] {
  margin-top: 32px;
}

.liveRegionPlayground #fixture {
  width: 85%;
  margin: 0 auto;
  text-align: center;
  border: 2px solid var(--dqu-border-secondary);
  border-radius: 8px;
  height: 300px;
  overflow: auto;
  padding: 16px;
  background: var(--dqu-bg-primary);
}

.liveRegionPlayground h3 {
  font-family: var(--dqu-font-family);
  color: var(--dqu-interactive);
}

/* Loading overlay */
.liveRegionPlayground #loading {
  position: absolute;
  width: 500px;
  padding: 25px;
  text-align: center;
  background-color: var(--dqu-interactive);
  color: #fff;
  border: 4px solid #ffffff;
  border-radius: 8px;
  font-family: var(--dqu-font-family);
}

.liveRegionPlayground #mean {
  position: absolute;
  clip: rect(1px 1px 1px 1px);
  clip: rect(1px, 1px, 1px, 1px);
  overflow: hidden;
  width: 1px;
  height: 1px;
}

Copy and Paste Full Page Example