This content originally appeared on Bits and Pieces - Medium and was authored by Nikita Barsukov
No div Arounds: Writing Semantic Progress Indicators in Angular

Developing a progress indicator is one of the simplest tasks for the frontend-developer. All you need is basic knowledge of HTML and CSS. JavaScript is used just to calculate the percentage of task completion.
However, this simplicity is deceiving. The Internet is teeming with a plethora of community solutions proposing to build the progress indicator with nested div-containers and CSS to spice things up. Steer clear of these solutions! Those are almost a crime against humanity since they worsen template semantics and violate accessibility.
In this article, I will show how in Taiga UI we developed Angular components: ProgressBar and ProgressCircle.
Developing ProgressBar
Task formulation
There is a built-in HTML tag <progress />. MDN claims:
The <progress> HTML element displays an indicator showing the completion progress of a task, typically displayed as a progress bar.
The important thing is that each browser differently renders this HTML tag. We require such a progress indicator which looks the same way on all browsers.
Wrong solution / De/IVious solution
The first idea which springs to one’s mind might be the superficial one: “Let us not use the built-in HTML tag at all. It is not fancy and looks different in each browser”. This idea cultivates another wrong solution within the community. The proposal revolves around creating two <div/>-containers: the first container is the progress tracker, and the second one is the progress indicator. The following step is to customize the containers and change the width of the progress indicator via JavaScript (from 0 to 100%).
The simplified version of this wrong solution is the following. The HTML file contains the two mentioned containers:
<div class="track">
<div class="indicator"></div>
</div>
The CSS-file:
.track {
position: relative;
background-color: grey;
width: 300px;
height: 20px;
}
.indicator {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 50%; /* change it via JS */
background-color: yellow;
}It does work visually. But that is only visually. However, what if a visually impaired user (e.g., cannot distinguish colors or is blind) decides to visit the website and doesn’t see the progress indicator?
In such cases, people use screen readers. These programs read out loud everything important that is happening on the screen to the user. Nevertheless, in the case of the ‘div’ solution the screen reader will not be able to comprehend this type of progress indicator. It will “see” only two <div />-containers filled in with different colors and will not pronounce it to the user.
This proposed solution not only worsens HTML semantics but also violates the accessibility of our web application. Of course, we can help the screen reader detect the progress indicator: we can set role=”progressbar” and also add aria-valuenow, aria-valuemax and aria-valuemin on the outer container. However, there is no need to reinvent the wheel and overcomplicate basic notions!
Improving DIV solution
There is a popular approach which can correct the accessibility of the previous solution. We can put visually-hidden native <progress /> HTML tag inside our custom progress component. Then we should pass attributes value and max to it and leave everything else as it was in the previous solution.
There are several approaches to visually hiding an HTML container but leaving it detectable for screen readers. Usually, it is sr-only CSS-class. For example, popular CSS frameworks such as Bootstrap and Tailwind CSS have such classes.
Warning! Do not use display: none, height: 0 and width: 0. They hide content not only visually but also for screen readers. Read more about this in the article “CSS in Action: Invisible Content Just for Screen Reader Users”.
After it, the progress indicator is not only visually fancy but also does not violate the accessibility principle. For example, the built-in Mac OS screen reader pronounces it as «n-percentage progress indicator» (where n — 100 × value / max).
Despite the progress, we are still not content with the template’s semantics! Moreover, we should create many @Input()-properties in our component to pass all required attributes to injected native <progress />-tag without any mutations. For now, it is only value and max, and who knows, maybe tomorrow we will need to add id or one of data-* attributes. As a result, our component carries attributes, which are of no use!
Our solution with Angular attribute component
We propose a solution which requires no extra HTML tags. All we need to do is to extend the native <progress />-element. Whenever we extend any native HTML element, the official Angular documentation recommends creating a component that uses an attribute selector with this element. This practice is actively used in our UI-Kit library Taiga: for example, button, link and label components.
Let’s create a TypeScript file with our attribute component:
import {
ChangeDetectionStrategy,
Component,
HostBinding,
Input
} from '@angular/core';
@Component({
selector: 'progress[tuiProgressBar]',
template: '',
styleUrls: ['./progress-bar.component.less'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TuiProgressBarComponent {
@Input()
@HostBinding('style.--tui-progress-color')
color?: string;
}The Less file includes some less-mixins.
The following one clears all built-in customization from the browser:
.clearProgress() {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
}And this one helps to customize the progress tracker:
.progressTrack(@property, @value) {
@{property}: @value; // Edge | Mozilla
&::-webkit-progress-bar {
@{property}: @value; // Chrome | Opera | Safari
}
}The last mixin helps to customize the color of the progress indicator:
.progressIndicatorColor(@color) {
color: @color; // Not Chromium Edge
&::-webkit-progress-value {
background: @color; // Chromium Edge | Chrome | Opera | Safari
}
&::-moz-progress-bar {
background: @color; // Mozilla
}
}Finally, apply all created mixins to :host-element of our component (reminder: it is a native <progress /> on which our attribute component was applied).
:host {
.clearProgress();
.progressIndicatorColor(var(--tui-progress-color, currentColor));
.progressTrack(background-color, grey);
color: yellow;
}That’s all! We developed a nice Angular attribute component which is wrapped around a native <progress /> element. The color of this indicator can be set via the CSS color-property, or via the input property of the component (for example, if we want to create a complex gradient color). In the code, the component declares in the following way:
<progress tuiProgressBar value="60" max="100"></progress>
You can see the component in action on the showcase of our project Taiga UI. The final source code is available on GitHub.
Developing ProgressCircle
Unfortunately, the creation of the ProgressCircle component from only native <progress />-element is not possible. We need some extra template tags.
And here the state of things is the same, curious Internet users might stumble upon various frivolous solutions which propose to create a circular progress indicator from div-containers which have been rounded using border-radius: 50%. Also, such solutions use a significant amount of JavaScript.
Yet, since we are in the ‘no-div club’, this task can be resolved without appeals to div-s. We will use svg-tag <circle />. Moreover, we will use as little JavaScript as possible. Less preprocessor and CSS variables help us with it.
Let’s create a TypeScript file of our future component:
import {
ChangeDetectionStrategy,
Component,
HostBinding,
Input,
} from '@angular/core';
@Component({
selector: 'tui-progress-circle',
templateUrl: './progress-circle.template.html',
styleUrls: ['./progress-circle.style.less'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TuiProgressCircleComponent {
@Input()
value = 0;
@Input()
max = 1;
@Input()
@HostBinding('style.--tui-progress-color')
color: string | null = null;
@Input()
@HostBinding('attr.data-size')
size: 'm' | 'l' = 'm';
@HostBinding('style.--progress-percentage')
get progressPercentage(): number {
return this.value / this.max;
}
}The HTML file contains:
<progress
class="hidden-progress"
[value]="value"
[max]="max"
></progress>
<svg class="svg" height="100%" width="100%" aria-hidden="true">
<circle
class="track"
cx="50%"
cy="50%"
></circle>
<circle
class="progress"
cx="50%"
cy="50%"
></circle>
</svg>
And the last step — the creation of the LESS file. We will use many features of Less: mixins, Maps, and built-in Math functions.
Firstly, let’s create map constants which store values for the different sizes of circular indicators (there are 4 sizes in our project, but for the sake of the code’s simplicity we will leave only 2). It is worth noting that Safari does not support rem-units inside svg-elements. However, it supports em-units. Therefore, we set font-size: 1rem for the host element to use em-units inside it.
@width: {
@m: 2em;
@l: 7em;
};
@track-stroke: {
@m: 0.5em;
@l: 0.25em;
};
@progress-stroke: {
@m: 0.5em;
@l: 0.375em;
};The process of progress filling happens due to setting of stroke-dasharray and recalculations of the stroke-dashoffset. Look into the article “Building a Progress Ring, Quickly” to understand how it works. Our solution is just an improved version of what was suggested by its author. Create a mixin which calculates the state of the progress indicator for different components’ sizes:
.circle-params(@size) {
width: @width[ @@size];
height: @width[ @@size];
.track {
r: (@width[ @@size] - @track-stroke[ @@size]) / 2;
stroke-width: @track-stroke[ @@size];
}
.progress {
@radius: (@width[ @@size] - @progress-stroke[ @@size]) / 2;
@circumference: 2 * pi() * @radius;
r: @radius;
stroke-width: @progress-stroke[ @@size];
stroke-dasharray: @circumference;
stroke-dashoffset: calc(@circumference - var(--progress-percentage) * @circumference);
}
}Finally, apply our mixin on host-element and add some cosmetic improvements:
:host {
display: block;
position: relative;
color: yellow;
transform: rotate(-90deg);
transform-origin: center;
font-size: 1rem;
&[data-size='m'] {
.circle-params(m);
}
&[data-size='l'] {
.circle-params(l);
}
}
.track {
fill: transparent;
stroke: grey;
}
.progress {
fill: transparent;
stroke: var(--tui-progress-color, currentColor);
transition: stroke-dashoffset 300 linear;
}
.hidden-progress {
.sr-only(); // see this mixin in the chapter with ProgressBar
}
.svg {
overflow: unset;
}That is all! You can see the component in action on the showcase of our Taiga UI project. The final source code is available on GitHub.
💡 Tip: You can now deploy your newly created semantic components to a platform such as Bit so they can be reused across all of your projects. With Bit, you’ll have independent versioning, tests, and documentation for your components, making it easier for others to understand and use your code. This would let your team reuse and collaborate on components to write scalable code, speed up development, and maintain a consistent UI. Find out more here.
Wrapping up
There are no absolutely correct solutions in the frontend-development. The same functionality can be implemented in different ways. But there are superficial and lazy solutions, which deliver the final product by sacrificing convenience and optimization. I have shown what mistakes can be made if you want to create progress indicators: poor semantics and neglected accessibility.
In this article, I have shown my point of view on these components. I don’t claim that my solutions are the best ones in the field. And still, during the development of those, I took all mentioned problems into consideration, ensured compatibility with all modern browsers and got simple solutions with a minimum usage of JavaScript.
Build apps with reusable components, just like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.
Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.
Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:
→ Micro-Frontends
→ Design System
→ Code-Sharing and reuse
→ Monorepo
Learn more
- How We Build Micro Frontends
- How we Build a Component Design System
- How to reuse React components across your projects
- 5 Ways to Build a React Monorepo
- How to Create a Composable React App with Bit
How to Build Semantic Progress Indicators in Angular was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by Nikita Barsukov
Nikita Barsukov | Sciencx (2023-03-17T07:16:46+00:00) How to Build Semantic Progress Indicators in Angular. Retrieved from https://www.scien.cx/2023/03/17/how-to-build-semantic-progress-indicators-in-angular/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.