Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f2b0881
chore(avatar): add 1st pass of migration plan
cdransf Mar 30, 2026
c974868
chore(avatar): iterate on component structure
cdransf Mar 30, 2026
7869c9a
chore(avatar): iterate on styles
cdransf Mar 30, 2026
ea87867
chore(avatar): add stubs to storybook sidebar
cdransf Mar 31, 2026
e2c5d51
chore(avatar): improve alignment w/react spectrum + design docs
cdransf Mar 31, 2026
02cee92
chore(avatar): improved storybook controls
cdransf Mar 31, 2026
1994cce
fix(avatar): lint violations + storybook improvements
cdransf Apr 1, 2026
6bf586a
fix(avatar): lint violations
cdransf Apr 1, 2026
4f442cc
chore(avatar): revise migration plan
cdransf Apr 1, 2026
b94f730
chore(avatar): migrate api + typescript code
cdransf Apr 1, 2026
43f999c
chore(avatar): implement a11y recommendations + aria-hidden on host e…
cdransf Apr 1, 2026
f8badd8
chore(avatar): migrate CSS to S2 style guide conventions
cdransf Apr 1, 2026
a98a220
chore(avatar): add Storybook interaction tests and Playwright ARIA sn…
cdransf Apr 2, 2026
7c479da
chore(avatar): add Storybook documentation, args table defaults, and …
cdransf Apr 2, 2026
ce7686e
chore(avatar): create consumer-facing migration guide
cdransf Apr 2, 2026
22a934e
chore(avatar): fix test failures
cdransf Apr 3, 2026
25ebbaf
fix(avatar): clean up todos and other minor issues
cdransf Apr 3, 2026
37f7a63
chore(avatar): adds disabled variant, tests and updates docs
cdransf Apr 3, 2026
7579223
Update 2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts
cdransf Apr 6, 2026
34356e9
Update 2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts
cdransf Apr 6, 2026
25c5bde
Update CONTRIBUTOR-DOCS/03_project-planning/03_components/avatar/migr…
cdransf Apr 6, 2026
047537f
Update CONTRIBUTOR-DOCS/03_project-planning/03_components/avatar/migr…
cdransf Apr 6, 2026
fb4d1c3
Update CONTRIBUTOR-DOCS/03_project-planning/03_components/avatar/migr…
cdransf Apr 6, 2026
20bdcba
Update 2nd-gen/packages/core/components/avatar/Avatar.base.ts
cdransf Apr 6, 2026
8d7d415
Update 2nd-gen/packages/core/components/avatar/Avatar.base.ts
cdransf Apr 6, 2026
8766779
Update 2nd-gen/packages/swc/components/avatar/migration.md
cdransf Apr 6, 2026
a2e6a83
Update 2nd-gen/packages/swc/components/avatar/migration.md
cdransf Apr 6, 2026
1127f36
Update 2nd-gen/packages/swc/components/avatar/migration.md
cdransf Apr 6, 2026
266cbf3
Update 2nd-gen/packages/swc/components/avatar/migration.md
cdransf Apr 6, 2026
bcb8ac0
Update 2nd-gen/packages/core/components/avatar/Avatar.base.ts
cdransf Apr 6, 2026
d95f4e4
Update 2nd-gen/packages/core/components/avatar/Avatar.base.ts
cdransf Apr 6, 2026
32a3aaa
Update 2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts
cdransf Apr 6, 2026
7b1f44d
Update 2nd-gen/packages/core/components/avatar/Avatar.base.ts
cdransf Apr 6, 2026
70b00d0
Update 2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts
cdransf Apr 6, 2026
80635fb
Update 2nd-gen/packages/core/components/avatar/Avatar.base.ts
cdransf Apr 6, 2026
5ca213c
fix(avatar): additional refactoring to properly support decorative pr…
cdransf Apr 6, 2026
8c5d9e2
Update 2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts
cdransf Apr 8, 2026
b072066
Update 2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts
cdransf Apr 8, 2026
9ff3672
Update 2nd-gen/packages/swc/components/avatar/test/avatar.test.ts
cdransf Apr 8, 2026
a63d98c
Update 2nd-gen/packages/swc/components/avatar/test/avatar.test.ts
cdransf Apr 8, 2026
1649d38
fix(avatar): remove duplicate test expectation
cdransf Apr 8, 2026
926af3d
Update 2nd-gen/packages/swc/components/avatar/avatar.css
cdransf Apr 8, 2026
35af4cc
docs(avatar): remove linked variant content from accessibility analysis
cdransf Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions 2nd-gen/packages/core/components/avatar/Avatar.base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { PropertyValues } from 'lit';
import { property } from 'lit/decorators.js';

import { SpectrumElement } from '@spectrum-web-components/core/element/index.js';

import {
AVATAR_DEFAULT_SIZE,
AVATAR_VALID_SIZES,
type AvatarSize,
} from './Avatar.types.js';

/**
* Base class for the avatar component.
*
* Provides the core API for displaying a circular profile image. Concrete
* classes supply the stylesheet and render template.
*/
export abstract class AvatarBase extends SpectrumElement {
// ─────────────────────────
// STATIC
// ─────────────────────────

/**
* @internal
*
* The set of valid numeric size values for the avatar.
*
* This is an internal property not intended for consumer use, but used in
* internal validation logic, stories, and tests to keep them in sync with
* the canonical type definition in `Avatar.types.ts`.
*/
static readonly VALID_SIZES: readonly AvatarSize[] = AVATAR_VALID_SIZES;

// ──────────────────
// CORE API
// ──────────────────

/**
* URL of the profile image to display.
*/
@property({ type: String })
public src = '';

/**
* Text description of the avatar image.
*
* Becomes the `alt` attribute on the underlying `<img>` element.
* Pass `alt=""` to treat the image as decorative — the host receives
* `aria-hidden="true"` so the entire shadow tree is hidden from assistive
* technology. If omitted, the image renders with `alt=""` and a DEBUG
* warning is issued; only the warning distinguishes this from an
* intentional decorative image.
*/
@property({ type: String })
public alt: string | undefined;

// ───────────────────
// SIZE API
// ───────────────────

/**
* The size of the avatar. Invalid values fall back to the default (500).
*/
@property({ type: Number, reflect: true })
public get size(): AvatarSize {
return this._size;
}

public set size(value: AvatarSize) {
const validSize = (AVATAR_VALID_SIZES as readonly number[]).includes(
Number(value)
)
? (Number(value) as AvatarSize)
: AVATAR_DEFAULT_SIZE;

if (this._size === validSize) {
return;
}

const oldSize = this._size;
this._size = validSize;
this.requestUpdate('size', oldSize);
}

private _size: AvatarSize = AVATAR_DEFAULT_SIZE;

// ───────────────────────
// VISUAL API
// ───────────────────────

/**
* Renders a stroke (outline) around the avatar image to create visual
* separation from adjacent content. Defaults to `true` within an Avatar
* Group; set explicitly on a standalone avatar when the image border color
* matches the surrounding background.
*/
@property({ type: Boolean, reflect: true, attribute: 'show-stroke' })
public showStroke = false;
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.

I'm wondering about alternatives, like hasStroke or isOutlined or even just outlined? Something about "stroke" is off to me, versus outline or border.

The design token ultimately uses border as the naming, but I know the desktop specs use stroke. If nothing else, I would align our exposed custom property name to this attribute, wherever it lands.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Badge uses outline, so we could align with that? It would get us away from stroke and make it consistent with what we already have. ✨


/**
* Renders the avatar at reduced opacity, indicating the entity is not
* currently active or available. The avatar remains present in the layout
* and accessible to assistive technology.
*/
@property({ type: Boolean, reflect: true })
public disabled = false;

// ───────────────────────────
// ACCESSIBILITY API
// ───────────────────────────

/**
* Marks the avatar as decorative, hiding it from assistive technology.
*
* Use when the surrounding context already identifies the person (e.g.,
* their name appears next to the avatar). Setting this attribute causes the
* host to receive `aria-hidden="true"`, removing it from the accessibility
* tree. Equivalent to setting `alt=""`.
*/
@property({ type: Boolean, reflect: true })
public decorative = false;

// ──────────────────────────────────────────
// DEPRECATED — 1st-gen compat shims
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.

Maybe I missed it, but do we have documented that we want to support shims like this? I was under the impression we were ok with breaking changes, and that things like this can live in a migration plan?

// ──────────────────────────────────────────

/**
* @deprecated Use `alt` instead. This shim will be removed in a future release.
*
* **Breaking change:** In 1st-gen, `label` was the primary way to provide
* alternative text for the avatar image. In 2nd-gen, use `alt` instead.
*/
public get label(): string | undefined {
return this.alt;
}

public set label(value: string | undefined) {
if (window.__swc?.DEBUG) {
window.__swc.warn(
this,
`The "label" property on <${this.localName}> is deprecated. Use "alt" instead.`,
'https://opensource.adobe.com/spectrum-web-components/components/avatar/',
{ type: 'api', level: 'deprecation' }
);
}
this.alt = value;
}

/**
* @deprecated Use `decorative` instead. This shim will be removed in a future release.
*
* **Breaking change:** In 1st-gen, `isDecorative` was used to mark an avatar
* as decorative. In 2nd-gen, use the `decorative` attribute instead.
*
* **Note:** This shim covers the JS property API only. The `is-decorative`
* HTML attribute is not shimmed — consumers using the attribute must migrate
* to `decorative`.
*/
public get isDecorative(): boolean {
return this.decorative;
}

public set isDecorative(value: boolean) {
if (window.__swc?.DEBUG) {
window.__swc.warn(
this,
`The "isDecorative" property on <${this.localName}> is deprecated. Use "decorative" instead.`,
'https://opensource.adobe.com/spectrum-web-components/components/avatar/',
{ type: 'api', level: 'deprecation' }
);
}
this.decorative = value;
}

// ──────────────────────
// IMPLEMENTATION
// ──────────────────────

protected override firstUpdated(changes: PropertyValues): void {
super.firstUpdated(changes);
if (!this.hasAttribute('size')) {
this.setAttribute('size', String(this.size));
}
this._syncAriaHidden();
if (window.__swc?.DEBUG) {
this._warnMissingAlt();
}
}

protected override updated(changes: PropertyValues): void {
super.updated(changes);
if (changes.has('decorative')) {
this._syncAriaHidden();
}
if (changes.has('alt') && window.__swc?.DEBUG) {
this._warnMissingAlt();
}
}

private _syncAriaHidden(): void {
if (this.decorative) {
this.setAttribute('aria-hidden', 'true');
} else {
this.removeAttribute('aria-hidden');
}
}

private _warnMissingAlt(): void {
if (this.alt === undefined && !this.decorative) {
window.__swc?.warn(
this,
`<${this.localName}> is missing an \`alt\` attribute. Provide a text description or pass \`alt=""\` and mark it as \`decorative\`.`,
'https://opensource.adobe.com/spectrum-web-components/components/avatar/#accessibility',
{
type: 'accessibility',
issues: [
'Provide an `alt` attribute with meaningful alternative text, or',
'Set `alt=""` and mark the image as `decorative` (hidden from screen readers).',
],
}
);
}
}
}
25 changes: 25 additions & 0 deletions 2nd-gen/packages/core/components/avatar/Avatar.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/**
* Valid numeric size values for the Avatar component.
*
* Sizes 50–700 match 1st-gen. Sizes 800–1500 are new in Spectrum 2.
*/
export const AVATAR_VALID_SIZES = [
50, 75, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300,
1400, 1500,
] as const;

export type AvatarSize = (typeof AVATAR_VALID_SIZES)[number];

export const AVATAR_DEFAULT_SIZE = 500 as const satisfies AvatarSize;
13 changes: 13 additions & 0 deletions 2nd-gen/packages/core/components/avatar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
export * from './Avatar.base.js';
export * from './Avatar.types.js';
7 changes: 7 additions & 0 deletions 2nd-gen/packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
"types": "./dist/components/asset/index.d.ts",
"import": "./dist/components/asset/index.js"
},
"./components/avatar": {
"types": "./dist/components/avatar/index.d.ts",
"import": "./dist/components/avatar/index.js"
},
"./components/badge": {
"types": "./dist/components/badge/index.d.ts",
"import": "./dist/components/badge/index.js"
Expand Down Expand Up @@ -131,6 +135,9 @@
"components/asset": [
"dist/components/asset/index.d.ts"
],
"components/avatar": [
"dist/components/avatar/index.d.ts"
],
"components/badge": [
"dist/components/badge/index.d.ts"
],
Expand Down
14 changes: 13 additions & 1 deletion 2nd-gen/packages/swc/.storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,11 @@ const preview = {
'Asset',
['Rendering and styling migration analysis'],
'Avatar',
['Rendering and styling migration analysis'],
[
'Accessibility migration analysis',
'Migration plan',
'Rendering and styling migration analysis',
],
'Badge',
[
'Accessibility migration analysis',
Expand Down Expand Up @@ -425,6 +429,14 @@ const preview = {
},
},
},
// Hide SpectrumElement infrastructure members from every component's API table.
// These are internal properties that consumers should not configure directly.
argTypes: {
dir: { table: { disable: true } },
VERSION: { table: { disable: true } },
CORE_VERSION: { table: { disable: true } },
hasVisibleFocusInTree: { table: { disable: true } },
},
tags: ['!autodocs', '!dev'], // We only want the playground stories to be visible in the docs and sidenav. Since a majority of our stories are tagged with '!autodocs' and '!dev', we set those tags globally. We can opt in to visibility by adding the 'autodocs' or 'dev' tags to individual stories.
loaders: [FontLoader],
};
Expand Down
48 changes: 48 additions & 0 deletions 2nd-gen/packages/swc/components/avatar/Avatar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { CSSResultArray, html, TemplateResult } from 'lit';

import { AvatarBase } from '@spectrum-web-components/core/components/avatar';

import styles from './avatar.css';

/**
* A static avatar component that displays a circular user profile image.
*
* Provide `alt` with a description of the person or entity depicted.
* Pass `alt=""` to treat the image as decorative and hide it from assistive
* technology.
*
* @element swc-avatar
*
* @example
* <swc-avatar src="/path/to/image.jpg" alt="Jane Doe"></swc-avatar>
*
* @example
* <swc-avatar src="/path/to/image.jpg" alt=""></swc-avatar>
*
* @example
* <swc-avatar src="/path/to/image.jpg" alt="Jane Doe" show-stroke></swc-avatar>
*/
export class Avatar extends AvatarBase {
public static override get styles(): CSSResultArray {
return [styles];
}

protected override render(): TemplateResult {
return html`
<div class="swc-Avatar">
<img class="swc-Avatar-image" src=${this.src} alt=${this.alt ?? ''} />
</div>
`;
}
}
Loading
Loading