Skip to content

feat(progresscircle): ensure s2 visual fidelity#6151

Open
marissahuysentruyt wants to merge 10 commits intoswc-1668/poc-componentsfrom
marissahuysentruyt/swc-1670-progress-circle
Open

feat(progresscircle): ensure s2 visual fidelity#6151
marissahuysentruyt wants to merge 10 commits intoswc-1668/poc-componentsfrom
marissahuysentruyt/swc-1670-progress-circle

Conversation

@marissahuysentruyt
Copy link
Copy Markdown
Collaborator

@marissahuysentruyt marissahuysentruyt commented Apr 7, 2026

Description

Cleans up progress circle styles in 2nd-gen. Also provides new accessibility improvements according to the a11y migration analysis.

  • Exports ProgressCircleSize type from @spectrum-web-components/core/components/progress-circle
  • Replaces hardcoded size templates in Sizes story with a PROGRESS_CIRCLE_VALID_SIZES.map() similar to badge and divider
  • Defines m as the default in the storybook control table
  • Adds new prefers-reduced-motion CSS media query
  • Creates dashOffset variable to ensure contrast is passed even when the progress is as 0% (notes in the code)

Motivation and context

The ProgressCircleSize type was already inferred in the types file but not exported, making it unavailable for stories and external consumers. Aligns with the pattern established in the badge component. The accessibility improvements set the component up for immediate success instead of bandaging something together later in life.

Related issue(s)

  • fixes swc-1670

Screenshots (if appropriate)


Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

Use these tables as a quick reference when validating the token usage in the browser!

Track color — --swc-progress-circle-track-border-color

Condition Token
Default track-color
static-color="white" static-white-track-color
static-color="black" static-black-track-color

Fill (indicator) color — --swc-progress-circle-fill-border-color

Condition Token
Default accent-content-color-default
static-color="white" static-white-track-indicator-color
static-color="black" static-black-track-indicator-color

Note: static-color="black" is new in S2. S1 only supported static-color="white".

Size-specific tokens

Property Token hook s m (default) l
Size --swc-progress-circle-size progress-circle-size-small progress-circle-size-medium progress-circle-size-large
Stroke width --swc-progress-circle-thickness progress-circle-thickness-small progress-circle-thickness-medium progress-circle-thickness-large
  • Progress circle stories render correctly at all sizes

    1. Go to the progress circle Storybook (Progress circle > Sizes)
    2. Verify all three sizes (s, m, l) render with the appropriate custom properties listed above.
  • Progress circle stories render correct color tokens

    1. Go to the progress circle Storybook docs page
    2. Inspect a default (non-static color) progress circle
    3. Verify the track and fill colors match the default tokens listed in the tables above.
    4. Inspect a static white progress circle in your browser.
    5. Verify the track and fill colors match the static white tokens listed in the tables above.
    6. Inspect a static black progress circle in your browser.
    7. Verify the track and fill colors match the static black tokens listed in the tables above.
  • Indeterminate story renders with animation

    1. Go to Progress circle > Indeterminate
    2. Verify the animated spinner is visible and the Processing request label is set
    3. Expect no aria-valuenow attribute on the host element
  • WHCM styles in AssistivLabs

    1. Go to the progress circle playground story in AssistivLabs or use the forced-colors emulator in Chrome
    2. Verify the track-border color and fill-border-color are visible in both dark and light high contrast modes. All variants (default, static white, static black) should appear identical in WHCM.
  • Reduced motion

    1. Go to the progress circle playground story and change the indeterminate control to true
    2. Verify you see the progress circle animation.
    3. In your computer settings, (System Settings > Accessibility > Motion if you're on a Mac), and turn on the Reduced Motion control.
    4. Verify the indeterminate progress circle animation follows your user setting and stops the animation. (example below!)
Screen.Recording.2026-04-07.at.4.21.39.PM.mov

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Accessibility testing checklist

Required: Complete each applicable item and document your testing steps (replace the placeholders with your component-specific instructions).

  • Keyboard
  1. Go to Progress circle > Playground in Storybook
  2. Confirm the component is not focusable (no interactive parts)
  • Screen reader (required — document steps below) — What to test for: Role and name are announced correctly; state changes (e.g. expanded, selected) are announced; labels and relationships are clear; no unnecessary or duplicate announcements.
  1. Go to Progress circle > Playground in Storybook with a screen reader active (VoiceOver/NVDA)
  2. It might be helpful to open the canvas in a fresh tab (outside of the iframe)
  3. Navigate to the progress circle element
  4. Expect the role progressbar to be announced, the aria-label to match the label arg (e.g. "Uploading
    document"), and aria-valuenow / aria-valuetext to reflect the current progress value
Screenshot 2026-04-07 at 2 02 58 PM
  1. Switch to the Indeterminate story and navigate to the element
  2. Expect the role progressbar to be announced with the label, but no value (no aria-valuenow)
Screenshot 2026-04-07 at 2 08 25 PM

NOTE: reduced motion testing steps are in the default "Manual testing" section

@marissahuysentruyt marissahuysentruyt self-assigned this Apr 7, 2026
@marissahuysentruyt marissahuysentruyt added Component:Progress circle Spectrum CSS 2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. labels Apr 7, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 7, 2026

⚠️ No Changeset found

Latest commit: b524d1f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

📚 Branch Preview Links

🔍 First Generation Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6151

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@marissahuysentruyt marissahuysentruyt added the Status:WIP PR is a work in progress or draft label Apr 7, 2026
.swc-ProgressCircle {
--swc-progress-circle-fill-border-color: Highlight;
--swc-progress-circle-track-color: Canvas;
--swc-progress-circle-track-border-color: Canvas;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@5t3ph do you think we could delete this Canvas definition for the track color? I think Canvas effectively "removes" the track, making it not visible. The additional dark and light queries also seem to be overriding Canvas anyways in AssistivLabs. Are you experiencing the same thing?

Image Image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you possibly resolve this already? I see the transparent values coming through in both "light" and "dark" themes. Since we are overwriting that var inside the nested light vs. dark queries, the initial assignment being pointed out here isn't being used.
image
image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't resolve anything yet, but I wanted to make sure I could remove that initial assignment to Canvas! I was seeing the light/dark theme transparent values like you screenshotted as well! Canvas was being overwritten by light/dark no matter what theme I was in.

I'm going to remove that initial track-border-color and just rely on the light/dark definitions. 👍

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following 👀 and will make sure that #6146 doesn't conflict, I removed that custom prop because it wasn't being used.

export const StaticColors: Story = {
render: (args) => html`
${ProgressCircle.STATIC_COLORS.map(
${PROGRESS_CIRCLE_STATIC_COLORS_S2.map(
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, I am importing the S2 suffixed types, even though I know the code styles say not to suffix those. I didn't want to overstep the progress circle code styles.

Comment on lines +107 to +109
@media (prefers-reduced-motion: reduce) {
.swc-ProgressCircle--indeterminate .swc-ProgressCircle-fill {
animation: none;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the reduced-motion query since it was in the a11y migration guide. Do we have a philosophy on reduced-motion? Right now, if a user has reduced motion turned on, it just doesn't do the indeterminate animation. Is that what we are envisioning?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikkimk just wanted to tag you here since I made some of the accessibility updates. I'd love to get your opinions on the reduced-motion and then the dashOffset concept so that even an empty/0% progress circle still shows a bit of the fill to meet contrast.

Comment on lines +79 to +88
// At progress=0, a dashoffset of 100 makes the fill fully invisible, which fails WCAG 1.4.11
// non-text contrast (the track alone may not meet 3:1 against the background).
// Clamp to 98 to show a minimum 2-unit fill so the graphical element remains perceivable.
// aria-valuenow stays at 0 — this is a visual-only adjustment.
const dashOffset = this.indeterminate
? 100 - this.progress
: this.progress === 0
? 98
: 100 - this.progress;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the a11y migration, I saw this:

When **`progress`** is **0**, show a **small** filled segment of the circle (or another treatment that keeps **graphical** ring details at **at least 3:1** with adjacent colors). A **fully empty** ring at **0%** often fails **non-text contrast** because the **track** alone is too weak against the background.

So, that's what this dashOffset is attempting to do. Do we have opinions on like, how much of the fill we'd like to continue to show when the value is 0? 98 is a completely magic number. ✨ ✨ ✨

The 0% fill is the first one in this set.

Image

[`swc-ProgressCircle--indeterminate`]: this.indeterminate,
[`swc-ProgressCircle--static${capitalize(this.staticColor)}`]:
typeof this.staticColor !== 'undefined',
[`swc-ProgressCircle--size${this.size?.toUpperCase()}`]:
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need the size classes on the internal wrapper div.

@marissahuysentruyt marissahuysentruyt marked this pull request as ready for review April 7, 2026 20:52
@marissahuysentruyt marissahuysentruyt requested a review from a team as a code owner April 7, 2026 20:52
@marissahuysentruyt marissahuysentruyt added Status:Ready for review PR ready for review or re-review. and removed Status:WIP PR is a work in progress or draft labels Apr 7, 2026
@marissahuysentruyt marissahuysentruyt force-pushed the swc-1668/poc-components branch from 419e8bf to 5038447 Compare April 8, 2026 13:08
rise-erpelding added a commit that referenced this pull request Apr 8, 2026
This reverts commit db5a16f.

This commit and the commit that it reverts will be dropped if this
branch needs a rebase if #6151 goes in first.
@marissahuysentruyt marissahuysentruyt force-pushed the marissahuysentruyt/swc-1670-progress-circle branch from f635672 to 181944f Compare April 8, 2026 22:30
Comment on lines +288 to +292
* #### Non-interactive element
*
* - Progress circles have no interactive behavior and are not focusable
* - Screen readers will announce the progress circle content as static text
* - No keyboard interaction is required or expected
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikkimk is this the right language for progress circles? I'm most concerned with the screen reader bullet point (like is it "static text" or something else?)

@rise-erpelding rise-erpelding force-pushed the swc-1668/poc-components branch from a099390 to 06a2855 Compare April 9, 2026 13:42
rise-erpelding added a commit that referenced this pull request Apr 9, 2026
This reverts commit db5a16f.

This commit and the commit that it reverts will be dropped if this
branch needs a rebase if #6151 goes in first.
Copy link
Copy Markdown
Contributor

@nikkimk nikkimk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some requested changes based on these proposed design guidelines: https://www.figma.com/design/42VzvpW262EAUbYsadO4e8/Loading-animation-discovery

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SVG is showing up in the a11y tree as an image with no alt text. Try this:

Suggested change
<svg aria-hidden="true" fill="none" width="100%" height="100%" class="swc-outerCircle">

Comment on lines 251 to 253
args: {
indeterminate: true,
size: 'm',
label: 'Processing request',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New guidelines for progress bars and circles is that they are indeterminate by default unless there is a value given:

https://www.figma.com/design/42VzvpW262EAUbYsadO4e8/Loading-animation-discovery?node-id=478-948465&t=7bf2ViRr4MWMGhre-0

*
* @property {string} static-color - Static color variant for use on different backgrounds.
* @property {number} progress - Progress value between 0 and 100.
* @property {boolean} indeterminate - Indeterminate state for loading.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be deprecated. A progress circle should be indeterminate until a value is given, so we can set indeterminate animation based on whether value is undefined.

New guidelines for progress bars and circles is that they are indeterminate by default unless there is a value given:

https://www.figma.com/design/42VzvpW262EAUbYsadO4e8/Loading-animation-discovery?node-id=478-948465&t=7bf2ViRr4MWMGhre-0

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a similar suggestion in the code styles PR, and also similarly, it feels out of scope for this ticket. Could we add another todo in the comments, referencing ticket 1891.

Does that work for now while the poc feature branch and epic are both still wrapping up?

// non-text contrast (the track alone may not meet 3:1 against the background).
// Clamp to 98 to show a minimum 2-unit fill so the graphical element remains perceivable.
// aria-valuenow stays at 0 — this is a visual-only adjustment.
const dashOffset = this.indeterminate
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic should be based on if value is undefined. We can deprecate indeterminate

<div
class=${classMap({
['swc-ProgressCircle']: true,
[`swc-ProgressCircle--indeterminate`]: this.indeterminate,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do this based on undefined value

* <swc-progress-circle progress="75" label="Loading progress"></swc-progress-circle>
*
* @example
* <swc-progress-circle indeterminate label="Loading..."></swc-progress-circle>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* <swc-progress-circle label="Loading..."></swc-progress-circle>


/**
* The indeterminate state shows an animated loading indicator when progress is unknown or cannot be determined.
* Set the `indeterminate` attribute to `true` to activate this state.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* If no value is set, the progress circle is indeterminate.

* 3. **Progress state** (determinate):
* - Sets `aria-valuenow` with the current `progress` value
* 4. **Loading state** (indeterminate):
* - Removes `aria-valuenow` when `indeterminate="true"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* - Removes `aria-valuenow` when no value is given.

* - Use specific, meaningful labels (e.g., "Uploading profile photo" instead of "Loading")
* - Use determinate progress (`progress="50"`) when possible to give users a clear sense of completion
* - For determinate progress, ensure the `progress` value accurately reflects the actual progress
* - Use indeterminate progress only when duration is truly unknown
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* - Use indeterminate progress only when duration is truly unknown or when the wait is less than 3 seconds.

* - Ensure sufficient color contrast between the progress circle and its background
* - Use `static-color="white"` on dark backgrounds or `static-color="black"` on light backgrounds
* - Test with screen readers to verify progress announcements are clear and timely
* - Avoid updating progress values more frequently than every 1-2 seconds to prevent announcement overload
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From https://www.figma.com/design/42VzvpW262EAUbYsadO4e8/Loading-animation-discovery?node-id=478-949150&t=7bf2ViRr4MWMGhre-0

Suggested change
* - Avoid updating progress values more frequently than every 1-2 seconds to prevent announcement overload
* - Do not force live region announcements for progress durations that are 3 seconds or less. Instead, consider status messages when progress is complete or there is an error.

to make sure a 0% fill progress circle meets contrast requirements, the
dashOffset variable was created so that a small portion of the fill
remains visible. this is a visual change only, and does not affect
labels or aria-values
@marissahuysentruyt marissahuysentruyt force-pushed the marissahuysentruyt/swc-1670-progress-circle branch from 181944f to b524d1f Compare April 10, 2026 16:19
@marissahuysentruyt marissahuysentruyt removed their assignment Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. Component:Progress circle Spectrum CSS Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants