Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
9 changes: 9 additions & 0 deletions .changeset/fix-action-button-avatar-icon-size.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@adobe/spectrum-wc': patch
---

**fix(action-button):** A `<swc-avatar>` slotted into `<swc-action-button>`'s `icon` slot now automatically scales to match the button's icon size.

Previously, Avatar's own `size` attribute controlled its rendered size regardless of the action button's size, since Avatar's `:host([size])` sizing rule won the specificity contest with the icon slot's generic sizing rule for the avatar's _host box_ only — the visible image inside Avatar's shadow root ignored that squeeze entirely and rendered at its own `size`, causing it to overflow. Consumers previously had to manually pair an avatar `size` with the action-button `size` (e.g. `xl` action button with avatar `size="1000"`) to avoid a visual mismatch.

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 needs a VRT run.

@rubencarvalho rubencarvalho Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great call! I even added this to the vrt permutations :)

@rubencarvalho rubencarvalho Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm glad that I did because I found another bug!!!!

Lit still leaves its child-part boundary marker, a comment node <!--?lit$…$--> with non-empty data as a direct light-DOM child of <swc-action-button>. With no slot attribute, it lands in the default slot.

Then in slot-text-controller.ts, checkInitialContent() classifies nodes as element-or-text:

if (el.tagName) {  }              // elements
if (this.slotName) return false;   // named slots
return node.textContent ? node.textContent.trim().length > 0 : false;  // ← comment falls here

A comment node has no tagName, so it hits the last branch, and node.textContent returns the comment's data (?lit$…$) → trims to non-empty → hasLabel = true → iconOnly = false.


The icon slot now also sets `--swc-avatar-size` directly (the same way it already sets its own internal icon-size custom properties), which wins over Avatar's own `:host([size])` rule for a slotted avatar specifically because of how shadow-tree custom property cascading resolves rules matching via `::slotted()` against rules from the slotted element's own shadow tree. Avatar's `size` attribute becomes a no-op when slotted into an action button's icon slot; standalone avatar sizing is unaffected.
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ slot[name="icon"]::slotted(*),
slot[name="icon"]::slotted(*) {
color: inherit;
fill: currentcolor;

/* Lets a slotted <swc-avatar> track the icon slot's own size instead of
requiring consumers to pair its size attribute with the button's size. */
--swc-avatar-size: var(--_swc-action-button-icon-size);

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.

Perhaps make this more explicitly scoped to ::slotted(swc-avatar)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

applied the recommendation!

}

/* ── Sizes ────────────────────────────────────────── */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import { html } from 'lit';
import type { Meta, StoryObj as Story } from '@storybook/web-components';

import '@adobe/spectrum-wc/components/avatar/swc-avatar.js';

import {
FORCED_STATES,
forcePseudoStates,
Expand Down Expand Up @@ -63,6 +65,22 @@ const asLinkAndButton = (classes: string, label: string) => [
`,
];

// A <swc-avatar> carrying the `.swc-ActionButton-icon` class, which the global
// stylesheet gives `--swc-avatar-size: var(--_swc-action-button-icon-size)` so
// the avatar tracks the button size — the class-based counterpart to the
// shadow-DOM ::slotted rule covered in action-button.vrt.ts. A fixed picsum id
// keeps the image deterministic for Chromatic (matching the docs stories).
const AVATAR_ICON_SRC = 'https://picsum.photos/id/64/500/500';

const globalAvatarIcon = html`
<swc-avatar
class="swc-ActionButton-icon"
src=${AVATAR_ICON_SRC}
alt=""
decorative
></swc-avatar>
`;

// Medium is the default (no size class); the rest map to their modifier class.
const SIZE_CASES = [
{ classes: 'swc-ActionButton--sizeXs', label: 'Extra-small' },
Expand Down Expand Up @@ -126,6 +144,31 @@ const globalStylesContent = () => html`
],
'Anatomy'
)}
${row(
[
...SIZE_CASES.map(
({ classes, label }) => html`
<button
type="button"
class="swc-ActionButton swc-ActionButton--hasIcon ${classes}"
>
${globalAvatarIcon}
<span class="swc-ActionButton-label">${label}</span>
</button>
`
),
html`
<button
type="button"
class="swc-ActionButton swc-ActionButton--iconOnly"
aria-label="Jane Doe"
>
${globalAvatarIcon}
</button>
`,
],
'Avatar icon'
)}
${staticColorBackground(
row(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
* governing permissions and limitations under the License.
*/

import { html } from 'lit';
import { html, nothing } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import type { Meta, StoryObj as Story } from '@storybook/web-components';

import {
Expand All @@ -19,6 +20,7 @@ import {
} from '@adobe/spectrum-wc-core/components/action-button';

import '@adobe/spectrum-wc/components/action-button/swc-action-button.js';
import '@adobe/spectrum-wc/components/avatar/swc-avatar.js';

import {
createPermutations,
Expand Down Expand Up @@ -156,6 +158,33 @@ const renderIconOnlyPermutation = ({
</swc-action-button>
`;

// A slotted <swc-avatar> as the icon. action-button.css sets
// `--swc-avatar-size: var(--_swc-action-button-icon-size)` on the icon slot, so
// the avatar tracks each button size without the consumer restating a size on
// the avatar. Written as direct markup (not template()) for the same icon-slot
// parsing reason as icon-only above, and rendered across every size because
// size is the axis this scaling actually drives. A fixed picsum id keeps the
// image deterministic for Chromatic (matching the docs stories).
const AVATAR_ICON_SRC = 'https://picsum.photos/id/64/500/500';

const renderAvatarActionButton = (
size: (typeof ACTION_BUTTON_VALID_SIZES)[number],
iconOnly: boolean
) => html`
<swc-action-button
size=${size}
accessible-label=${ifDefined(iconOnly ? 'Jane Doe' : undefined)}
>
<swc-avatar
slot="icon"
src=${AVATAR_ICON_SRC}
alt=""
decorative
></swc-avatar>
${iconOnly ? nothing : 'Jane Doe'}
</swc-action-button>
`;

const forceActionButtonStates = forcePseudoStates(
'swc-action-button[data-force-state]',
'.swc-ActionButton'
Expand Down Expand Up @@ -185,6 +214,17 @@ const permutationContent = () => html`
ICON_ONLY_PERMUTATIONS.map(renderIconOnlyPermutation),
'Icon-only anatomy'
)}
${row(
[
...ACTION_BUTTON_VALID_SIZES.map((size) =>
renderAvatarActionButton(size, false)
),
...ACTION_BUTTON_VALID_SIZES.map((size) =>
renderAvatarActionButton(size, true)
),
],
'Avatar icon'
)}
${row(
[
html`
Expand Down
2 changes: 2 additions & 0 deletions 2nd-gen/packages/swc/components/avatar/avatar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ A disabled avatar indicates that the entity is not currently active or available

An avatar can be placed inside an action button to create a user-triggered action tied to a specific person or entity.

When slotted into the `icon` slot, the avatar automatically scales to match the action button's own icon size; the avatar's own `size` attribute has no effect in that context.

<Canvas of={AvatarStories.InActionButton} />

## Accessibility
Expand Down
30 changes: 17 additions & 13 deletions 2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Avatar } from '@adobe/spectrum-wc/avatar';
import { AVATAR_VALID_SIZES } from '@adobe/spectrum-wc-core/components/avatar/index.js';

import '@adobe/spectrum-wc/components/avatar/swc-avatar.js';
import '@adobe/spectrum-wc/components/action-button/swc-action-button.js';

// ────────────────
// METADATA
Expand Down Expand Up @@ -231,26 +232,29 @@ export const Disabled: Story = {
// ──────────────────────────────

export const InActionButton: Story = {
// TODO: Replace <button> with <swc-action-button> once that component is migrated to 2nd-gen.
render: (args) => html`
<button
type="button"
style="display:inline-flex;align-items:center;gap:8px;padding:4px 12px;cursor:pointer;"
>
<swc-avatar
src=${args.src}
alt=${args.alt}
size=${args.size}
></swc-avatar>
<swc-action-button>
<swc-avatar slot="icon" src=${args.src} alt="" decorative></swc-avatar>
Jane Doe
</button>
</swc-action-button>
<swc-action-button accessible-label="Jane Doe">
<swc-avatar slot="icon" src=${args.src} alt="" decorative></swc-avatar>
</swc-action-button>
<swc-action-button size="xl">
<swc-avatar slot="icon" src=${args.src} alt="" decorative></swc-avatar>
Jane Doe
</swc-action-button>
<swc-action-button size="xl" accessible-label="Jane Doe">
<swc-avatar slot="icon" src=${args.src} alt="" decorative></swc-avatar>
</swc-action-button>
`,
args: {
src: PLACEHOLDER_SRC,
alt: 'Jane Doe',
size: '100',
},
tags: ['behaviors'],
parameters: {
flexLayout: 'row-wrap',
},
};

// ────────────────────────────────
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
.swc-ActionButton-icon {
color: inherit;
fill: currentcolor;
--swc-avatar-size: var(--_swc-action-button-icon-size);

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 this come from the generator or hand generated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

global-component styles are auto-generated!

}

.swc-ActionButton--sizeXs {
Expand Down
Loading