Live Region Playground
Live Region Playground
Turn on a screen reader to experience this example in action.
Page Contents:
Configuration Options
Live Region Output:
Static Content
The changing content will take place below.
Reference:
| 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;
}