Add S2 button whitelist with authorable outline variant - #675
Add S2 button whitelist with authorable outline variant#675echen-adobe wants to merge 18 commits into
Conversation
The dark .con-button.fill/.button.fill focus ring used a #292929 inner ring that is invisible on a dark section. Use the SoT focus indicator (--color-focus-ring-strong #4B75FF) as the inner ring hugging the white button, with a white outer ring against the dark background, mirroring the light-mode rule and Express's existing .button.dark:focus convention. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the black-fill button's double box-shadow focus ring with the QA-approved S2 SoT 'Focus Ring Background' treatment already shipped in transparent-img-marquee: a 2px --color-focus-ring-strong outline offset 2px, with an 11%-white halo. Same ring in light and dark mode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Instead of overriding the global (legacy-matched) con-button rules and risking site-wide regressions, introduce an opt-in 's2' namespace. A block/section marked 's2' renders the design-reviewed Spectrum-2 CTAs: accent blue #3b63fb, black fill (#292929), the QA-approved outline ladder and focus ring, and dark-section flips — covering both .con-button.{blue,outline,fill} and the legacy .button.{accent,fill} the fallback emits (so ax-columns benefits). Everything is scoped under .s2, so existing buttons are untouched. The black fill/focus previously added globally now live under this namespace.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add S2_BUTTON_BLOCKS in decorateAreaWithLCP (runs on every page/fragment) to apply the s2 class to whitelisted blocks centrally, so the Spectrum-2 button styling rolls out block-by-block without per-page authoring. Starts with ax-columns; add blocks as they are verified, then retire the gate for a global default. Manual s2 opt-in still works. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rquee, comparison-table-v2, blog-columns - Make the S2 outline (secondary) button link-authorable via a #_button-outline hash: generalize the decorateButtonsDeprecated fallback to handle any #_button-<class> (fill/outline), matching milo, and extend the .s2 outline rules to cover legacy .button.outline via :is(.con-button, .button). - Add transparent-img-marquee, comparison-table-v2, blog-columns to S2_BUTTON_BLOCKS and remove their now-redundant local button rules (the .s2 outline/focus were ported verbatim from transparent-img-marquee; the two fill blocks' local .con-button.fill now come from .s2). Blue shifts from legacy #5c5ce0 to SoT #3b63fb on these blocks — intended S2 alignment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
|
|
@echen-adobe Has this gone through design review yet? Because of the nature of the request, I think they should sign off on it. |
meganthecoder
left a comment
There was a problem hiding this comment.
A few initial comments, and then as I posted earlier, I think this should be reviewed by design first.
| // Milo's core loadArea() never calls config.decorateArea for the main | ||
| // document — only its fragment block does, for fragment content (see | ||
| // libs/blocks/fragment/fragment.js). Blocks authored directly on the page | ||
| // (the common case) are classed explicitly after loadArea() in loadPage(). | ||
| applyS2ButtonClasses(area); |
There was a problem hiding this comment.
Yes, because the button decoration function is called in the block. It'd be good to check with Ernest that we want to deviate from milo in this way. It sounds like once we retire the gate, buttons would get decorated in the body too.
| // Custom button variants authored the milo way via `#_button-<class>` | ||
| // hashes on the href (e.g. `#_button-fill`, `#_button-outline`). The | ||
| // blocks that reach this fallback (ax-columns, banner, | ||
| // fullscreen-marquee, link-list) bypass milo's decorateButtons, which | ||
| // would normally strip these hashes and add the classes — so replicate | ||
| // that here: add each class, strip the hash, and drop the default | ||
| // `accent` when an explicit fill/outline variant is requested. | ||
| // Additive and hash-gated: no effect on content without these hashes. | ||
| if ($a.classList.contains('button')) { | ||
| const customClasses = [...originalHref.matchAll(/#_button-([a-zA-Z-]+)/g)]; | ||
| customClasses.forEach(([token, cls]) => { | ||
| $a.classList.add(cls); | ||
| $a.setAttribute('href', $a.href.replace(token, '')); | ||
| }); | ||
| if ($a.classList.contains('fill') || $a.classList.contains('outline')) { | ||
| $a.classList.remove('accent'); | ||
| } | ||
| } |
There was a problem hiding this comment.
Ideally we would upgrade to the new button decoration rather than continue to add code to the deprecated function. Do you have an idea of how much work that would be?
There was a problem hiding this comment.
Why ax-columns stays on decorateButtonsDeprecated (whitelist), not milo's native decorateButtons
We prototyped switching ax-columns to call milo's real decorateButtons directly, matching the pattern already used by blog-columns, comparison-table-v2, transparent-img-marquee, etc. It doesn't hold up for this block:
- Milo's decorateButtons only converts links matching em a, strong a, or p > a strong (verified against the live decorate.js source) — i.e., a link needs bold/italic formatting to be recognized as a CTA at all.
- ax-columns content relies on the older Express convention where any link alone in a paragraph becomes a button, formatted or not. That behavior only exists in the deprecated fallback (decorateButtonsDeprecated's own single-child-of-p check) — milo has no equivalent.
- Switching would silently drop button styling from every plain-link CTA authored in existing ax-columns content, including the #_button-fill/#_button-outline hash convention on unformatted links, since milo never even reaches them to strip the hash. This wouldn't be a CSS specificity fix — the elements never get a class in the first place.
- Working around it requires re-implementing a plain-link catch-up in ax-columns.js (matching what blog-columns.js already does), which only shrinks — doesn't eliminate — the custom classification code the migration was meant to remove.
Given ax-columns is one of our highest-traffic, highest-regression-risk blocks, the whitelist/deprecated-fallback approach remains the safer choice until/unless we're ready to also handle the plain-link gap deliberately, rather than fold it into a "quick" migration.
https://main--da-express-milo--adobecom.aem.page/drafts/echen/plain-link-decoration-demo
|
|
||
| let fragmentLcpPreloaded = false; | ||
|
|
||
| // Rollout gate for the Spectrum-2 (s2) button system. Blocks listed here get the |
There was a problem hiding this comment.
Can you confirm with design that S2 is the right name for our design system? We're not fully on S2 so they might have a name more specific to express.

Summary
Introduces Spectrum 2 (S2) button styling under an opt-in
.s2namespace, gated by a central block whitelist inutils.js/scripts.js. Adds an authorable outline variant scoped to S2. Adopts the design-reviewed focus ring for black-fill buttons (light and dark sections). Cleans up scoped button overrides intransparent-img-marquee,comparison-table-v2, andblog-columnsnow that styles are managed centrally.Jira Ticket
Resolves: MWPW-202637
Test URLs
Verification Steps
.s2.#_button-outlinehref hash.Potential Regressions
| After | https://milo-buttons--da-express-milo--adobecom.aem.page/docs/library/kitchen-sink/ax-columns?martech=off |
| After | https://milo-buttons--da-express-milo--adobecom.aem.page/docs/library/kitchen-sink/comparison-table-v2 |
| After | https://milo-buttons--da-express-milo--adobecom.aem.page/docs/library/kitchen-sink/transparent-img-marquee |
| After | https://milo-buttons--da-express-milo--adobecom.aem.page/docs/library/kitchen-sink/blog-columns
Additional Notes
CSS lint flagged ~20 translucent color values in the S2 outline button rules (e.g.
#00000038,#ffffff38,#000000ed). These are intentional alpha-channel design tokens for outline button states (border, hover, active, focus) and cannot be replaced with solidvar(--color-black)/var(--color-white)tokens without breaking the visual. Proper S2 translucent token definitions are pending upstream.min-height: 40pxon.con-buttonintransparent-img-marquee.cssis likewise intentional (40px pill height per design spec) and cannot usevar(--spacing-*)per project policy.