diff --git a/docs/components/progress-ring.md b/docs/components/progress-ring.md
index ba1c1a8c4..75d656d53 100644
--- a/docs/components/progress-ring.md
+++ b/docs/components/progress-ring.md
@@ -20,10 +20,10 @@ Use the `--size` custom property to set the diameter of the progress ring.
### Track Width
-Use the `track-width` attribute to set the width of the progress ring's track.
+Use the `--track-width` custom property to set the width of the progress ring's track.
```html preview
-
+
```
### Colors
diff --git a/src/components/progress-bar/progress-bar.test.ts b/src/components/progress-bar/progress-bar.test.ts
new file mode 100644
index 000000000..542314c12
--- /dev/null
+++ b/src/components/progress-bar/progress-bar.test.ts
@@ -0,0 +1,89 @@
+import { expect, fixture, html } from '@open-wc/testing';
+
+import '../../../dist/shoelace.js';
+import type SlProgressBar from './progress-bar';
+
+describe('', () => {
+ let el: SlProgressBar;
+
+ describe('when provided just a value parameter', async () => {
+ before(async () => {
+ el = await fixture(html``);
+ });
+
+ it('should render a component that does not pass accessibility test.', async () => {
+ await expect(el).not.to.be.accessible();
+ });
+ });
+
+ describe('when provided a title, and value parameter', async () => {
+ let base: HTMLDivElement;
+ let indicator: HTMLDivElement;
+
+ before(async () => {
+ el = await fixture(
+ html``
+ );
+ base = el.shadowRoot?.querySelector('[part="base"]') as HTMLDivElement;
+ indicator = el.shadowRoot?.querySelector('[part="indicator"]') as HTMLDivElement;
+ });
+
+ it('should render a component that passes accessibility test.', async () => {
+ await expect(el).to.be.accessible();
+ });
+
+ it('uses the value parameter on the base, as aria-valuenow', async () => {
+ expect(base).attribute('aria-valuenow', '25');
+ });
+
+ it('appends a % to the value, and uses it as the the value parameter to determine the width on the "indicator" part', async () => {
+ expect(indicator).attribute('style', 'width:25%;');
+ });
+ });
+
+ describe('when provided an indeterminate parameter', async () => {
+ let base: HTMLDivElement;
+
+ before(async () => {
+ el = await fixture(
+ html``
+ );
+ base = el.shadowRoot?.querySelector('[part="base"]') as HTMLDivElement;
+ });
+
+ it('should render a component that passes accessibility test.', async () => {
+ await expect(el).to.be.accessible();
+ });
+
+ it('should append a progress-bar--indeterminate class to the "base" part.', async () => {
+ expect(base.classList.value.trim()).to.eq('progress-bar progress-bar--indeterminate');
+ });
+ });
+
+ describe('when provided a ariaLabel, and value parameter', async () => {
+ before(async () => {
+ el = await fixture(
+ html``
+ );
+ });
+
+ it('should render a component that passes accessibility test.', async () => {
+ await expect(el).to.be.accessible();
+ });
+ });
+
+ describe('when provided a ariaLabelledBy, and value parameter', async () => {
+ before(async () => {
+ el = await fixture(
+ html`
+
+
+ `
+ );
+ });
+
+ it('should render a component that passes accessibility test.', async () => {
+ await expect(el).to.be.accessible();
+ });
+ });
+});
diff --git a/src/components/progress-bar/progress-bar.ts b/src/components/progress-bar/progress-bar.ts
index e9f010624..5e6e39bef 100644
--- a/src/components/progress-bar/progress-bar.ts
+++ b/src/components/progress-bar/progress-bar.ts
@@ -1,5 +1,6 @@
import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
+import { ifDefined } from 'lit/directives/if-defined.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import styles from './progress-bar.styles';
@@ -29,6 +30,15 @@ export default class SlProgressBar extends LitElement {
/** When true, percentage is ignored, the label is hidden, and the progress bar is drawn in an indeterminate state. */
@property({ type: Boolean, reflect: true }) indeterminate = false;
+ /** When set, will place a hoverable title on the progress bar. */
+ @property() title: string;
+
+ /** When set, will place a label on the progress bar. */
+ @property() ariaLabel: string;
+
+ /** When set, will place a labelledby on the progress bar. */
+ @property() ariaLabelledBy: string;
+
render() {
return html`
${!this.indeterminate
diff --git a/src/components/progress-ring/progress-ring.test.ts b/src/components/progress-ring/progress-ring.test.ts
new file mode 100644
index 000000000..c8a4704bc
--- /dev/null
+++ b/src/components/progress-ring/progress-ring.test.ts
@@ -0,0 +1,68 @@
+import { expect, fixture, html } from '@open-wc/testing';
+
+import '../../../dist/shoelace.js';
+import type SlProgressRing from './progress-ring';
+
+describe('', () => {
+ let el: SlProgressRing;
+
+ describe('when provided just a value parameter', async () => {
+ before(async () => {
+ el = await fixture(html``);
+ });
+
+ it('should render a component that does not pass accessibility test.', async () => {
+ await expect(el).not.to.be.accessible();
+ });
+ });
+
+ describe('when provided a title, and value parameter', async () => {
+ let base: HTMLDivElement;
+
+ before(async () => {
+ el = await fixture(
+ html``
+ );
+ base = el.shadowRoot?.querySelector('[part="base"]') as HTMLDivElement;
+ });
+
+ it('should render a component that passes accessibility test.', async () => {
+ await expect(el).to.be.accessible();
+ });
+
+ it('uses the value parameter on the base, as aria-valuenow', async () => {
+ expect(base).attribute('aria-valuenow', '25');
+ });
+
+ it('translates the value parameter to a percentage, and uses translation on the base, as percentage css variable', async () => {
+ expect(base).attribute('style', '--percentage: 0.25');
+ });
+ });
+
+ describe('when provided a ariaLabel, and value parameter', async () => {
+ before(async () => {
+ el = await fixture(
+ html``
+ );
+ });
+
+ it('should render a component that passes accessibility test.', async () => {
+ await expect(el).to.be.accessible();
+ });
+ });
+
+ describe('when provided a ariaLabelledBy, and value parameter', async () => {
+ before(async () => {
+ el = await fixture(
+ html`
+
+
+ `
+ );
+ });
+
+ it('should render a component that passes accessibility test.', async () => {
+ await expect(el).to.be.accessible();
+ });
+ });
+});
diff --git a/src/components/progress-ring/progress-ring.ts b/src/components/progress-ring/progress-ring.ts
index 1eeccd65a..687069419 100644
--- a/src/components/progress-ring/progress-ring.ts
+++ b/src/components/progress-ring/progress-ring.ts
@@ -1,5 +1,6 @@
import { LitElement, html } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
+import { ifDefined } from 'lit/directives/if-defined.js';
import styles from './progress-ring.styles';
/**
@@ -27,6 +28,15 @@ export default class SlProgressRing extends LitElement {
/** The current progress, 0 to 100. */
@property({ type: Number, reflect: true }) value = 0;
+ /** When set, will place a hoverable title on the progress ring. */
+ @property() title: string;
+
+ /** When set, will place a label on the progress ring. */
+ @property() ariaLabel: string;
+
+ /** When set, will place a labelledby on the progress ring. */
+ @property() ariaLabelledBy: string;
+
updated(changedProps: Map) {
super.updated(changedProps);
@@ -50,6 +60,9 @@ export default class SlProgressRing extends LitElement {
part="base"
class="progress-ring"
role="progressbar"
+ title=${ifDefined(this.title)}
+ aria-label=${ifDefined(this.ariaLabel)}
+ aria-labelledby=${ifDefined(this.ariaLabelledBy)}
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="${this.value}"