feat(progresscircle): ensure s2 visual fidelity#6151
feat(progresscircle): ensure s2 visual fidelity#6151caseyisonit merged 4 commits intoswc-1668/poc-componentsfrom
Conversation
|
📚 Branch Preview Links🔍 First Generation Visual Regression Test ResultsWhen 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: If the changes are expected, update the |
| .swc-ProgressCircle { | ||
| --swc-progress-circle-fill-border-color: Highlight; | ||
| --swc-progress-circle-track-color: Canvas; | ||
| --swc-progress-circle-track-border-color: Canvas; |
There was a problem hiding this comment.
@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?
There was a problem hiding this comment.
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. 👍
There was a problem hiding this comment.
Following 👀 and will make sure that #6146 doesn't conflict, I removed that custom prop because it wasn't being used.
| @media (prefers-reduced-motion: reduce) { | ||
| .swc-ProgressCircle--indeterminate .swc-ProgressCircle-fill { | ||
| animation: none; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
The visual effect of none is a filled-in indicator, which seems too extreme and loses meaning.
I think we would have a couple options:
- also add
stroke-dashoffset: 75px(or other value) to show a partial indicator to keep meaning of a load in-progress - instead of stopping animation, instead slow it waaaay down and have it be linear rather than the "jump/catch-up" effect it has to remove the "blinking/flashing" effect
reduced-motion-progress-indicator.mov
| // 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; | ||
|
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
turned this in to a private method with jsdocs
| [`swc-ProgressCircle--indeterminate`]: this.indeterminate, | ||
| [`swc-ProgressCircle--static${capitalize(this.staticColor)}`]: | ||
| typeof this.staticColor !== 'undefined', | ||
| [`swc-ProgressCircle--size${this.size?.toUpperCase()}`]: |
There was a problem hiding this comment.
We don't need the size classes on the internal wrapper div.
419e8bf to
5038447
Compare
f635672 to
181944f
Compare
| * #### 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 |
There was a problem hiding this comment.
@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?)
a099390 to
06a2855
Compare
nikkimk
left a comment
There was a problem hiding this comment.
I have some requested changes based on these proposed design guidelines: https://www.figma.com/design/42VzvpW262EAUbYsadO4e8/Loading-animation-discovery
There was a problem hiding this comment.
The SVG is showing up in the a11y tree as an image with no alt text. Try this:
| <svg aria-hidden="true" fill="none" width="100%" height="100%" class="swc-outerCircle"> |
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
i went ahead and refactored it in this PR
There was a problem hiding this comment.
| * - Removes `aria-valuenow` when no value is given. |
181944f to
b524d1f
Compare
e0d3061 to
b446021
Compare
- Exports `ProgressCircleSize` type from core
- Adds `prefers-reduced-motion` CSS media query
- Adds `dashOffset` variable to ensure contrast at 0% progress
- Replaces hardcoded size templates with `PROGRESS_CIRCLE_VALID_SIZES.map()`
- Removes sizing antipattern in render
- Removes canvas WHCM track color
- Implements a11y improvements per migration analysis
Also includes minor cross-component cleanup:
- Renames `STATUSLIGHT_*` constants to `STATUS_LIGHT_*` convention
- Adds `{ type: String }` to `@property()` decorators in Asset and Icon base classes
- Fixes `customElements` path in swc package.json
- Tags icon internal stories as migrated
- Updates Storybook source type from auto to dynamic
- Minor avatar stories and status-light stories updates
fixes swc-1670
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0c46b62 to
0a87ab4
Compare
…70-progress-circle
@nikkimk im going to dismiss this changes requested to unblock this merging to the feature branch. you will be a required reviewer in the final PR review but i have applied all of your feedback. Hope you feel better!!
5t3ph
left a comment
There was a problem hiding this comment.
None of these are blocking for MVP, so adding approval.
| * 3. **Label** - Accessible text describing the operation (not visually rendered). | ||
| * | ||
| * ### Content | ||
| * > **A11y Note:** Light DOM children are not projected into the shadow tree, so content between the opening and closing tags does not supply an accessible name. Use the `label` attribute or property, or `aria-label` / `aria-labelledby` on the host. |
There was a problem hiding this comment.
[todo] we perhaps should have a take-away here to figure out patterns for "callouts" especially since the semantics of this being a blockquote are imperfect (although handy for markdown)
There was a problem hiding this comment.
i agree! i will flag this in a comment. i think this also ties nicely into the CEM additions i would like to introduce and we can create a custom doc block for rendering these in a consistent manner
| * @property {boolean} indeterminate - Indeterminate state for loading. | ||
| * @property {number | null} progress - Progress between 0 and 100, reflected as the `progress` attribute when set. When `null` (indeterminate), the attribute is omitted. | ||
| * @property {string} size - Size of the component. | ||
| * @property {string} label - Label for the component. |
There was a problem hiding this comment.
(this can be a follow-up/possible group discussion): for button migration, I am suggesting dropping label in favor of the appropriate aria attributes only to simplify the API / make intent more explicit, and also not muddy "label" in case it ends up used as a slot name ever.
There was a problem hiding this comment.
OMG i had the same thought!! i actaully would like to do that for progress circle because that actually aligns more with the implementation and usage of RSP and good semantic a11y HTML
| @media (prefers-reduced-motion: reduce) { | ||
| .swc-ProgressCircle--indeterminate .swc-ProgressCircle-fill { | ||
| animation: none; |
There was a problem hiding this comment.
The visual effect of none is a filled-in indicator, which seems too extreme and loses meaning.
I think we would have a couple options:
- also add
stroke-dashoffset: 75px(or other value) to show a partial indicator to keep meaning of a load in-progress - instead of stopping animation, instead slow it waaaay down and have it be linear rather than the "jump/catch-up" effect it has to remove the "blinking/flashing" effect
reduced-motion-progress-indicator.mov
cdransf
left a comment
There was a problem hiding this comment.
A few minor things, but looks great! ✨
| style: 'percent', | ||
| unitDisplay: 'narrow', | ||
| }).format(this.progress / 100); | ||
| this.setAttribute('role', 'progressbar'); |
There was a problem hiding this comment.
Is there a reason we removed the guard here? Would this remove whatever the user/consumer sets? ✨
There was a problem hiding this comment.
good catch this was not intended to be removed, i will add this back in!
There was a problem hiding this comment.
oh after looking at this, the format progess is handled in the updated() to make sure it stays in sync with the reflective attribute which with trigger an updated lifecycle. the formatProgress method was moved up to the methods section since it was sandwiched between two lifecycles. its still present
| size: 'l', | ||
| label: 'Upload complete', | ||
| })} | ||
| render: () => html` |
There was a problem hiding this comment.
I think we need to pass args here to wire up the controls ✨
| render: () => html` | |
| render: (args) => html` |
There was a problem hiding this comment.
ill wire it up with args, will need to spread them in the components so ill do it in a commit
| */ | ||
| export const Indeterminate: Story = { | ||
| export const ProgressValues: Story = { | ||
| render: () => html` |
There was a problem hiding this comment.
We lose the storybook controls here too. ✨
| render: () => html` | |
| render: (args) => html` |
There was a problem hiding this comment.
same as a above, this will come via a new commit
| } else { | ||
|
|
||
| if (changes.has('progress')) { | ||
| if (this.progress !== null && this.progress >= 0) { |
There was a problem hiding this comment.
We can simplify this ✨
| if (this.progress !== null && this.progress >= 0) { | |
| if (this.progress !== null) { |
There was a problem hiding this comment.
actually it is needed because setting this.progree === 0 also can compile to null so this makes sure its reading zero as a value and not as a falsey statement


Description
Cleans up progress circle styles in 2nd-gen. Also provides new accessibility improvements according to the a11y migration analysis.
ProgressCircleSizetype from@spectrum-web-components/core/components/progress-circleSizesstory with aPROGRESS_CIRCLE_VALID_SIZES.map()similar to badge and dividermas the default in the storybook control tableprefers-reduced-motionCSS media querydashOffsetvariable to ensure contrast is passed even when the progress is as 0% (notes in the code)Motivation and context
The
ProgressCircleSizetype 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)
Screenshots (if appropriate)
Author's checklist
Reviewer's checklist
patch,minor, ormajorfeaturesManual 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-colortrack-colorstatic-color="white"static-white-track-colorstatic-color="black"static-black-track-colorFill (indicator) color —
--swc-progress-circle-fill-border-coloraccent-content-color-defaultstatic-color="white"static-white-track-indicator-colorstatic-color="black"static-black-track-indicator-colorNote:
static-color="black"is new in S2. S1 only supportedstatic-color="white".Size-specific tokens
sm(default)l--swc-progress-circle-sizeprogress-circle-size-smallprogress-circle-size-mediumprogress-circle-size-large--swc-progress-circle-thicknessprogress-circle-thickness-smallprogress-circle-thickness-mediumprogress-circle-thickness-largeProgress circle stories render correctly at all sizes
Progress circle > Sizes)s,m,l) render with the appropriate custom properties listed above.Progress circle stories render correct color tokens
Indeterminate story renders with animation
Progress circle > IndeterminateProcessing requestlabel is setaria-valuenowattribute on the host elementWHCM styles in AssistivLabs
Reduced motion
indeterminatecontrol to trueScreen.Recording.2026-04-07.at.4.21.39.PM.mov
Device review
Accessibility testing checklist
Required: Complete each applicable item and document your testing steps (replace the placeholders with your component-specific instructions).
Progress circle > Playgroundin StorybookProgress circle > Playgroundin Storybook with a screen reader active (VoiceOver/NVDA)progressbarto be announced, thearia-labelto match thelabelarg (e.g. "Uploadingdocument"), and
aria-valuenow/aria-valuetextto reflect the current progress valueIndeterminatestory and navigate to the elementprogressbarto be announced with the label, but no value (noaria-valuenow)NOTE: reduced motion testing steps are in the default "Manual testing" section