Progress Bar (Unbounded)
Progress Bar (Unbounded)
The unbounded progress bar is not tied to any progressive increments. It is basically a "busy" or "in progress" message. It will continue until it is cancelled by another event. If there is no such event, it will continue forever.
Note:
The unbounded progress bar is not keyboard-focusable, so if you want to hear it read by a screen reader, you will need to navigate to it via text navigation (down arrow key in JAWS and NVDA; Alt + right arrow in Narrator; Control + Option + Right arrow in VoiceOver)
Turn on a screen reader to experience this example in action.
| Attribute/Option | Description |
|---|---|
message |
This attribute/option specifies message for the screen readers. If this option is not provided, a default message is set for the screen readers. |
height |
This attribute/option specifies the height of the progressbar widget.
By default, it is set to 50px.
|
padding |
This attribute/option specifies the padding to be used for the progressbar widget.
By default, it is set to 5px.
|
background_color |
This attribute/option sets the background color of the progressbar.
By default, it is set to "aliceblue" color.
|
Data Processing
HTML Source Code
<div class="dqu-example">
<table class="data">
<tr>
<th width="150px" >Attribute/Option</th>
<th>Description</th>
</tr>
<tr>
<td><code>message</code></td>
<td>This attribute/option specifies message for the screen readers.
If this option is not provided, a default message is set for the screen readers.
</td>
</tr>
<tr>
<td><code>height</code></td>
<td>This attribute/option specifies the height of the progressbar widget.
By default, it is set to <code>50px</code>.
</td>
</tr>
<tr>
<td><code>padding</code></td>
<td>This attribute/option specifies the padding to be used for the progressbar widget.
By default, it is set to <code>5px</code>.
</td>
</tr>
<tr>
<td><code>background_color</code></td>
<td>This attribute/option sets the background color of the progressbar.
By default, it is set to <code>"aliceblue"</code> color.
</td>
</tr>
</table>
<h2 id="task_label">
Data Processing
</h2>
<div id="progressbar-container"></div>
<p>
<button class="button" id="start-progressbar">
Start
</button>
<button class="button" id="stop-progressbar">
Stop
</button>
</p>
</div>
JavaScript Source Code
//Language object to be modified as per the language
var langText = {
"errorNodeNotObject": "Node provided is not a DOM object.",
"errorNodeNotDivObject": "Node provided is not a DIV HTML object.",
"msgProcessing": "processing in progress...",
"msgPleaseWait": "please wait...",
"msgProcessingCompleted": "processing completed.",
}
var progressbar;
window.addEventListener('load', function () {
var div = document.getElementById("progressbar-container");
var options = [];
options["height"] = 10;
progressbar = new ProgressbarUnbounded(div, options);
var startButton = document.getElementById("start-progressbar");
startButton.addEventListener("click", startProgressbar);
var stopButton = document.getElementById("stop-progressbar");
stopButton.addEventListener("click", stopProgressbar);
});
var intervalId = null;
var progressValue = 10;
function startProgressbar(event)
{
progressbar.show();
}
function stopProgressbar()
{
progressbar .hide(langText["msgProcessingCompleted"]);
}
class ProgressbarUnbounded {
constructor(node, options) {
// Check whether node is a DOM element
if (typeof node !== 'object') {
console.log(langText["errorNodeNotObject"]);
return;
}
if( (typeof node.nodeName === "undefined") || ( node.nodeName.toLowerCase() !== "div" ))
{
console.log(langText["errorNodeNotDivObject"]);
return;
}
this.parentNode = node;
if ( (typeof options == "undefined") || (!Array.isArray(options)) ) options = [];
this.processingMessage = langText["msgProcessing"];
if(typeof options["message"] === "string")
this.processingMessage = options["message"];
var height = "";
if(typeof options["height"] === "number")
height = options["height"];
var padding = "5";
if(typeof options["padding"] === "number")
padding = options["padding"];
var indicatorColorGradient = "linear-gradient(to right, #006cc1 0, rgba(255, 255, 255, 0) 50%, #006cc1 100%)";
if(typeof options["indicator_color_gradient"] === "string")
indicatorColorGradient = options["indicator_color_gradient"];
var backgroundColor = "#3838FF";
if(typeof options["background_color"] === "string")
backgroundColor = options["background_color"];
var progressbar = document.createElement("progressbar");
progressbar.classList.add("progressbar");
progressbar.style.width = "0%";
if(height != "" )
progressbar.style.height = height+"px";
progressbar.style.visibility = "hidden";
progressbar.style.padding = padding + "px";
this.parentNode.appendChild(progressbar);
progressbar.style.backgroundColor = backgroundColor;
this.progressbar = progressbar;
var notifyElement = document.createElement("p");
notifyElement.setAttribute("aria-live", "polite");
notifyElement.classList.add("visually-hidden");
this.parentNode.appendChild(notifyElement);
this.notifyElement = notifyElement;
}
show()
{
this.progressbar.style.visibility = "visible";
var self = this;
var announceMessage = true;
var width = 10;
this.announce(self.processingMessage,120,2000,"show");
this.progressbar.style.width = width+"%";
width +=10;
if(typeof this.intervalId != "undefined") clearInterval(this.intervalId);
this.intervalId = setInterval(function(){
if(announceMessage)
self.announce(self.processingMessage,120,2000,"show");
else
self.announce(langText["msgPleaseWait"],120,1500,"show");
announceMessage = !announceMessage;
self.progressbar.style.width = width+"%";
width +=10;
if(width > 100) width = 0;
}, 2000);
}
hide(message)
{
this.progressbar.style.visibility = "hidden";
if(typeof this.intervalId != "undefined") clearInterval(this.intervalId);
if(typeof message === "string")
this.announce(message,0,2000,"hide");
}
announce(message, initialDelay, msgTime, scope)
{
var self=this;
if(typeof scope == "undefined") scope = "unknown";
if(typeof this.scopeIds == "undefined" ) this.scopeIds = [];
if( typeof this.scopeIds[scope] == "number")
{
clearTimeout(this.scopeIds[scope]);
this.scopeIds[scope] = null;
}
if (initialDelay > 1)
{
this.scopeIds[scope] = setTimeout(function() {
self.notifyElement.innerHTML = message;
}, initialDelay);
}
else
this.notifyElement.innerHTML = message;
setTimeout(function() {
self.notifyElement.innerHTML ="";
self.scopeIds[scope] = null;
}, msgTime);
}
}
CSS Source Code
/*
Progress Bar (Unbounded) — Restyled
Deque University ARIA Component
Visually distinct from the bounded progressbar:
- Uses animated striped pattern instead of solid fill
- Narrower bar height
- Striped gradient animation signals "indeterminate/processing"
*/
: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;
}
/* Buttons */
.button {
padding: 8px 14px;
width: auto;
display: inline-block;
font-family: var(--dqu-font-family);
font-size: 0.875rem;
font-weight: 600;
margin: 4px;
border-radius: 9999px;
cursor: pointer;
border: 1px solid var(--dqu-interactive);
background: #ffffff;
color: var(--dqu-interactive);
transition: background-color 0.15s ease;
}
button.button {
width: auto;
}
.button:hover {
background-color: var(--dqu-interactive-light);
outline: 2px solid var(--dqu-interactive) !important;
outline-offset: 2px;
}
.button:focus {
outline: 3px solid var(--dqu-interactive) !important;
outline-offset: 2px;
}
.pressed {
background: var(--dqu-interactive);
color: #ffffff;
border-color: var(--dqu-interactive);
}
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
}
/* Cream "track" on the container — always visible behind the
growing stripe bar. 20px total visible height (border-box) so it
matches the bounded progressbar exactly. */
#progressbar-container {
margin: 12px 0;
width: calc(100% - 10px);
height: 20px;
box-sizing: border-box;
border: 1px solid var(--dqu-border-secondary);
border-radius: 9999px;
background: var(--dqu-bg-secondary);
overflow: hidden;
}
/* The JS creates a progressbar element inside the container and
grows its width from 0% → 100%. We use an animated diagonal
striped pattern to distinguish unbounded from the bounded
solid-fill bar. Height fills the container's inner area; the
cream container background shows through where the bar hasn't
grown yet. */
.progressbar {
height: 100% !important;
display: block !important;
padding: 0 !important;
border: none !important;
background-image: repeating-linear-gradient(
-45deg,
var(--dqu-interactive),
var(--dqu-interactive) 10px,
var(--dqu-bg-secondary) 10px,
var(--dqu-bg-secondary) 20px
) !important;
background-size: 28px 28px;
animation: stripe-slide 1.5s linear infinite;
}
@keyframes stripe-slide {
0% { background-position: 0 0; }
100% { background-position: 28px 0; }
}
/* Respect prefers-reduced-motion: stop the animation
but keep the static stripes so the bar is still
visually distinct from the bounded progressbar */
@media (prefers-reduced-motion: reduce) {
.progressbar {
animation: none;
}
}
/* Data table */
table.data {
border-collapse: collapse;
font-family: var(--dqu-font-family);
font-size: 0.9375rem;
margin: 12px 0;
}
table.data th,
table.data td {
border: 1px solid var(--dqu-border-secondary);
padding: 10px 12px;
text-align: left;
color: var(--dqu-text-primary);
}
table.data th {
background: var(--dqu-bg-secondary);
font-weight: 600;
}
.dqu-example h2 {
font-family: var(--dqu-font-family);
color: var(--dqu-text-primary);
}
.dqu-example p {
font-family: var(--dqu-font-family);
}