Skip to content

refactor: Improved ButtonBase to support all shapes and sizes#1150

Merged
brianacnguyen merged 15 commits into
mainfrom
update/buttonbase
May 11, 2026
Merged

refactor: Improved ButtonBase to support all shapes and sizes#1150
brianacnguyen merged 15 commits into
mainfrom
update/buttonbase

Conversation

@brianacnguyen

@brianacnguyen brianacnguyen commented May 4, 2026

Copy link
Copy Markdown
Contributor

Description

This change improves ButtonBase on React and React Native so layout, corners, and typography match design specs more closely, and documentation/stories/tests reflect how the component should be used (especially on-device Storybook).

Figma link: https://www.figma.com/design/1D6tnzXqWgnUC3spaAOELN/branch/8KPwHulIorYFJiTWK8MapC/%F0%9F%A6%8A-MMDS-Components?node-id=0-1&p=f&t=Koewqu719qPiDDbF-0


Related issues

Fixes: https://consensyssoftware.atlassian.net/browse/DSYS-693


Manual testing steps

  1. Web: From the repo root, run yarn storybook, open Components → ButtonBase, and confirm Default, Size, Shape, and Spacing — check pill vs rounded, and label-only vs start/end/both icons for padding and gap.
  2. React Native: Run yarn storybook:ios or yarn storybook:android, open ButtonBase stories, and confirm the same stories and controls behave as expected on device (especially shape and size with icons).
  3. Optional: In a small consumer, render ButtonBase with shape={ButtonBaseShape.Pill} and with start/end icons to confirm visual spacing vs main.

Screenshots/Recordings

Before

After

Simulator.Screen.Recording.-.iPhone.15.Pro.Max.-.2026-05-04.at.08.33.39.mov

Pre-merge author checklist

  • I've followed MetaMask Contributor Docs
  • I've completed the PR template to the best of my ability
  • I’ve included tests if applicable
  • I’ve documented my code using JSDoc format if applicable
  • I’ve applied the right labels on the PR (see labeling guidelines). Not required for external contributors.

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Updates core ButtonBase layout/styling logic on both web and React Native (padding, radius, icon/text sizing, loading rendering) and removes a default loading-overlay testID, which can affect visuals and existing UI tests.

Overview
Refactors ButtonBase on web and React Native to compute size-based border radius, horizontal padding, icon size, and label text variant, and to use a consistent gap model when start/end icons or accessories are present.

On React Native, loading/content rendering is restructured around Box/BoxRow, adding loadingWrapperProps and contentWrapperProps for customization; the loading overlay no longer has a default testID (migration/docs updated to pass one explicitly when needed).

Storybook stories, READMEs, and tests are updated to reflect the new spacing behavior, loading placeholder behavior, and the revised Storybook control guidance (favoring practical interactive argTypes, especially for on-device RN Storybook).

Reviewed by Cursor Bugbot for commit 77c7ef3. Bugbot is set up for automated code reviews on this repo. Configure here.

@brianacnguyen brianacnguyen self-assigned this May 4, 2026
@brianacnguyen brianacnguyen requested a review from a team as a code owner May 4, 2026 15:36
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@brianacnguyen brianacnguyen changed the title refactor: Improved ButtonBase to support all sizes and shapes refactor: Improved ButtonBase to support all shapes and sizes May 4, 2026
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

size: IconSize.Sm,
...endIconProps,
}}
textProps={textProps}

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.

Removing overrides since these live with ButtonBase now

Comment thread packages/design-system-react-native/src/components/ButtonBase/ButtonBase.tsx Outdated
size: IconSize.Sm,
...endIconProps,
}}
textProps={textProps}

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.

Removing overrides since these live with ButtonBase now

size: IconSize.Sm,
...endIconProps,
}}
textProps={textProps}

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.

Removing overrides since these live with ButtonBase now

size: IconSize.Sm,
...endIconProps,
}}
textProps={textProps}

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.

Removing overrides since these live with ButtonBase now

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

argTypes: {
children: {
control: 'text',
description:

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 feel like these might be needed at some point so I chose not to remove these

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall left a comment

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.

A few non-blocking follow-ups before approval: I'm open to the shape direction, but I think we should line it up with actual Figma usage before committing it to the shared ButtonBase API, and there are a few cleanup items in tests, docs, and release-process files that would reduce maintenance overhead.

*
* @default ButtonBaseShape.Rounded
*/
shape?: ButtonBaseShape;

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.

non-blocking: Open to adding shape to the shared ButtonBase API, but it would be good to know how many real button instances in Figma actually use rounded vs pill before we commit to it as a base-level prop. The linked Figma component set does not expose shape today, so I'm not sure yet whether this is truly shared ButtonBase behavior or still a variant-level concern.

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.

We're heading in that direction, and its good to just have this included in as it's a low hanging fruit

Comment thread .cursor/rules/component-documentation.md
Comment thread packages/design-system-react-native/CHANGELOG.md
Comment thread packages/design-system-react-native/MIGRATION.md Outdated
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall left a comment

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.

One non-blocking story-structure follow-up from this pass.

),
};

export const Spacing: Story = {

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.

non-blocking: I appreciate the Spacing story, but I think it’s most useful when it answers a specific consumer question: "If I swap in a custom startAccessory / endAccessory, how much visual space should I design for at each button size?" Given that, I wonder if we could remove the dedicated Spacing story and instead add a rough guideline to the startAccessory / endAccessory sections indicating how much spacing to account for across the different button sizes. That would keep the story list tighter while preserving the part of the guidance that seems most valuable.

@georgewrmarshall georgewrmarshall left a comment

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.

One non-blocking docs follow-up from this pass.


If your React web usage relied on uncontrolled **`Input`** behavior, move that state into the caller and pass a controlled **`value`** instead.

#### ButtonBase: loading wrappers and overlay testID

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.

non-blocking: Generally we just add breaking changes to the migration docs. Additional features or props in this case can just be mentioned in the changelog.

@brianacnguyen brianacnguyen enabled auto-merge (squash) May 8, 2026 20:01
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

/**
* Props passed to the label row (`BoxRow`). Label typography remains controlled via `textProps` on ButtonBase.
*/
contentWrapperProps?: Omit<Partial<BoxRowProps>, 'children' | 'textProps'>;

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.

contentWrapperProps type allows silently-overridden props

Low Severity

The contentWrapperProps type omits children and textProps but still allows startAccessory, endAccessory, and gap, all of which are explicitly overridden by ButtonBase's own props in the JSX (startAccessory={...}, endAccessory={...}, gap={hasAccessories ? 1 : 0}). A consumer passing any of these via contentWrapperProps would have them silently ignored, since the explicit props come after the {...restContentWrapper} spread and win. This is inconsistent with the textProps omission and could confuse consumers.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 539fbaf. Configure here.

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@brianacnguyen brianacnguyen merged commit cc67a13 into main May 11, 2026
83 checks passed
@brianacnguyen brianacnguyen deleted the update/buttonbase branch May 11, 2026 15:46

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 77c7ef3. Configure here.

[ButtonBaseSize.Sm]: TextVariant.BodySm,
[ButtonBaseSize.Md]: TextVariant.BodyMd,
[ButtonBaseSize.Lg]: TextVariant.BodyMd,
};

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.

Identical constants files duplicated across two packages

Low Severity

MAP_BUTTONBASE_SIZE_ICONSIZE and MAP_BUTTONBASE_SIZE_TEXT_VARIANT are character-for-character identical across design-system-react and design-system-react-native. Both map between shared types (ButtonBaseSizeIconSize, ButtonBaseSizeTextVariant) imported from @metamask/design-system-shared with no platform-specific content. Keeping them duplicated creates a divergence risk — if one package updates a mapping without the other, button sizes silently render differently across platforms.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by learned rule: Define component const assertions and shared props in design-system-shared

Reviewed by Cursor Bugbot for commit 77c7ef3. Configure here.

}
return children;
};
const renderContent = () => renderLabel();

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.

Redundant renderContent wrapper delegates to renderLabel

Low Severity

renderContent is now just () => renderLabel() and is called only once in the return statement. The extra indirection adds no value — renderLabel() could be called directly at the single call site (isLoading ? renderLoadingState() : renderLabel()). This leaves a dead-weight function that obscures the actual flow.

Fix in Cursor Fix in Web

Triggered by learned rule: Inline single-use styling values in components

Reviewed by Cursor Bugbot for commit 77c7ef3. Configure here.

@brianacnguyen brianacnguyen mentioned this pull request May 15, 2026
17 tasks
brianacnguyen added a commit that referenced this pull request May 15, 2026
## Release 40.0.0

This release updates the shared layer and both UI packages: new
extension-aligned primitives and form helpers on web, new selection and
multi-line input components on React Native, shared types and context
for those APIs, coordinated **`BannerBase`** close behavior, and
**`ButtonBase`** defaults driven by **`size`** (with migration guidance
for wrappers that overrode label, icon, or spacing).

### Package versions

- `@metamask/design-system-shared`: **0.18.0**
- `@metamask/design-system-react`: **0.23.0**
- `@metamask/design-system-react-native`: **0.25.0**

### Shared type updates (0.18.0)

#### Component type additions
([#1172](#1172),
[#1164](#1164),
[#1169](#1169),
[#1141](#1141))

**What changed**

- **`SelectButton`** shared props and variants
(`SelectButtonPropsShared`, `SelectButtonVariant`,
`SelectButtonEndArrow`).
- **`SegmentGroup`** shared props and **`SegmentGroupContext`**
(`SegmentGroupPropsShared`, `SegmentGroupContext`,
`SegmentGroupContextValue`).
- **`SensitiveText`** shared types (`SensitiveTextLength`,
`SensitiveTextPropsShared`, and related exports).
- **`HelpText`** shared types (`HelpTextSeverity`,
`HelpTextPropsShared`, and related exports).
- **`TextAreaPropsShared`** for multi-line input wrappers.

**Impact**

- React and React Native stay aligned on the same ADR-0003/0004-style
contracts for the new and updated surfaces above.

### React web updates (0.23.0)

#### Added

- **`PopoverHeader`** — popover title rows and trailing actions,
extension migration patterns
([#1158](#1158)).
- **`ModalHeader`** — modal title rows and accessory slots
([#1144](#1144)).
- **`Label`** — captions paired with form controls
([#1152](#1152)).
- **`SensitiveText`** — mask/reveal for sensitive strings with
configurable visible length
([#1164](#1164)).
- **`HelpText`** — helper, success, warning, and error copy under inputs
and controls
([#1169](#1169)).

#### Changed

- **`ButtonBase`** — label **`Text`** variant, start/end icon sizes, and
internal spacing are derived from **`size`** for each
**`ButtonBaseSize`**
([#1150](#1150)).
See [Migration guide —
ButtonBase](https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react/MIGRATION.md#buttonbase-size-defaults).
- **`BannerBase`** — close behavior uses **`onClose`** only;
**`closeButtonProps.onClick`** is not the dismiss API;
**`closeButtonProps`** is for customization
([#1166](#1166)).

### React Native updates (0.25.0)

#### Added

- **`SelectButton`**, **`SegmentButton`**, **`SegmentGroup`**
([#1172](#1172)).
- **`SensitiveText`**, aligned with the shared contract
([#1164](#1164)).
- **`HeaderStandardAnimated`** and **`useHeaderStandardAnimated`**
([#1151](#1151)).
- **`TextArea`**
([#1141](#1141)).

#### Changed

- **`ButtonBase`** — same size-driven defaults as web
([#1150](#1150)).
See [Migration guide —
ButtonBase](https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/MIGRATION.md#buttonbase-size-defaults).
- **`BannerBase`** — close behavior uses **`onClose`** only;
**`closeButtonProps.onPress`** is not the dismiss API
([#1166](#1166)).

### Breaking changes

#### `BannerBase` close API (both platforms)

**What changed**

- **React:** Use **`onClose`** for dismiss behavior.
**`closeButtonProps.onClick`** is removed from that role. The close
control is tied to **`onClose`**. **`closeButtonProps`** remains for
non-behavioral customization (e.g. **`data-testid`**, accessibility,
styling hooks).
- **React Native:** Same pattern: **`onClose`** instead of
**`closeButtonProps.onPress`** for dismiss behavior.

**Migration**

- Move any close action from **`closeButtonProps.onClick`** /
**`closeButtonProps.onPress`** to **`onClose`**.
- If you previously forced a close button with only
**`closeButtonProps`**, also provide **`onClose`**.

**Impact**

- Call sites that dismissed via **`closeButtonProps`** or showed a close
button without **`onClose`** must be updated.

See:

- [React — From version 0.22.0 to
0.x.0](https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react/MIGRATION.md#from-version-0220-to-0x0)
- [React Native — From version 0.24.0 to
0.x.0](https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/MIGRATION.md#from-version-0240-to-0x0)

**`ButtonBase`:** not called out here as a semver-breaking API change;
consumers who overrode typography, icon size, or spacing on
**`ButtonBase`** wrappers should follow the ButtonBase migration
sections linked above.

### Checklist

- [x] Changelogs updated with human-readable descriptions
- [x] `yarn changelog:validate` passes
- [x] Version bumps follow semantic versioning
- [x] `@metamask/design-system-shared`: **minor** (0.17.x → 0.18.0) —
new shared exports for select/segment, sensitive text, help text, text
area
- [x] `@metamask/design-system-react`: **minor** (0.22.x → 0.23.0) — new
components; **`BannerBase`** / **`ButtonBase`** behavior
- [x] `@metamask/design-system-react-native`: **minor** (0.24.x →
0.25.0) — new components; **`BannerBase`** / **`ButtonBase`** behavior
- [x] Breaking changes documented (**`BannerBase`**) in MIGRATION with
before/after examples
- [x] PR references present in changelog entries

## Pre-merge author checklist

- [ ] [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs)
- [ ] [Release
Workflow](https://github.com/MetaMask/metamask-design-system/blob/main/.cursor/rules/release-workflow.md)
- [ ] `yarn build && yarn test && yarn lint`
- [ ] `yarn changelog:validate`

## Pre-merge reviewer checklist

- [ ] [Reviewing Release
PRs](https://github.com/MetaMask/metamask-design-system/blob/main/docs/reviewing-release-prs.md)
- [ ] Package versions and semver match what this PR actually publishes
- [ ] Changelogs are consumer-facing
- [ ] Breaking changes covered in MIGRATION.md
- [ ] No unpublished package is missing from the changelogs / version
list for **this** PR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants