Automattic / Automattic/jetpack
Deprecate Button from @automattic/jetpack-components in favor of @wordpress/ui Button
- Dominant language
- PHP
- Stars
- 1.8k
- Forks
- 898
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 774
Description
# Deprecate `Button` from `@automattic/jetpack-components` in favor of `@wordpress/ui` Button
## Background
The `Button` component exported from `@automattic/jetpack-components` duplicates functionality now covered by the WordPress Design System's `Button` in `@wordpress/ui` (the new themeable UI package in Gutenberg, currently at `v0.10.0`). To reduce our custom-component surface and align with the WordPress Design System, we want to migrate all usages and eventually remove the jetpack-components version.
This issue captures an audit of current usage and proposes a phased migration plan.
> **Note:** An earlier draft of this issue mentioned `@wordpress/components`. The correct target is **`@wordpress/ui`**, which is a distinct package with a different API surface. The plan below has been rewritten accordingly.
## `@wordpress/ui` exports relevant to this migration
From `gutenberg/packages/ui/src/index.ts`, the relevant exports are:
- **`Button`** — the general-purpose button. No `icon` prop, no `href` prop.
- **`IconButton`** — `Omit & { label, icon, shortcut? }`. Used for icon-only buttons; provides built-in tooltip + a11y labeling.
- **`Link`** — separate component for anchor-style navigation.
## `@wordpress/ui` Button API (v0.10.0)
```ts
interface ButtonProps {
variant?: 'solid' | 'outline' | 'minimal' | 'unstyled'; // default: 'solid'
tone?: 'brand' | 'neutral'; // default: 'brand'
size?: 'default' | 'compact' | 'small'; // default: 'default'
disabled?: boolean;
focusableWhenDisabled?: boolean; // default: true
'aria-pressed'?: …; // for toggle buttons
loading?: boolean; // built-in loading support
loadingAnnouncement?: string; // a11y announcement
children?: ReactNode;
}
```
Built on top of `@base-ui/react/button`. The component auto-disables while `loading` is true and announces loading state via `speak()`.
## Audit Summary
- **Total sites using the jetpack-components Button:** ~90 imports across **~70 files** in **11 packages/plugins**
- **Main `projects/plugins/jetpack` client code:** **not affected** — it uses `ActionButton`, which is out of scope.
### Usage by package
| Package / Plugin | Button usages | CSS overrides |
|---|---|---|
| `projects/plugins/boost` | 18 | 2 |
| `projects/plugins/protect` | 14 | 0 |
| `projects/packages/videopress` | 13 | 1 |
| `projects/packages/publicize` | 13 | 3 |
| `projects/packages/my-jetpack` | 8 | 0 |
| `projects/js-packages/scan` | 6 | 0 |
| `projects/plugins/crm` | 2 | 0 |
| `projects/plugins/automattic-for-agencies-client` | 2 | 0 |
| `projects/js-packages/licensing` | 1 | 1 |
| `projects/js-packages/connection` | 1 | 0 |
| `projects/js-packages/components` | 1 | 0 |
## API gap analysis — `@automattic/jetpack-components` ➜ `@wordpress/ui`
This is a more substantial rewrite than a drop-in rename. The variant vocabulary, the icon story, and the link story are all different.
### Variant/tone mapping (provisional — needs design review)
| Jetpack prop | `@wordpress/ui` equivalent | Notes |
|---|---|---|
| `variant="primary"` | `variant="solid"` + `tone="brand"` (default) | Both are defaults — no attribute needed. |
| `variant="secondary"` | `variant="outline"` (likely) | Design confirmation required. |
| `variant="tertiary"` | `variant="minimal"` (likely) | Design confirmation required. |
| `variant="link"` | **`Link` component** | Not a Button variant — use the `Link` export. |
| `isDestructive` | ❌ no direct equivalent | `tone` only offers `brand`/`neutral`. Needs design guidance or a CSS utility. |
| `isLoading` | `loading` | **Better** than jetpack — built-in spinner + a11y announcement. `loadingAnnouncement` replaces the `text` prop. |
| `text` (loading description) | `loadingAnnouncement` | 1:1 replacement when used with `loading`. |
| `weight="bold"` / `weight="regular"` | ❌ no equivalent | Add a CSS utility or drop the distinction. |
| `size="normal"` | `size="default"` | Rename. |
| `size="small"` | `size="small"` | Unchanged. |
| `disabled` | `disabled` | Unchanged. |
| `className`, `children`, `onClick` | same | Unchanged. |
| `icon` / `iconSize` | **`IconButton` component** | Separate import. Requires a `label` prop (a11y). |
| `href` | **`Link` component** | Button doesn't accept `href`. Requires either switching to `Link` or using base-ui's `render` prop. |
| `isExternalLink` | ❌ no equivalent | `Link` may or may not support it — needs confirmation. |
| `fullWidth` | ❌ no equivalent | CSS-only. |
### Component choice flowchart for each call site
1. Used purely as a link (`href`, no `onClick`) → migrate to **`Link`**.
2. Icon-only / icon-with-tooltip → migrate to **`IconButton`**.
3. Button with text + icon next to it → either `{icon}{text}` manually, or evaluate `IconButton` if the icon *is* the meaning.
4. Button with `isDestructive` → flag for design, pending decision.
5. All other buttons → **`Button`** with mapped `variant`/`tone`/`size`.
## Migration plan
### Phase 0 — Prerequisites
Before any bulk migration, we should:
1. **Design sign-off on the variant/tone mapping** — particularly for `secondary`, `tertiary`, and `isDestructive`. Missing destructive styling is the biggest open question.
2. **Decide on `weight` handling** — either add a `.is-bold` utility class or drop the distinction in coordination with design.
3. **Confirm `@wordpress/ui` is acceptable as a runtime dependency** for every affected package (dependency graph, bundle size, build config).
4. **Document the migration guide** in a short README inside the jetpack-components package (or a Make post) covering the flowchart above.
### Phase 1 — True straight swaps
Files using only default props (no `icon`, no `href`, no `isDestructive`, no `variant`, no `weight`, no `isLoading`). These reduce to an import rename plus possibly `size="normal"` → `size="default"`.
Candidates from the audit (to be re-verified call-by-call during migration):
- `projects/plugins/automattic-for-agencies-client/components/connected-card/index.jsx`
- `projects/plugins/boost/.../features/ui/collapsible-meta/collapsible-meta.tsx`
- `projects/plugins/boost/.../features/speed-score/speed-score.tsx`
- `projects/plugins/boost/.../features/performance-history/performance-history.tsx`
- `projects/plugins/boost/.../features/critical-css/folding-element/folding-element.tsx`
- `projects/plugins/boost/.../pages/purchase-success/purchase-success.tsx`
- `projects/plugins/boost/.../pages/critical-css-advanced/critical-css-advanced.tsx`
- `projects/plugins/crm/.../automations-admin/.../edit-modal/index.tsx`
- `projects/packages/publicize/.../form/broken-connections-notice.tsx`
- `projects/packages/publicize/.../connection-management/index.tsx`
- `projects/packages/publicize/.../connection-management/disconnect.tsx`
- `projects/packages/publicize/.../connection-management/reconnect.tsx`
- `projects/packages/publicize/.../admin-page/header/index.js`
- `projects/packages/publicize/.../services/connect-form.tsx`
- `projects/packages/my-jetpack/.../golden-token/tooltip/index.tsx`
- `projects/packages/videopress/.../components/chapters-learn-more-helper/index.tsx`
- `projects/packages/videopress/.../components/incomplete-chapters-notice/index.tsx`
### Phase 2 — `href`-only call sites (migrate to `Link`)
Files where the Button is used as navigation (has `href`, no significant handler state). These need to be re-evaluated: should it render as a `Link` styled as a button, or just a `Link`?
- `projects/plugins/automattic-for-agencies-client/components/disconnect-site-link/index.jsx`
- `projects/plugins/boost/.../features/ui/back-button/back-button.tsx`
- `projects/plugins/boost/.../features/speed-score/pop-out/pop-out.tsx`
- `projects/plugins/boost/.../features/page-cache/switch-to-boost/switch-to-boost.tsx`
- `projects/plugins/boost/.../features/cornerstone-pages/meta/meta.tsx`
- `projects/plugins/boost/.../features/lcp/lcp.tsx`
- `projects/plugins/boost/.../features/minify-legacy-notice/minify-legacy-notice.tsx`
- `projects/plugins/boost/.../layout/settings-page/support/support.tsx`
- `projects/js-packages/connection/components/manage-connection-dialog/index.jsx`
- `projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx`
- `projects/plugins/protect/src/js/routes/firewall/firewall-upgrade-prompt.jsx`
- `projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx`
- `projects/packages/my-jetpack/.../product-interstitial-modal/product-interstitial-my-jetpack.tsx`
### Phase 3 — Icon buttons (migrate to `IconButton`)
Files whose Button primarily conveys meaning via an icon. Each needs a `label` for accessibility.
- `projects/packages/videopress/.../admin/components/pagination/index.tsx`
- `projects/packages/videopress/.../admin/components/video-quick-actions/index.tsx`
- `projects/packages/videopress/.../admin/components/video-filter/index.tsx`
- `projects/packages/videopress/.../admin/components/clipboard-button-input/index.tsx`
- `projects/plugins/crm/src/js/components/automations-admin/components/workflow-row/index.tsx`
- `projects/packages/publicize/.../connection-management/connection-info.tsx`
### Phase 4 — Files requiring per-site review (deferred pending Phase 0 decisions)
Flagged because they depend on props or behavior that is not yet resolved (`isDestructive`, `weight`, `isExternalLink`, `fullWidth`), have CSS overrides targeting the jetpack-components button, or wrap the button in additional logic.
#### Depends on missing/unresolved props
- `projects/js-packages/scan/src/components/threat-fixer-button/index.tsx` — `isLoading` (OK), `isDestructive`, `weight`, `size="small"`, icon, Tooltip wrapper
- `projects/plugins/boost/.../features/critical-css/status/status.tsx` — dynamic `variant`, `size="small"`, `weight="regular"`, icon, disabled
- Any call site using `isExternalLink` or `fullWidth` (to be enumerated during migration)
#### CSS overrides targeting the button
- `projects/plugins/boost/.../features/minify-meta/minify-meta.module.scss` — margin
- `projects/plugins/boost/.../features/page-cache/meta/meta.module.scss` — scoped spacing
- `projects/packages/publicize/.../admin-page/toggles/social-image-generator-toggle/styles.module.scss` — grid + margin
- `projects/packages/publicize/.../admin-page/toggles/social-notes-toggle/styles.module.scss` — spacing
- `projects/packages/videopress/.../admin/components/pagination/style.module.scss` — width + disabled state
- `projects/js-packages/licensing/components/golden-token-modal/styles.module.scss` — modal button layout
#### Wrappers / complex components
- `projects/packages/my-jetpack/_inc/components/action-button/index.tsx` — ~450 lines, dynamic variant logic across 15+ product statuses. Candidate for a dedicated follow-up PR.
- `projects/plugins/protect/src/js/components/button-group/index.jsx` — wraps Button as `ButtonGroup.Button` with default variant.
- `projects/js-packages/components/components/upsell-banner/index.tsx` — Button embedded in a reusable component consumed elsewhere; swap affects all consumers.
#### Composed usage to verify visually
Files using `ThemeProvider`, `useBreakpointMatch`, `Text`, `IconTooltip`, or `getRedirectUrl` alongside the button. Mechanically safe but need visual verification as a group:
- `projects/plugins/protect/src/js/components/` — `ignore-threat-modal`, `unignore-threat-modal`, `fix-threat-modal`, `fix-all-threats-modal`, `user-connection-needed-modal`, `scan-button`, `navigation/group`, `threats-list/pagination`, `threats-list/free-list`
- `projects/js-packages/scan/.../threat-modal/` — `threat-actions`, `threat-summary`, `threat-notice`, `threat-technical-details`
- `projects/packages/videopress/.../admin/components/` — `video-thumbnail-selector-modal`, `admin-page/libraries`, `delete-video-confirmation-modal`, `video-upload-area`
- `projects/packages/publicize/.../admin-page/toggles/social-image-generator-toggle`, `.../admin-page/toggles/social-notes-toggle`, `.../share-buttons/share-buttons`, `.../media-picker`, `.../services/service-item`
- `projects/packages/my-jetpack/.../action-button/secondary-button`, `.../plans-section`, `.../product-interstitial-modal/*` (2 files), `.../product-card/recommendation-actions`
- `projects/js-packages/licensing/components/golden-token-modal/index.jsx`
## Out of scope
- `ActionButton` from `@automattic/jetpack-components` — separate component, not part of this deprecation.
- Removal of the `Button` export from `@automattic/jetpack-components` — follow-up after all call sites are migrated.
## Acceptance criteria
- [ ] Phase 0 prerequisites resolved (design mapping, missing-prop decisions, dependency acceptance, migration guide)
- [ ] Phase 1 straight swaps merged (one PR per package is reasonable)
- [ ] Phase 2 `href`-only sites migrated to `Link`
- [ ] Phase 3 icon-only sites migrated to `IconButton`
- [ ] Phase 4 deferred files either migrated or documented if intentionally left behind
- [ ] `Button` removed from `@automattic/jetpack-components` exports
- [ ] Changelog entries added per affected package
---
*Audit performed against the current state of `trunk` on 2026-04-17. `@wordpress/ui` API reviewed against `gutenberg/packages/ui` at v0.10.0.*
Contributor guide
Assessment
This issue has not been assessed yet.