Repository: Automattic/wp-calypso
Pull request #101626: WP Button: Add reusable style for secondary A8C override
Author: login: mirka
Labels: name: Enhancement
description: Changes to an existing feature — removing, adding, or changing parts of it
name: Components
name: Storybook
Created: 2025-03-19T21:58:48Z | Merged: 2025-03-21T11:54:51Z

Addresses https://github.com/Automattic/wp-calypso/pull/100807#issuecomment-2735786149

## Proposed Changes

Add a way to override the `@wordpress/components` secondary Button variant style as frequently done in Calypso.

https://github.com/user-attachments/assets/ea4d9954-e5ee-4e44-843d-e18ebbf4c4dc

## Why are these changes being made?

While we may want to abstract this differently in the near future, having this override class available will unblock us quickly for now in a way that's relatively simple to refactor.

## Testing Instructions

`yarn components:storybook:start` and see the documentation for "WP Overrides". 

Styles for pseudostates (e.g. `:hover`) should be looking good for the busy, destructive, disabled, and pressed button states.

## Pre-merge Checklist

<!--
Complete applicable items on this checklist **before** merging into trunk. Inapplicable items can be left unchecked.

Both the PR author and reviewer are responsible for ensuring the checklist is completed.
-->

- [ ] Has the general commit checklist been followed? (PCYsg-hS-p2)
- [ ] [Have you written new tests](https://wpcalypso.wordpress.com/devdocs/docs/testing/index.md) for your changes?
- [ ] Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
- [ ] Have you checked for TypeScript, React or other console errors?
- [ ] Have you used memoizing on expensive computations? More info in [Memoizing with create-selector](https://github.com/Automattic/wp-calypso/blob/trunk/packages/state-utils/src/create-selector/README.md) and [Using memoizing selectors](https://react-redux.js.org/api/hooks#using-memoizing-selectors) and [Our Approach to Data](https://github.com/Automattic/wp-calypso/blob/trunk/docs/our-approach-to-data.md)
- [ ] Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    - [ ] For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages. 
- [ ] For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?



Diff:
diff --git a/packages/components/.storybook/main.js b/packages/components/.storybook/main.js
index dfda91f34e9a..cf4519e5c46e 100644
--- a/packages/components/.storybook/main.js
+++ b/packages/components/.storybook/main.js
@@ -1,6 +1,8 @@
 const storybookDefaultConfig = require( '@automattic/calypso-storybook' );
 
 module.exports = {
-	...storybookDefaultConfig(),
+	...storybookDefaultConfig( {
+		stories: [ '../{src,styles}/**/*.stories.{js,jsx,ts,tsx}' ],
+	} ),
 	docs: { autodocs: true },
 };
diff --git a/packages/components/package.json b/packages/components/package.json
index d146e30fce9b..24aa70c766ee 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -23,7 +23,8 @@
 	"bugs": "https://github.com/Automattic/wp-calypso/issues",
 	"files": [
 		"dist",
-		"src"
+		"src",
+		"styles"
 	],
 	"types": "dist/types",
 	"dependencies": {
diff --git a/packages/components/styles/_wp-components-theme-variables.scss b/packages/components/styles/_wp-components-theme-variables.scss
new file mode 100644
index 000000000000..512ff43af571
--- /dev/null
+++ b/packages/components/styles/_wp-components-theme-variables.scss
@@ -0,0 +1,9 @@
+/**
+ * Makes the `$components-color-*` variables from `@wordpress/components` available
+ * for local overrides.
+ *
+ * Remember to import this file with `@use` (not `@import`) so it doesn't pollute the global namespace.
+ */
+
+@import '@wordpress/base-styles/colors'; // TODO: The theme-variables.scss file itself should import this.
+@import '@wordpress/components/src/utils/theme-variables';
diff --git a/packages/components/styles/wp-button-override.scss b/packages/components/styles/wp-button-override.scss
new file mode 100644
index 000000000000..719d113fb087
--- /dev/null
+++ b/packages/components/styles/wp-button-override.scss
@@ -0,0 +1,24 @@
+@use './_wp-components-theme-variables' as theme;
+
+@import '@wordpress/base-styles/colors';
+@import '@wordpress/base-styles/variables';
+
+.a8c-components-wp-button {
+	&.is-secondary:not(:disabled, [aria-disabled="true"], [aria-pressed="true"]) {
+		box-shadow: inset 0 0 0 $border-width theme.$components-color-gray-300;
+
+		&:not(.is-destructive) {
+			color: theme.$components-color-foreground;
+		}
+			
+		&:hover:not(:disabled, [aria-disabled="true"], :focus) {
+			color: theme.$components-color-accent;
+			box-shadow: inset 0 0 0 $border-width theme.$components-color-gray-300;
+		}
+		
+		&:active,
+		&:active:hover {
+			background-color: theme.$components-color-background;
+		}
+	}
+} 
diff --git a/packages/components/styles/wp-button-override.stories.tsx b/packages/components/styles/wp-button-override.stories.tsx
new file mode 100644
index 000000000000..5e5f603f79a9
--- /dev/null
+++ b/packages/components/styles/wp-button-override.stories.tsx
@@ -0,0 +1,57 @@
+import { Button } from '@wordpress/components';
+import React from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+// Not using a relative path here to ensure that the build is working.
+import '@automattic/components/styles/wp-button-override.scss';
+
+// TODO: Investigate why displayName becomes `UnforwardedButton` when imported.
+Button.displayName = 'Button';
+
+/**
+ * This reference is for A8C-specific style overrides for the `Button` component from `@wordpress/components`.
+ *
+ * See the [WordPress Storybook](https://wordpress.github.io/gutenberg/?path=/docs/components-button--docs) docs for more information.
+ */
+const meta: Meta< typeof Button > = {
+	title: 'packages/components/WP Overrides/Button',
+	component: Button,
+	decorators: [
+		( Story ) => (
+			<div style={ { fontFamily: 'sans-serif' } }>
+				<Story />
+			</div>
+		),
+	],
+};
+
+export default meta;
+type Story = StoryObj< typeof Button >;
+
+export const Default: Story = {
+	args: {
+		children: 'Button',
+		__next40pxDefaultSize: true,
+		accessibleWhenDisabled: true,
+	},
+};
+
+/**
+ * The secondary button styles can be overridden to the A8C styles by adding the
+ * `.a8c-components-wp-button` class.
+ *
+ * To load these styles, import the `@automattic/components/styles/wp-button-override.scss` file
+ * somewhere in your project.
+ */
+export const Secondary: Story = {
+	args: {
+		...Default.args,
+		variant: 'secondary',
+		className: 'a8c-components-wp-button',
+	},
+	parameters: {
+		docs: {
+			canvas: { sourceState: 'shown' },
+		},
+	},
+};


Reviews:
user: login: mirka
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T21:51:33Z
user: login: mirka
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T22:05:11Z
user: login: adamwoodnz
body: Thanks for this!

I've rebased my [social buttons PR](https://github.com/Automattic/wp-calypso/pull/100807) on this branch again today and it's still working well. I've stripped back the styles on those buttons to the bare minimum; font size and layout rules for the internals. There are still overrides for some heavily branded buttons (eg. Woo, Gravatar), but otherwise it's mostly just theming using `--color-accent`.

Storybook works for me.

LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T01:30:42Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T15:29:47Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T15:32:16Z
user: login: jasmussen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-25T08:27:10Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T11:21:36Z
user: login: mirka
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T22:07:47Z
user: login: mirka
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T22:45:53Z

Review comments:
diff_hunk: @@ -0,0 +1,57 @@
+import { Button } from '@wordpress/components';
+import React from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+// Not using a relative path here to ensure that the build is working.
+import '@automattic/components/styles/wp-button-override.scss';
+
+// TODO: Investigate why displayName becomes `UnforwardedButton` when imported.
+Button.displayName = 'Button';
+
+/**
+ * This reference is for A8C-specific style overrides for the `Button` component from `@wordpress/components`.
+ *
+ * See the [WordPress Storybook](https://wordpress.github.io/gutenberg/?path=/docs/components-button--docs) docs for more information.
+ */
+const meta: Meta< typeof Button > = {
+	title: 'packages/components/WP Overrides/Button',
+	component: Button,
path: packages/components/styles/wp-button-override.stories.tsx
user: login: mirka
body: @ciampo Aside: Apparently `react-docgen-typescript` doesn't pull types from external dependencies, which WP Button is in this case. It could be that there are config tweaks we could do to make that happen, but I didn't go down that rabbit hole for now. Anyway, that's why the full props table is not showing in Storybook.
created_at: 2025-03-20T21:51:33Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,24 @@
+@use './_wp-components-theme-variables' as theme;
+
+@import '@wordpress/base-styles/colors';
+@import '@wordpress/base-styles/variables';
+
+.a8c-components-wp-button {
+	&.is-secondary:not(:disabled, [aria-disabled="true"], [aria-pressed="true"]) {
+		box-shadow: inset 0 0 0 $border-width theme.$components-color-gray-300;
path: packages/components/styles/wp-button-override.scss
user: login: mirka
body: @ciampo Are the `$components-color-` variables too much? They're not strictly necessary but I thought some consistency here could be helpful since we're seeing some ad hoc CSS variable-based theming starting to happen.
created_at: 2025-03-20T22:05:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+import { Button } from '@wordpress/components';
+import React from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+// Not using a relative path here to ensure that the build is working.
+import '@automattic/components/styles/wp-button-override.scss';
+
+// TODO: Investigate why displayName becomes `UnforwardedButton` when imported.
+Button.displayName = 'Button';
+
+/**
+ * This reference is for A8C-specific style overrides for the `Button` component from `@wordpress/components`.
+ *
+ * See the [WordPress Storybook](https://wordpress.github.io/gutenberg/?path=/docs/components-button--docs) docs for more information.
+ */
+const meta: Meta< typeof Button > = {
+	title: 'packages/components/WP Overrides/Button',
+	component: Button,
path: packages/components/styles/wp-button-override.stories.tsx
user: login: ciampo
body: Mhh, definitely not ideal — maybe we can create, only for the purpose of Storybook, a `Button` component that more explicitly defines prop types (something like `props: React.ComponentProps< typeof Button >`, or any other technique) ? 
created_at: 2025-03-24T15:29:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,24 @@
+@use './_wp-components-theme-variables' as theme;
+
+@import '@wordpress/base-styles/colors';
+@import '@wordpress/base-styles/variables';
+
+.a8c-components-wp-button {
+	&.is-secondary:not(:disabled, [aria-disabled="true"], [aria-pressed="true"]) {
+		box-shadow: inset 0 0 0 $border-width theme.$components-color-gray-300;
path: packages/components/styles/wp-button-override.scss
user: login: ciampo
body: I don't like the idea because it can quickly spread across the codebase and become really hard to migrate away from.

I honestly think more and more that we should spend some time to create the theming engine that we've been long discussed about. It could live in `@automattic/theme-provider` and be general enough that, if/when needed, it can be ported to Gutenberg with virtually zero changes.
created_at: 2025-03-24T15:32:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,24 @@
+@use './_wp-components-theme-variables' as theme;
+
+@import '@wordpress/base-styles/colors';
+@import '@wordpress/base-styles/variables';
+
+.a8c-components-wp-button {
+	&.is-secondary:not(:disabled, [aria-disabled="true"], [aria-pressed="true"]) {
+		box-shadow: inset 0 0 0 $border-width theme.$components-color-gray-300;
path: packages/components/styles/wp-button-override.scss
user: login: jasmussen
body: CC: @jameskoster @youknowriad. Would love to see if parts of [this](https://github.com/WordPress/gutenberg/pull/56669) can be reused. Also connecting some dots to this conversation: pgle0O-7K-p2#comment-193
created_at: 2025-03-25T08:27:10Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,24 @@
+@use './_wp-components-theme-variables' as theme;
+
+@import '@wordpress/base-styles/colors';
+@import '@wordpress/base-styles/variables';
+
+.a8c-components-wp-button {
+	&.is-secondary:not(:disabled, [aria-disabled="true"], [aria-pressed="true"]) {
+		box-shadow: inset 0 0 0 $border-width theme.$components-color-gray-300;
path: packages/components/styles/wp-button-override.scss
user: login: ciampo
body: We can definitely reuse parts of Saxon's exploratory work — from my point of view, it's mostly a matter of prioritisation and resource assignment.
created_at: 2025-03-25T11:21:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+import { Button } from '@wordpress/components';
+import React from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+// Not using a relative path here to ensure that the build is working.
+import '@automattic/components/styles/wp-button-override.scss';
+
+// TODO: Investigate why displayName becomes `UnforwardedButton` when imported.
+Button.displayName = 'Button';
+
+/**
+ * This reference is for A8C-specific style overrides for the `Button` component from `@wordpress/components`.
+ *
+ * See the [WordPress Storybook](https://wordpress.github.io/gutenberg/?path=/docs/components-button--docs) docs for more information.
+ */
+const meta: Meta< typeof Button > = {
+	title: 'packages/components/WP Overrides/Button',
+	component: Button,
path: packages/components/styles/wp-button-override.stories.tsx
user: login: mirka
body: That kind of thing didn't work either 🫤 I also tried some of [these tricks](https://github.com/storybookjs/storybook/issues/13708) on Storybook 8, but didn't work.
created_at: 2025-03-26T22:07:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+import { Button } from '@wordpress/components';
+import React from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+// Not using a relative path here to ensure that the build is working.
+import '@automattic/components/styles/wp-button-override.scss';
+
+// TODO: Investigate why displayName becomes `UnforwardedButton` when imported.
+Button.displayName = 'Button';
+
+/**
+ * This reference is for A8C-specific style overrides for the `Button` component from `@wordpress/components`.
+ *
+ * See the [WordPress Storybook](https://wordpress.github.io/gutenberg/?path=/docs/components-button--docs) docs for more information.
+ */
+const meta: Meta< typeof Button > = {
+	title: 'packages/components/WP Overrides/Button',
+	component: Button,
path: packages/components/styles/wp-button-override.stories.tsx
user: login: mirka
body: Ok, it's annoying but I figured out some workarounds. Will update.
created_at: 2025-03-26T22:45:53Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-19T21:59:01Z
body: <details>
<summary>Calypso Live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-137894">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-137894%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-137894">https://calypso.live?image=registry.a8c.com/calypso/app:build-137894</a>
</td>
</tr>
</table>
</details>
<details>
<summary>Jetpack Cloud live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-137894&env=jetpack">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-137894%26env%3Djetpack%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-137894&env=jetpack">https://calypso.live?image=registry.a8c.com/calypso/app:build-137894&env=jetpack</a>
</td>
</tr>
</table>
</details>
<details>
<summary>Automattic for Agencies live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-137894&env=a8c-for-agencies">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-137894%26env%3Da8c-for-agencies%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-137894&env=a8c-for-agencies">https://calypso.live?image=registry.a8c.com/calypso/app:build-137894&env=a8c-for-agencies</a>
</td>
</tr>
</table>
</details>
<!--calypso-live-watermark:apr@v1-->
author_association: NONE
user: login: matticbot
created_at: 2025-03-19T22:58:38Z
body: **This PR modifies the release build for the following Calypso Apps:**

_For info about this notification, see here: PCYsg-OT6-p2_

* blaze-dashboard
* command-palette-wp-admin
* help-center
* notifications
* odyssey-stats
* whats-new
* wpcom-block-editor

To test WordPress.com changes, run `install-plugin.sh $pluginSlug button-secondary-override` on your sandbox.
<!--calypso-app-artifacts-watermark:apr@v1-->
author_association: CONTRIBUTOR
user: login: matticbot
created_at: 2025-03-19T23:01:27Z
body: <!-- icfy-watermark: c52822 -->
This PR does not affect the size of JS and CSS bundles shipped to the user's browser.

Generated by performance advisor bot at [iscalypsofastyet.com](http://iscalypsofastyet.com).
author_association: CONTRIBUTOR
user: login: ciampo
created_at: 2025-03-20T05:44:53Z
body: Better than having many variations of these overrides scattered across the codebase 😓  Do you think we should try to consolidate all those overrides to use this new classname?
author_association: CONTRIBUTOR
user: login: mirka
created_at: 2025-03-20T21:44:17Z
body: > Do you think we should try to consolidate all those overrides to use this new classname?

@ciampo From a quick skim, I estimate the number of `@wordpress/components` secondary Button overrides in the codebase to be less than 20. So it is doable.

However I think the bigger problem is that the standard [`@automattic/components` Button](https://wpcalypso.wordpress.com/devdocs/design/button) style is basically what this override is trying to emulate. So my estimate here is that we have like 500–800 counts of these now deprecated A8C Buttons in the codebase. These cannot be mass migrated to this flimsy CSS class on top of WP Button 😂 We need a solid commitment to whatever design system we're moving to, and we need a more robust component setup to support it. @Automattic/dotcom-design Let's figure out a plan on Linear.
author_association: MEMBER
user: login: jasmussen
created_at: 2025-03-21T08:23:07Z
body: > We need a solid commitment to whatever design system we're moving to, and we need a more robust component setup to support it. Let's figure out a plan on Linear.

Will you ping us there? I'm doing some Linear work today to hopefully set up some ping-groups, probably a design-system related one. But otherwise, just let me know if there was a specific action item, such as feedback on this particular PR. Visualyl this is solid, I'll defer to engineering on the technical side of things. But as-is, the existing Calypso/a8c button component does not seem superior to the core Button, as far as keeping around.

I don't think a manual search/replace of the entire codebase, with the sole purpose of replacing one component with another, is an effort we should undertake. I think the legacy instances can exist until that section gets organically revisited for some other purpose. This is also to say, between completing untangling work, and work on an improved hosting dashboard, there will both be many such opportunities, but there will also be cases where the entire context disappears, gets retired, or deleted.
author_association: MEMBER
user: login: mirka
created_at: 2025-03-21T11:55:48Z
body: @Automattic/team-calypso I'm going to merge now so we don't keep @adamwoodnz in rebase hell, but please leave any comments so I can address them as follow-up 🙏
author_association: MEMBER