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);
}

Copy and Paste Full Page Example