Progress Ring
This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.
The Progress Ring component provides a circular progress indicator that can display determinate or indeterminate progress states. It's highly customizable with different sizes, colors, and animation options.
Examples
Live Preview
Default Progress Ring
A basic progress ring component.
Different Progress Values
Progress rings showing various completion levels.
Sizes
Three size options: small, medium (default), and large.
Variants
Color variants for different contexts.
Primary
Success
Warning
Danger
Info
Custom Labels
Override the default slot to display custom content.
Storage
No Animation
Disable the fill animation with the noAnimation prop.
Animated Progress
Progress ring with dynamic value updates.
CSS Shadow Parts Customization
Use CSS Shadow Parts to customize the component's appearance.
Real-world Example
Progress rings in a dashboard-style layout.
Tasks Complete
Storage Used
CPU Usage
View Vue Code
<template>
<section>
<div class="mbe4">
<h2>Default Progress Ring</h2>
<p class="mbs2 mbe3">A basic progress ring component.</p>
<VueProgressRing :value="75" />
</div>
<div class="mbe4">
<h2>Different Progress Values</h2>
<p class="mbs2 mbe3">Progress rings showing various completion levels.</p>
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
<VueProgressRing :value="0" />
<VueProgressRing :value="25" />
<VueProgressRing :value="50" />
<VueProgressRing :value="75" />
<VueProgressRing :value="100" />
</div>
</div>
<div class="mbe4">
<h2>Sizes</h2>
<p class="mbs2 mbe3">Three size options: small, medium (default), and large.</p>
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
<VueProgressRing
size="small"
:value="60"
/>
<VueProgressRing
size="medium"
:value="60"
/>
<VueProgressRing
size="large"
:value="60"
/>
</div>
</div>
<div class="mbe4">
<h2>Variants</h2>
<p class="mbs2 mbe3">Color variants for different contexts.</p>
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
<div style="text-align: center;">
<VueProgressRing
variant="primary"
:value="70"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Primary</p>
</div>
<div style="text-align: center;">
<VueProgressRing
variant="success"
:value="70"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Success</p>
</div>
<div style="text-align: center;">
<VueProgressRing
variant="warning"
:value="70"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Warning</p>
</div>
<div style="text-align: center;">
<VueProgressRing
variant="danger"
:value="70"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Danger</p>
</div>
<div style="text-align: center;">
<VueProgressRing
variant="info"
:value="70"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Info</p>
</div>
</div>
</div>
<div class="mbe4">
<h2>Custom Labels</h2>
<p class="mbs2 mbe3">Override the default slot to display custom content.</p>
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
<VueProgressRing :value="45">
<span style="font-size: 0.875rem; font-weight: bold;">45/100</span>
</VueProgressRing>
<VueProgressRing
:value="80"
variant="success"
>
<span style="font-size: 1.25rem;">✓</span>
</VueProgressRing>
<VueProgressRing
:value="30"
variant="warning"
>
<span style="font-size: 0.75rem; text-align: center;">Low<br />Storage</span>
</VueProgressRing>
</div>
</div>
<div class="mbe4">
<h2>No Animation</h2>
<p class="mbs2 mbe3">Disable the fill animation with the <code>noAnimation</code> prop.</p>
<VueProgressRing
:value="65"
:no-animation="true"
/>
</div>
<div class="mbe4">
<h2>Animated Progress</h2>
<p class="mbs2 mbe3">Progress ring with dynamic value updates.</p>
<div>
<VueProgressRing
:value="animatedValue"
variant="info"
/>
<div class="mbs6">
<VueButton
@click="startAnimation"
variant="primary"
bordered
grouped
>
Start
</VueButton>
<VueButton
@click="resetAnimation"
variant="primary"
bordered
grouped
>
Reset
</VueButton>
</div>
</div>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p class="mbs2 mbe3">
Use CSS Shadow Parts to customize the component's appearance.
</p>
<VueProgressRing
:value="85"
class="custom-ring"
/>
</div>
<div class="mbe4">
<h2>Real-world Example</h2>
<p class="mbs2 mbe3">Progress rings in a dashboard-style layout.</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1.5rem;">
<div style="padding: 1rem; border: 1px solid #e0e0e0; border-radius: 8px; text-align: center;">
<h3 style="margin: 0 0 1rem 0; font-size: 0.875rem;">Tasks Complete</h3>
<VueProgressRing
:value="85"
variant="success"
>
<strong>17/20</strong>
</VueProgressRing>
</div>
<div style="padding: 1rem; border: 1px solid #e0e0e0; border-radius: 8px; text-align: center;">
<h3 style="margin: 0 0 1rem 0; font-size: 0.875rem;">Storage Used</h3>
<VueProgressRing
:value="68"
variant="warning"
>
<strong>68GB</strong>
</VueProgressRing>
</div>
<div style="padding: 1rem; border: 1px solid #e0e0e0; border-radius: 8px; text-align: center;">
<h3 style="margin: 0 0 1rem 0; font-size: 0.875rem;">CPU Usage</h3>
<VueProgressRing
:value="42"
variant="info"
>
<strong>42%</strong>
</VueProgressRing>
</div>
</div>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, ref, onUnmounted } from "vue";
import { VueProgressRing } from "agnosticui-core/progress-ring/vue";
import VueButton from "agnosticui-core/button/vue";
export default defineComponent({
name: "ProgressRingExamples",
components: {
VueProgressRing,
VueButton,
},
setup() {
const animatedValue = ref(0);
let animationInterval: number | null = null;
const startAnimation = () => {
if (animationInterval) {
clearInterval(animationInterval);
}
animatedValue.value = 0;
animationInterval = setInterval(() => {
if (animatedValue.value < 100) {
animatedValue.value += 1;
} else {
if (animationInterval) {
clearInterval(animationInterval);
animationInterval = null;
}
}
}, 30);
};
const resetAnimation = () => {
if (animationInterval) {
clearInterval(animationInterval);
animationInterval = null;
}
animatedValue.value = 0;
};
onUnmounted(() => {
if (animationInterval) {
clearInterval(animationInterval);
}
});
return {
animatedValue,
startAnimation,
resetAnimation,
};
},
});
</script>
<style>
/* CSS Shadow Parts customization examples */
.custom-ring::part(track) {
stroke: #e0e0e0;
stroke-width: 6;
}
.custom-ring::part(indicator) {
stroke: #667eea;
stroke-width: 6;
stroke-linecap: round;
filter: drop-shadow(0 0 4px rgba(102, 126, 234, 0.5));
}
.custom-ring::part(label) {
font-weight: bold;
font-size: 1.5rem;
color: #667eea;
}
</style>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/progress-ring';
import 'agnosticui-core/button';
export class ProgressRingLitExamples extends LitElement {
static properties = {
animatedValue: { type: Number }
};
constructor() {
super();
this.animatedValue = 0;
this.animationInterval = null;
}
// Render in light DOM to access global utility classes
createRenderRoot() {
return this;
}
disconnectedCallback() {
super.disconnectedCallback();
if (this.animationInterval) {
clearInterval(this.animationInterval);
}
}
startAnimation() {
if (this.animationInterval) {
clearInterval(this.animationInterval);
}
this.animatedValue = 0;
this.animationInterval = setInterval(() => {
if (this.animatedValue < 100) {
this.animatedValue += 1;
} else {
if (this.animationInterval) {
clearInterval(this.animationInterval);
this.animationInterval = null;
}
}
}, 30);
}
resetAnimation() {
if (this.animationInterval) {
clearInterval(this.animationInterval);
this.animationInterval = null;
}
this.animatedValue = 0;
}
render() {
return html`
<section>
<div class="mbe4">
<h2>Default Progress Ring</h2>
<p class="mbs2 mbe3">A basic progress ring component.</p>
<ag-progress-ring value="75"></ag-progress-ring>
</div>
<div class="mbe4">
<h2>Different Progress Values</h2>
<p class="mbs2 mbe3">Progress rings showing various completion levels.</p>
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
<ag-progress-ring value="0"></ag-progress-ring>
<ag-progress-ring value="25"></ag-progress-ring>
<ag-progress-ring value="50"></ag-progress-ring>
<ag-progress-ring value="75"></ag-progress-ring>
<ag-progress-ring value="100"></ag-progress-ring>
</div>
</div>
<div class="mbe4">
<h2>Sizes</h2>
<p class="mbs2 mbe3">Three size options: small, medium (default), and large.</p>
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
<ag-progress-ring size="small" value="60"></ag-progress-ring>
<ag-progress-ring size="medium" value="60"></ag-progress-ring>
<ag-progress-ring size="large" value="60"></ag-progress-ring>
</div>
</div>
<div class="mbe4">
<h2>Variants</h2>
<p class="mbs2 mbe3">Color variants for different contexts.</p>
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
<div style="text-align: center;">
<ag-progress-ring variant="primary" value="70"></ag-progress-ring>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Primary</p>
</div>
<div style="text-align: center;">
<ag-progress-ring variant="success" value="70"></ag-progress-ring>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Success</p>
</div>
<div style="text-align: center;">
<ag-progress-ring variant="warning" value="70"></ag-progress-ring>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Warning</p>
</div>
<div style="text-align: center;">
<ag-progress-ring variant="danger" value="70"></ag-progress-ring>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Danger</p>
</div>
<div style="text-align: center;">
<ag-progress-ring variant="info" value="70"></ag-progress-ring>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Info</p>
</div>
</div>
</div>
<div class="mbe4">
<h2>Custom Labels</h2>
<p class="mbs2 mbe3">Override the default slot to display custom content.</p>
<div style="display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;">
<ag-progress-ring value="45">
<span style="font-size: 0.875rem; font-weight: bold;">45/100</span>
</ag-progress-ring>
<ag-progress-ring value="80" variant="success">
<span style="font-size: 1.25rem;">✓</span>
</ag-progress-ring>
<ag-progress-ring value="30" variant="warning">
<span style="font-size: 0.75rem; text-align: center;">Low<br />Storage</span>
</ag-progress-ring>
</div>
</div>
<div class="mbe4">
<h2>No Animation</h2>
<p class="mbs2 mbe3">Disable the fill animation with the <code>noAnimation</code> prop.</p>
<ag-progress-ring value="65" no-animation></ag-progress-ring>
</div>
<div class="mbe4">
<h2>Animated Progress</h2>
<p class="mbs2 mbe3">Progress ring with dynamic value updates.</p>
<div>
<ag-progress-ring value=${this.animatedValue} variant="info"></ag-progress-ring>
<div class="mbs6">
<ag-button
@click=${this.startAnimation}
variant="primary"
bordered
grouped
>
Start
</ag-button>
<ag-button
@click=${this.resetAnimation}
variant="primary"
bordered
grouped
>
Reset
</ag-button>
</div>
</div>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p class="mbs2 mbe3">
Use CSS Shadow Parts to customize the component's appearance.
</p>
<ag-progress-ring value="85" class="custom-ring"></ag-progress-ring>
</div>
<div class="mbe4">
<h2>Real-world Example</h2>
<p class="mbs2 mbe3">Progress rings in a dashboard-style layout.</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1.5rem;">
<div style="padding: 1rem; border: 1px solid #e0e0e0; border-radius: 8px; text-align: center;">
<h3 style="margin: 0 0 1rem 0; font-size: 0.875rem;">Tasks Complete</h3>
<ag-progress-ring value="85" variant="success">
<strong>17/20</strong>
</ag-progress-ring>
</div>
<div style="padding: 1rem; border: 1px solid #e0e0e0; border-radius: 8px; text-align: center;">
<h3 style="margin: 0 0 1rem 0; font-size: 0.875rem;">Storage Used</h3>
<ag-progress-ring value="68" variant="warning">
<strong>68GB</strong>
</ag-progress-ring>
</div>
<div style="padding: 1rem; border: 1px solid #e0e0e0; border-radius: 8px; text-align: center;">
<h3 style="margin: 0 0 1rem 0; font-size: 0.875rem;">CPU Usage</h3>
<ag-progress-ring value="42" variant="info">
<strong>42%</strong>
</ag-progress-ring>
</div>
</div>
</div>
</section>
<style>
/* CSS Shadow Parts customization examples */
.custom-ring::part(track) {
stroke: #e0e0e0;
stroke-width: 6;
}
.custom-ring::part(indicator) {
stroke: #667eea;
stroke-width: 6;
stroke-linecap: round;
filter: drop-shadow(0 0 4px rgba(102, 126, 234, 0.5));
}
.custom-ring::part(label) {
font-weight: bold;
font-size: 1.5rem;
color: #667eea;
}
</style>
`;
}
}
// Register the custom element
customElements.define('progress-ring-lit-examples', ProgressRingLitExamples);
Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.
View React Code
import { useState, useEffect, useRef } from "react";
import { ReactProgressRing } from "agnosticui-core/progress-ring/react";
import { ReactButton } from "agnosticui-core/button/react";
export default function ProgressRingReactExamples() {
const [animatedValue, setAnimatedValue] = useState(0);
const animationIntervalRef = useRef(null);
useEffect(() => {
return () => {
if (animationIntervalRef.current) {
clearInterval(animationIntervalRef.current);
}
};
}, []);
const startAnimation = () => {
if (animationIntervalRef.current) {
clearInterval(animationIntervalRef.current);
}
setAnimatedValue(0);
animationIntervalRef.current = setInterval(() => {
setAnimatedValue((prev) => {
if (prev < 100) {
return prev + 1;
} else {
if (animationIntervalRef.current) {
clearInterval(animationIntervalRef.current);
animationIntervalRef.current = null;
}
return prev;
}
});
}, 30);
};
const resetAnimation = () => {
if (animationIntervalRef.current) {
clearInterval(animationIntervalRef.current);
animationIntervalRef.current = null;
}
setAnimatedValue(0);
};
return (
<section>
<div className="mbe4">
<h2>Default Progress Ring</h2>
<p className="mbs2 mbe3">A basic progress ring component.</p>
<ReactProgressRing value={75} />
</div>
<div className="mbe4">
<h2>Different Progress Values</h2>
<p className="mbs2 mbe3">Progress rings showing various completion levels.</p>
<div style={{ display: "flex", gap: "2rem", alignItems: "center", flexWrap: "wrap" }}>
<ReactProgressRing value={0} />
<ReactProgressRing value={25} />
<ReactProgressRing value={50} />
<ReactProgressRing value={75} />
<ReactProgressRing value={100} />
</div>
</div>
<div className="mbe4">
<h2>Sizes</h2>
<p className="mbs2 mbe3">Three size options: small, medium (default), and large.</p>
<div style={{ display: "flex", gap: "2rem", alignItems: "center", flexWrap: "wrap" }}>
<ReactProgressRing size="small" value={60} />
<ReactProgressRing size="medium" value={60} />
<ReactProgressRing size="large" value={60} />
</div>
</div>
<div className="mbe4">
<h2>Variants</h2>
<p className="mbs2 mbe3">Color variants for different contexts.</p>
<div style={{ display: "flex", gap: "2rem", alignItems: "center", flexWrap: "wrap" }}>
<div style={{ textAlign: "center" }}>
<ReactProgressRing variant="primary" value={70} />
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>Primary</p>
</div>
<div style={{ textAlign: "center" }}>
<ReactProgressRing variant="success" value={70} />
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>Success</p>
</div>
<div style={{ textAlign: "center" }}>
<ReactProgressRing variant="warning" value={70} />
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>Warning</p>
</div>
<div style={{ textAlign: "center" }}>
<ReactProgressRing variant="danger" value={70} />
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>Danger</p>
</div>
<div style={{ textAlign: "center" }}>
<ReactProgressRing variant="info" value={70} />
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>Info</p>
</div>
</div>
</div>
<div className="mbe4">
<h2>Custom Labels</h2>
<p className="mbs2 mbe3">Override the default slot to display custom content.</p>
<div style={{ display: "flex", gap: "2rem", alignItems: "center", flexWrap: "wrap" }}>
<ReactProgressRing value={45}>
<span style={{ fontSize: "0.875rem", fontWeight: "bold" }}>45/100</span>
</ReactProgressRing>
<ReactProgressRing value={80} variant="success">
<span style={{ fontSize: "1.25rem" }}>✓</span>
</ReactProgressRing>
<ReactProgressRing value={30} variant="warning">
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
Low<br />Storage
</span>
</ReactProgressRing>
</div>
</div>
<div className="mbe4">
<h2>No Animation</h2>
<p className="mbs2 mbe3">
Disable the fill animation with the <code>noAnimation</code> prop.
</p>
<ReactProgressRing value={65} noAnimation={true} />
</div>
<div className="mbe4">
<h2>Animated Progress</h2>
<p className="mbs2 mbe3">Progress ring with dynamic value updates.</p>
<div>
<ReactProgressRing value={animatedValue} variant="info" />
<div className="mbs6">
<ReactButton
onClick={startAnimation}
variant="primary"
bordered
grouped
>
Start
</ReactButton>
<ReactButton
onClick={resetAnimation}
variant="primary"
bordered
grouped
>
Reset
</ReactButton>
</div>
</div>
</div>
<div className="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p className="mbs2 mbe3">
Use CSS Shadow Parts to customize the component's appearance.
</p>
<ReactProgressRing value={85} className="custom-ring" />
</div>
<div className="mbe4">
<h2>Real-world Example</h2>
<p className="mbs2 mbe3">Progress rings in a dashboard-style layout.</p>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: "1.5rem" }}>
<div style={{ padding: "1rem", border: "1px solid #e0e0e0", borderRadius: "8px", textAlign: "center" }}>
<h3 style={{ margin: "0 0 1rem 0", fontSize: "0.875rem" }}>Tasks Complete</h3>
<ReactProgressRing value={85} variant="success">
<strong>17/20</strong>
</ReactProgressRing>
</div>
<div style={{ padding: "1rem", border: "1px solid #e0e0e0", borderRadius: "8px", textAlign: "center" }}>
<h3 style={{ margin: "0 0 1rem 0", fontSize: "0.875rem" }}>Storage Used</h3>
<ReactProgressRing value={68} variant="warning">
<strong>68GB</strong>
</ReactProgressRing>
</div>
<div style={{ padding: "1rem", border: "1px solid #e0e0e0", borderRadius: "8px", textAlign: "center" }}>
<h3 style={{ margin: "0 0 1rem 0", fontSize: "0.875rem" }}>CPU Usage</h3>
<ReactProgressRing value={42} variant="info">
<strong>42%</strong>
</ReactProgressRing>
</div>
</div>
</div>
</section>
);
}
Usage
TIP
The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add ProgressRingThe CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.
Vue
<template>
<section>
<VueProgressRing :value="75" />
<VueProgressRing :value="50" variant="success" />
<VueProgressRing :value="25" size="large" />
</section>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { VueProgressRing } from 'agnosticui-core/progress-ring/vue';
export default defineComponent({
components: { VueProgressRing }
});
</script>React
import { ReactProgressRing } from 'agnosticui-core/progress-ring/react';
export default function Example() {
return (
<section>
<ReactProgressRing value={75} />
<ReactProgressRing value={50} variant="success" />
<ReactProgressRing value={25} size="large" />
</section>
);
}Lit (Web Components)
<script type="module">
import 'agnosticui-core/progress-ring';
</script>
<section>
<ag-progress-ring value="75"></ag-progress-ring>
<ag-progress-ring value="50" variant="success"></ag-progress-ring>
<ag-progress-ring value="25" size="large"></ag-progress-ring>
</section>Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | 0 | The current progress value (0–100). |
size | 'small' | 'medium' | 'large' | 'medium' | The size of the progress ring. |
variant | 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | The color variant of the progress ring. |
label | string | 'Progress' | Accessible label for screen readers. |
noAnimation | boolean | false | Disable animation (also respects prefers-reduced-motion). |
Slots
| Slot | Description |
|---|---|
| default | Content displayed in the center of the ring. Defaults to showing the percentage value. |
CSS Shadow Parts
| Part | Description |
|---|---|
base | The main container element. |
ring | The SVG element containing the progress ring. |
track | The background circle of the progress ring. |
indicator | The foreground circle showing the progress. |
label | The center content area containing the slot. |
Customization Example
ag-progress-ring.custom-ring::part(track) {
stroke: #e0e0e0;
stroke-width: 4;
}
ag-progress-ring.custom-ring::part(indicator) {
stroke: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
stroke-width: 4;
stroke-linecap: round;
}
ag-progress-ring.custom-ring::part(label) {
font-weight: bold;
font-size: 1.25rem;
color: #667eea;
}Accessibility
- The component uses
role="progressbar"for screen reader compatibility. aria-valuenow,aria-valuemin, andaria-valuemaxconvey the current, minimum, and maximum values.- The
labelprop provides an accessible name viaaria-label. - The component respects the
prefers-reduced-motionmedia query.
Best Practices
- Use meaningful labels that describe what progress is being tracked.
- Provide textual feedback in the center slot for users who may have difficulty perceiving the visual progress.
- Consider using different variants to indicate status (e.g.,
successfor completed,warningfor needs attention). - For indeterminate loading states, consider using a spinner component instead.