Skip to content
Open
53 changes: 53 additions & 0 deletions techniques/aria/ARIA25.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Using the ARIA progressbar role with a status message</title>
</head>
<body>
<h1>Using the ARIA <code>progressbar</code> role with a status message</h1>
<section id="description">
<h2>Description</h2>
<p>This technique demonstrates how to use the <a href="https://www.w3.org/TR/wai-aria/#progressbar">ARIA <code>progressbar</code> role</a> with an <a href="https://www.w3.org/TR/wai-aria/#live_region_roles">ARIA live region</a> to provide progress information for a file upload.</p>
</section>

<section id="example">
<h2>Example</h2>
<p>This example simulates the progress of uploading a document. The progress of the upload is communicated to the user with an ARIA <code>progressbar</code> and visible text underneath the progress bar. The visible text is contained in an element with an <code>aria-live="polite"</code> attribute. This attribute tells screen readers to announce updates made to the content of the element.</p>

<p class="note">The <code>aria-valuemin</code>, <code>aria-valuemax</code>, and <code>aria-valuenow</code> values on the <code>progressbar</code> component programmatically communicate the progress of the upload, but because the <code>progressbar</code> role is not a live region screen readers won't announce updates to those values as they change. Programmatically connecting a status message to the <code>progressbar</code> to announce the changes in value, and a final "upload complete" message creates a better user experience.</p>

<p>The following code can also be seen as a <a href="../../working-examples/aria-role-status-progressbar/">working example</a>.</p>

<pre><code class="language-html">&lt;p id=&quot;upload-progress-label&quot;&gt;Uploading document: &lt;strong&gt;report.pdf&lt;/strong&gt;&lt;/p&gt;
&lt;div
aria-describedby=&quot;progress-text&quot;
aria-labelledby=&quot;upload-progress-label&quot;
aria-valuemax=&quot;100&quot;
aria-valuemin=&quot;0&quot;
aria-valuenow=&quot;0&quot;
id=&quot;upload-progress&quot;
role=&quot;progressbar&quot;&gt;
&lt;/div&gt;
&lt;p id=&quot;progress-text&quot; aria-live=&quot;polite&quot;&gt;0% complete&lt;/p&gt;</code></pre>
</section>

<section id="tests">
<h2>Tests</h2>
<section class="test-procedure">
<h3>Procedure</h3>
<ol>
<li>Check that the visual progress bar element has a <code>role="progressbar"</code> attribute.</li>
<li>Check that there is a suitable ARIA live region in the code.</li>
<li>Using a screen reader, check that announcements are made that communicate the progress of the task.</li>
</ol>
</section>
<section class="test-results">
<h3>Expected Results</h3>
<ul>
<li>#1, #2, and #3 are true.</li>
</ul>
</section>
</section>
</body>
</html>
4 changes: 1 addition & 3 deletions understanding/understanding.11tydata.js
Original file line number Diff line number Diff line change
Expand Up @@ -1368,7 +1368,7 @@ export default function (data) {
"Situation C: If a status message conveys information on the progress of a process:",
techniques: [
"ARIA23",
'Using <code>role="progressbar"</code> (future link)',
"ARIA25",
{
and: ["ARIA22", "G193"],
andConjunction: "in combination with",
Expand All @@ -1377,9 +1377,7 @@ export default function (data) {
},
],
advisory: [
"Using aria-live regions with chat clients (future link)",
'Using aria-live regions to support <a href="content-on-hover-or-focus">1.4.13 Content on Hover or Focus</a> (future link)',
'Using <code>role="marquee"</code> (future link)',
'Using <code>role="timer"</code> (future link)',
{
id: "ARIA18",
Expand Down
162 changes: 162 additions & 0 deletions working-examples/aria-role-status-progressbar/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Working example - Using the ARIA progressbar role with a status message</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style>
:root{
background: #fff;
color:#000;
font:100% / 1.5 system-ui;
}

button{
background: #0054AE;
border:1px solid transparent;
color:#fff;
font:inherit;
&[aria-disabled=true]{
background: #E9E9E9;
color: #696969;
cursor: not-allowed;
}
&:focus-visible{
outline:2px solid #0054AE;
outline-offset: 2px;
}
}

.upload-container {
max-width: 400px;
margin: 20px 0;
}

#upload-progress {
background-color: #f0f0f0;
border: 1px solid #808080;
border-radius: 4px;
height: 20px;
max-width: 500px;
overflow: hidden;
position: relative;
--width: 0%;
}

#upload-progress::before {
background-color: #653171;
content: '';
height: 100%;
left: 0;
position: absolute;
top: 0;
transition: width 0.8s ease-in-out;
width: var(--width);
}

@media (prefers-reduced-motion: reduce) {
#upload-progress::before {
transition: none;
}
}

#upload-progress-label{
margin-block-end:0;
}

#progress-text {
font-size: 0.875em;
margin:0;
}

#start-upload {
padding: 8px 16px;
margin-top: 10px;
}
</style>
</head>
<body>
<h1>Working example - Using the ARIA <code>progressbar</code> role with a status message</h1>
<p>This working example relates to <a href="../../techniques/aria/ARIA25.html">technique ARIA25</a>.</p>

<div class="upload-container">
<p id="upload-progress-label">Uploading document: <strong>report.pdf</strong></p>
<div
aria-labelledby="upload-progress-label"
role="progressbar"
aria-describedby="progress-text"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="0"
id="upload-progress">
</div>
<p id="progress-text" aria-live="polite">0% complete</p>
<button id="start-upload" type="button">Start Upload</button>
</div>

<script>
function startUpload() {
const progressBar = document.getElementById('upload-progress');
const progressText = document.getElementById('progress-text');
const startButton = document.getElementById('start-upload');

// Non-uniform progress intervals: 0 → 15 → 35 → 70 → 90 → 100
const progressSteps = [0, 15, 35, 70, 90, 100];
const timeIntervals = [0, 800, 2000, 1500, 800, 1800]; // Different timing for each step

let currentStep = 0;
startButton.setAttribute('aria-disabled', 'true');

function updateProgress() {
if (currentStep < progressSteps.length) {
const progress = progressSteps[currentStep];

// Update ARIA attributes
progressBar.setAttribute('aria-valuenow', progress);

// Update visual progress using CSS custom property for smooth animation
progressBar.style.setProperty('--width', progress + '%');

// Update text after animation completes (only if motion is preferred)
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const delay = prefersReducedMotion ? 0 : 800; // No delay if reduced motion is preferred

if (progress === 100) {
// Wait for the animation to complete before showing final text
setTimeout(() => {
progressText.textContent = 'Upload complete!';
}, delay);
} else {
// Wait for the animation to complete before showing progress text
setTimeout(() => {
progressText.textContent = progress + '%';
}, delay);
}

currentStep++;

if (currentStep < progressSteps.length) {
setTimeout(updateProgress, timeIntervals[currentStep]);
}
}
}

// Start the progress simulation
updateProgress();
}

// Add event listener instead of onclick
document.addEventListener('DOMContentLoaded', function() {
const startButton = document.getElementById('start-upload');
startButton.addEventListener('click', function(event) {
// Ignore click events when button is aria-disabled
if (startButton.getAttribute('aria-disabled') === 'true') {
event.preventDefault();
return;
}
startUpload();
});
});
</script>
</body>
</html>