Skip to content

Progress Ring

Experimental Alpha

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

Vue
Lit
React
Live Preview

Default Progress Ring

A basic progress ring component.

75%

Different Progress Values

Progress rings showing various completion levels.

0%25%50%75%100%

Sizes

Three size options: small, medium (default), and large.

60%60%60%

Variants

Color variants for different contexts.

70%

Primary

70%

Success

70%

Warning

70%

Danger

70%

Info

Custom Labels

Override the default slot to display custom content.

45/100Low
Storage

No Animation

Disable the fill animation with the noAnimation prop.

65%

Animated Progress

Progress ring with dynamic value updates.

0%
Start Reset

CSS Shadow Parts Customization

Use CSS Shadow Parts to customize the component's appearance.

85%

Real-world Example

Progress rings in a dashboard-style layout.

Tasks Complete

17/20

Storage Used

68GB

CPU Usage

42%
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>
  );
}
Open in StackBlitz

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:

bash
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add ProgressRing

The 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
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
tsx
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)
html
<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

PropTypeDefaultDescription
valuenumber0The 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.
labelstring'Progress'Accessible label for screen readers.
noAnimationbooleanfalseDisable animation (also respects prefers-reduced-motion).

Slots

SlotDescription
defaultContent displayed in the center of the ring. Defaults to showing the percentage value.

CSS Shadow Parts

PartDescription
baseThe main container element.
ringThe SVG element containing the progress ring.
trackThe background circle of the progress ring.
indicatorThe foreground circle showing the progress.
labelThe center content area containing the slot.

Customization Example

css
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, and aria-valuemax convey the current, minimum, and maximum values.
  • The label prop provides an accessible name via aria-label.
  • The component respects the prefers-reduced-motion media 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., success for completed, warning for needs attention).
  • For indeterminate loading states, consider using a spinner component instead.