Skip to content

Conversation

castastrophe
Copy link
Collaborator

@castastrophe castastrophe commented Jul 29, 2025

Description

This PR:

  • Adds a new @spectrum-css/actionmenu component that composes ActionButton, Popover, and Menu to present action lists from a trigger.
  • Updates @spectrum-css/actionbutton and @spectrum-css/actiongroup to treat selection via .is-selected as well as :where([aria-pressed="true"], [aria-expanded="true"]) to cover more accessibility use-cases while keeping selector specificity low.
  • Refines @spectrum-css/menu to align with Spectrum 2 specifications and accessibility improvements.

Design references:

  • Figma S2 token specs: link

Includes a changeset with the following bumps:

  • @spectrum-css/actionmenu: major (new component)
  • @spectrum-css/actionbutton: minor (selection semantics with ARIA via :where)
  • @spectrum-css/menu: patch (S2 refinements, accessibility)
  • @spectrum-css/actiongroup: patch (selection semantics alignment)

How and where has this been tested?

  • Storybook locally:
    • Verified Action menu stories (default, long-press, placements).
    • Verified Menu stories for focus indicators, CJK line-height, external link/drill-in icons, thumbnails, and forced-colors behavior.
    • Verified Action button and Action group selected visuals using .is-selected, [aria-pressed="true"], and [aria-expanded="true"].
  • Forced Colors (Windows High Contrast) visual pass in Storybook.
  • Chromatic/VRT pending reviewer confirmation.

Validation steps

  1. Open Storybook for Action menu:
    • Toggle isOpen and confirm popover/menu spacing and placement reflect updates.
    • Enable “Long press” and confirm press vs long-press behavior in docs.
  2. Open Storybook for Action button:
    • Toggle .is-selected, set aria-pressed="true", and aria-expanded="true"; confirm identical visuals due to :where(...).
  3. Open Storybook for Action group:
    • In compact group, confirm hover/selected/focus ring layering and selection visuals via .is-selected and ARIA attributes.
  4. Open Storybook for Menu:
    • Confirm external-link and drill‑in icon sizing; thumbnail sizing/alignment.
    • Confirm forced-colors readability.

Screenshots

  • Add Storybook screenshots for:
    • Action menu default and long-press
    • Action button selected (class vs ARIA)
    • Menu focus and forced-colors states

To-do list

  • I have read the contribution guidelines.
  • I have updated relevant Storybook stories and templates.
  • I have tested these changes in Windows High Contrast mode.
  • If my change impacts other components, I have tested to make sure they don't break.
  • If my change impacts documentation, I have updated the documentation accordingly.
  • I have included a well-written changeset if my change needs to be published.

Notes for reviewers

  • No class name changes; selection semantics expanded via :where([aria-pressed],[aria-expanded]) to avoid specificity issues and broaden accessibility support.
  • Menu refinements include focus margin reservation, CJK line-height variables, transparent-at-rest backgrounds per S2, and forced-colors improvements.

References

  • Changesets documentation: https://github.com/changesets/changesets
  • Figma S2 token specs: https://www.figma.com/design/eoZHKJH9a3LJkHYCGt60Vb/S2-Token-specs?node-id=19758-3424

@castastrophe castastrophe changed the title feat(action-menu): S2 migration feat(action-menu): S2 migration [CSS-1160] Jul 29, 2025
Copy link
Contributor

github-actions bot commented Jul 29, 2025

📚 Branch preview

PR #4085 has been deployed to Azure Blob Storage: https://spectrumcss.z13.web.core.windows.net/pr-4085/index.html.

Copy link
Contributor

github-actions bot commented Jul 29, 2025

File metrics

Summary

Total size: 1.43 MB*
Total change (Δ): 🔴 ⬆ 0.83 KB (0.06%)

Table reports on changes to a package's main file. Other changes can be found in the collapsed Details section below.

Package Size Minified Gzipped Δ
actionbutton 23.60 KB 22.52 KB 2.99 KB 🟢 ⬇ 0.06 KB
actiongroup 4.85 KB 4.60 KB 0.95 KB 🔴 ⬆ 0.06 KB
actionmenu 0.84 KB 🆕 0.83 KB 🆕 0.48 KB 🆕 0.84 KB
menu 47.47 KB 45.21 KB 5.00 KB 🟢 ⬇ 0.45 KB
popover 16.55 KB 15.95 KB 2.03 KB 🔴 ⬆ 0.13 KB

File change details

actionbutton

Filename Head Minified Gzipped Compared to base
index.css 23.60 KB 22.52 KB 2.99 KB 🟢 ⬇ 0.06 KB
metadata.json 10.41 KB - - 🔴 ⬆ 0.22 KB

actiongroup

Filename Head Minified Gzipped Compared to base
index.css 4.85 KB 4.60 KB 0.95 KB 🔴 ⬆ 0.06 KB
metadata.json 2.45 KB - - 🔴 ⬆ 0.06 KB

actionmenu

Filename Head Minified Gzipped Compared to base
index.css 0.84 KB 🆕 0.83 KB 🆕 0.48 KB 🆕 0.84 KB
metadata.json 0.31 KB - - 🆕 0.31 KB

menu

Filename Head Minified Gzipped Compared to base
index.css 47.47 KB 45.21 KB 5.00 KB 🟢 ⬇ 0.45 KB
metadata.json 23.43 KB - - 🟢 ⬇ 0.52 KB

popover

Filename Head Minified Gzipped Compared to base
index.css 16.55 KB 15.95 KB 2.03 KB 🔴 ⬆ 0.13 KB
metadata.json 7.30 KB - - -
* Size is the sum of all main files for packages in the library.
* An ASCII character in UTF-8 is 8 bits or 1 byte.

@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch from 2729f84 to 779e411 Compare July 30, 2025 20:23
Copy link

changeset-bot bot commented Jul 30, 2025

🦋 Changeset detected

Latest commit: d1c459a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@spectrum-css/actionmenu Major
@spectrum-css/actionbutton Minor
@spectrum-css/menu Patch
@spectrum-css/actiongroup Patch
@spectrum-css/bundle Patch
@spectrum-css/preview Patch

Not sure what this means? Click here to learn what changesets are.

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

@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch 3 times, most recently from 635f709 to 242929c Compare August 1, 2025 20:06
@castastrophe castastrophe marked this pull request as ready for review August 1, 2025 20:06
@castastrophe castastrophe self-assigned this Aug 1, 2025
@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch 2 times, most recently from 4baa099 to eb8cde2 Compare August 4, 2025 15:23
@castastrophe castastrophe added size-2 S ~6-18hrs; not hard or time consuming, one or two work days to complete. wip This is a work in progress, don't judge. run_vrt For use on PRs looking to kick off VRT S2 Spectrum 2 labels Aug 4, 2025
@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch 4 times, most recently from 313ee43 to 3babcfa Compare August 6, 2025 23:43
@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch 2 times, most recently from 4d618da to f3fa4d4 Compare August 7, 2025 16:14
@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch 3 times, most recently from f2ef53c to 409436c Compare August 18, 2025 16:08
@adobe adobe deleted a comment from github-actions bot Aug 18, 2025
@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch 2 times, most recently from dbd613f to 00038a9 Compare August 18, 2025 17:12
@castastrophe
Copy link
Collaborator Author

Not sure if you consider this in-scope but you called it out for testing - maybe for the compact action group the focus radius should be reduced?

@5t3ph I opened an issue for this and will get the question asked in the design channel! #4246

Copy link
Collaborator

@rise-erpelding rise-erpelding left a comment

Choose a reason for hiding this comment

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

I left more details on some of the popover issues I've been noticing, I'd love to hear your thoughts! I'm cool with these being a follow-up ticket if they can't be solved here.

Otherwise, just some minor adjustments needed, I think!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Try this and see if you can replicate it, this is what I'm finding that breaks it (regardless of browser):

  • Open the Action menu component in Storybook (Docs page or Default story, it doesn't matter, as long as the popover is open, this won't happen if the action menu popover stays closed)
  • Open either the Coach mark component or the Popover component in Storybook (Docs page or Default story)
  • Expect to see either the height or width of the popover there expand infinitely until you refresh

I think there's something about Action menu, because I can go between Coach mark and Popover just fine.

Another thing I noticed is that if I start on the Popover with position bottom, then go to Action menu where bottom isn't a valid option (there's only bottom-start and bottom-end), then go back to Popover, the popover ends up in a completely different position. Unsure if that's related or not, but something I noticed.
image

Unless you've got an idea on how to address it, I'm cool with a follow-up ticket.

@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch 3 times, most recently from 41b6453 to 03a7ae4 Compare October 2, 2025 18:15
Copy link
Collaborator

@rise-erpelding rise-erpelding left a comment

Choose a reason for hiding this comment

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

I’m still seeing some issues with Action Menu/Popover. I’ve documented most inline and tested across multiple browsers, so hopefully they’re reproducible on your end.

The CSS looks solid overall, though! I wonder if simplifying some of the JS implementation could help stabilize things so we can ship this.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am still seeing the issue when navigating from an open action menu to something like popover or coachmark without refreshing, both locally and in the PR preview 😕

@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch 3 times, most recently from 3f6dad6 to 94e9252 Compare October 13, 2025 18:08
@rise-erpelding rise-erpelding dismissed their stale review October 13, 2025 21:20

Dismissing stale review after latest changes

Copy link
Collaborator

@rise-erpelding rise-erpelding left a comment

Choose a reason for hiding this comment

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

I looked at this quickly and noted that the popover issues from before are resolved! 🎉 Really, really nice work!

I did check out Coachmark after I checked out Action menu and Popover, and I'm seeing a similar issue there, but only after switching on "has action menu":
image

It's also visible from the docs page.

@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch from 94e9252 to d8fc6e3 Compare October 15, 2025 14:20
@castastrophe castastrophe added run_vrt For use on PRs looking to kick off VRT and removed skip_vrt Add to a PR to skip running VRT (but still pass the action) labels Oct 15, 2025
opacity: 1;
transition-delay: var(--mod-overlay-animation-duration-opened, var(--spectrum-animation-duration-0, 0ms));

@starting-style {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@5t3ph Would you mind validating that I set this up correctly?

Copy link
Contributor

Choose a reason for hiding this comment

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

It is working but partly because the processing is pulling this out of being nested. For future security (in case the preprocessor is changed / browserlist support changes) we should not nest this style. The reason has to do with weirdness with specificity. In case you didn't have it at hand, here's my fave article explaining this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Pushing up that suggested change now, great point about future-proofing

@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch 5 times, most recently from b6ed5e8 to 14ad6aa Compare October 16, 2025 17:30
@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch from 14ad6aa to faeccb5 Compare October 16, 2025 17:39
@castastrophe castastrophe force-pushed the castastrophe/feat-action-menu-migration branch from faeccb5 to d1c459a Compare October 16, 2025 17:41
@castastrophe castastrophe enabled auto-merge (squash) October 16, 2025 17:48
};

/**
* Action menus can be positioned in four locals relative to the trigger but <em>only one menu can be triggered at a single time</em>.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I definitely read this as four "low-kuls" first, and figured maybe it was supposed to be "low-cals." Is that a typo for "locales?" Does that word have multiple spellings that I need to remember? 😆

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Haha yes, it's a typo for locales! 😊

Copy link
Member

@cdransf cdransf left a comment

Choose a reason for hiding this comment

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

This looks awesome! Just have a few minor questions. ✨

@@ -0,0 +1,32 @@
/*!
* Copyright 2024 Adobe. All rights reserved.
Copy link
Member

Choose a reason for hiding this comment

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

Should we update this to 2025? ✨

isOpen: {
...isOpen,
name: "Pop-up is open",
description: "When the button triggers a pop-up, this should be true when the pop-up is open.",
Copy link
Member

Choose a reason for hiding this comment

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

Should this be "...this should be true while the pop-up is open." ✨

Copy link
Member

Choose a reason for hiding this comment

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

I'm seeing this as well — do we need to increase the height for that Coachmark example? ✨

else role = "menuitemradio";
}

// hasCheckbox = selectionMode == "multiple" && !hasActions
Copy link
Member

Choose a reason for hiding this comment

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

Should we remove this altogether? ✨

Copy link
Collaborator

@marissahuysentruyt marissahuysentruyt left a comment

Choose a reason for hiding this comment

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

I wanted to leave the comments I had so far! I need to go through the menu and popover changes a little more thoroughly tomorrow!

},
menuArgs: {
customStyles: {
"--mod-menu-inline-size": "max-content",
Copy link
Collaborator

Choose a reason for hiding this comment

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

You may have already left a comment somewhere about this, but instead of using the mod here, is it possible to use a --spectrum custom property? If we're going to remove mods, would it make sense to not introduce another place where one is being used?

There's a couple more menu mods in the PlaceholderIcons args as well.


- Adds wrapper classes: `spectrum-ActionMenu`, `spectrum-ActionMenu-trigger`, `spectrum-ActionMenu-popover`, and `spectrum-ActionMenu-menu`.
- Supports long press triggers and four placements (start/end, top/bottom) via the underlying popover API.
- Design reference: [Figma S2 token specs](https://www.figma.com/design/eoZHKJH9a3LJkHYCGt60Vb/S2-Token-specs?node-id=19758-3424).
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is actually different than the link that's in the action menu stories! 🤔


Updates `@spectrum-css/menu` styles to align with latest Spectrum 2 design specifications and improve accessibility.

- Added this not to prevent clash with the `.is-selectable` placement.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did we want to elaborate more on what was added? Does the "this" refer to that extra Wait! I got it- there's a not selector I saw you added. At the very least, can you add backticks around not just to set it off from the rest of the sentence? I completely read it wrong, and I'd love to see this item look more like how you wrote the action group changes (specifically that one with where)


&:disabled,
&.is-disabled {
/* ideal when we want to disable the button but still allow it's content to be focused */
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not a blocker, but it should be "its" instead of "it's" with an apostrophe.

name: "Has popup",
description: "If the button triggers a popup action, this should be set to reflect the type of element that pops-up.",
name: "Has pop-up",
description: "If the button triggers a pop-up action, this should be set to reflect the type of element that pops-up.",
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would argue that pops-up shouldn't be hyphenated. Not a blocker though, and not something you introduced either.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I double checked with Jess and she confirmed it is "pop-up" when talking about the component but "pop up" when describing the action. So it's probably hyphenated in the name and maybe not supposed to be hyphenated in the description ("a pop up action" vs. a "pop-up component").

[`${rootClass}--static${capitalize(staticColor)}`]:
typeof staticColor !== "undefined",
["is-disabled"]: isDisabled,
["is-hover"]: isHovered,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are we missing the ["is-selected"]: isSelected, class here? I noticed it's not in this list anymore, but does it need to be any longer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'd removed it in favor of attaching the styles to the aria-pressed state in the CSS but kept the styles for the class as a polyfill for those not using the correct aria yet.

["is-active"]: isActive,
...customClasses.reduce((a, c) => ({ ...a, [c]: true }), {}),
})}
[rootClass]: true,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not a blocker, but we could clean up the identation of the classMap if we wanted. That might be why there's such a big diff here to begin with... 🤔

type: { summary: "string" },
category: "Content",
type: { summary: "boolean" },
category: "Accessibility",
Copy link
Collaborator

Choose a reason for hiding this comment

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

oooooh I love the idea of having an accessibility section even in storybook! I have to keep it in mind as we get second-gen SWC up and going and iterating.

...(menuArgs?.customClasses ?? []),
],
customStyles: {
"--mod-menu-inline-size": "100%",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here- do we want to introduce another instance of a mod if we plan to start removing them?

...popoverArgs
} = {}, context = {}) => {
return Popover({
size,
Copy link
Collaborator

Choose a reason for hiding this comment

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

🤔 I wonder why we had a sized popover! thanks for catching and removing it!

It looks like maybe we were trying to pass size to the action button and menu. but as long as we include a size in the menuArgs and triggerArgs, consumers can still control the size, correct? I'll be honest that I haven't tried this, but that's how I'm understanding it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

design-approved ready-for-review run_vrt For use on PRs looking to kick off VRT S2 Spectrum 2 size-3 M ~18-30hrs; moderate effort or complexity, several work days needed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants