[BpkButton]Add loading prop for BpkButton#4275
[BpkButton]Add loading prop for BpkButton#4275GC Zhu (gc-skyscanner) wants to merge 7 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a loading prop to BpkButton (V2) so consumers can show an in-button spinner and prevent interaction during async operations, with supporting theming, docs, examples, and test updates.
Changes:
- Add
loadingprop toBpkButtonV2, rendering a spinner, applyingaria-busy, and disabling interaction. - Update button SCSS mixins and component styles to support loading visuals (including link-type theming).
- Extend tests (unit + a11y), docs, examples, and Figma mapping; add a new
linkThemeAttributesexport.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/bpk-mixins/_buttons.scss | Refactors hover/pressed token usage into shared mixins and adds loading-disabled visual overrides. |
| packages/bpk-component-button/src/themeAttributes.ts | Adds linkThemeAttributes for theming link loading colour. |
| packages/bpk-component-button/src/themeAttributes-test.ts | Tests new linkThemeAttributes export. |
| packages/bpk-component-button/src/BpkButtonV2/common-types.tsx | Adds loading?: boolean to the public props type. |
| packages/bpk-component-button/src/BpkButtonV2/accessibility-test.tsx | Adds a11y coverage for loading=true across button types and checks aria-busy. |
| packages/bpk-component-button/src/BpkButtonV2/BpkButton.tsx | Implements loading UI (spinner + hidden content) and disables interaction when loading. |
| packages/bpk-component-button/src/BpkButtonV2/BpkButton.module.scss | Adds layout styles for loading spinner/content. |
| packages/bpk-component-button/src/BpkButtonV2/BpkButton.figma.tsx | Exposes loading in the Figma-connected example mapping. |
| packages/bpk-component-button/src/BpkButtonV2/BpkButton-test.tsx | Adds unit tests for loading behaviour and spinner variants. |
| packages/bpk-component-button/index.ts | Re-exports linkThemeAttributes. |
| packages/bpk-component-button/README.md | Documents the loading state usage and link loading theming. |
| examples/bpk-component-button/examples.tsx | Adds Storybook examples showcasing loading state for multiple variants. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
packages/bpk-component-button/src/BpkButtonV2/BpkButton-test.tsx
Outdated
Show resolved
Hide resolved
packages/bpk-component-button/src/BpkButtonV2/BpkButton.module.scss
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| &__content--hidden { | ||
| visibility: hidden; |
There was a problem hiding this comment.
visibility: hidden on the children wrapper will likely remove the button’s text from the accessibility tree in real browsers, which can cause the button to lose its accessible name while loading=true. Consider using a visually-hidden approach that keeps content available to assistive tech (e.g., opacity: 0 / color: transparent) or setting an explicit aria-label/aria-labelledby on the button during loading.
| visibility: hidden; | |
| opacity: 0; | |
| color: transparent; |
packages/bpk-component-button/src/BpkButtonV2/BpkButton-test.tsx
Outdated
Show resolved
Hide resolved
packages/bpk-component-button/src/BpkButtonV2/BpkButton-test.tsx
Outdated
Show resolved
Hide resolved
|
Visit https://backpack.github.io/storybook-prs/4275 to see this build running in a browser. |
1 similar comment
|
Visit https://backpack.github.io/storybook-prs/4275 to see this build running in a browser. |
5f004dc to
2adc2e8
Compare
|
Visit https://backpack.github.io/storybook-prs/4275 to see this build running in a browser. |
| const isLinkType = type === BUTTON_TYPES.link || type === BUTTON_TYPES.linkOnDark; | ||
| const alternate = type === BUTTON_TYPES.linkOnDark; | ||
| const shouldUnderline = isLinkType && !iconOnly && !disabled; | ||
| const shouldUnderline = isLinkType && !iconOnly && !isDisabled; |
| : <BpkSpinner type={getSpinnerType(type)} alignToButton />} | ||
| </span> | ||
| <div className={getCommonClassName('bpk-button__content--hidden')}> | ||
| {innerContent} |
There was a problem hiding this comment.
This content is still required and we need to set its opacity to 0 to maintain the same dimensions as when it’s not in a loading state.
The reason for setting opacity: 0 is referenced here: https://github.com/Skyscanner/backpack/pull/4275#discussion_r2917502180.https://github.com/Skyscanner/backpack/pull/4275#discussion_r2917502180
| ); | ||
|
|
||
| @include utils.bpk-hover { | ||
| @include utils.bpk-themeable-property( |
There was a problem hiding this comment.
extract it to reduce duplication, since the styling of loading state is same with hover's
|
Visit https://backpack.github.io/storybook-prs/4275 to see this build running in a browser. |
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
|
Visit https://backpack.github.io/storybook-prs/4275 to see this build running in a browser. |
| className={classNames} | ||
| {...getDataComponentAttribute('Button')} | ||
| onClick={onClick} | ||
| target={target} |
There was a problem hiding this comment.
didn't add aria-busy={loading || undefined} in since it won't hit when it is loading, becasue isDisabled will be set as true
| } | ||
|
|
||
| &__content--hidden { | ||
| opacity: 0; |
There was a problem hiding this comment.
| ? <span className={underlinedClassName}>{children}</span> | ||
| : children; | ||
|
|
||
| const content = loading ? ( |
There was a problem hiding this comment.
I can imagine that if the PR for leadingIcon and trailingIcon gets merged first, we’ll need to wrap leadingIcon and trailingIcon inside the content container. Otherwise, leadingIcon and trailingIcon will still be visible when the button is in a loading state.
| type = BUTTON_TYPES.primary, | ||
| ...rest | ||
| }: Props) => { | ||
| const isDisabled = disabled || loading; |
|
Visit https://backpack.github.io/storybook-prs/4275 to see this build running in a browser. |
886205d to
2ab9461
Compare
|
Visit https://backpack.github.io/storybook-prs/4275 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4275 to see this build running in a browser. |
Gert-Jan Vercauteren (gert-janvercauteren)
left a comment
There was a problem hiding this comment.
LGTM, can you hold until the icon PR is in and then rebase?


Summary
loadingprop toBpkButtoncomponentloadingistrue, the button displays a spinner and is disabled to prevent double-submissionFigma
BpkButton.tsx— addedloadingprop handlingcommon-types.tsx— addedloadingto props interfaceBpkButton.module.scss— added loading state stylesBpkButton-test.tsx/accessibility-test.tsx— added test coveragethemeAttributes.ts/themeAttributes-test.ts— updated theme attributesBpkButton.figma.tsx— updated Figma mappingexamples.tsx/README.md— added usage examples and documentationTest plan
npm run jest -- --testPathPattern bpk-component-buttonto verify unit tests passnpm run lintto verify no lint errors🤖 Generated with Claude Code