[Bpk-checkbox-card][CLOV-1271]Adding bpk-checkbox-card#4256
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new bpk-component-checkbox-card package to Backpack, implementing a selectable “checkbox card” pattern with a compound-component API, optional theming, and Storybook examples.
Changes:
- Introduces
BpkCheckboxCardcompound components (Root,Control,Content,Label,Description,Indicator) plus aBpkCheckboxCardSimplewrapper. - Adds styling, tests (unit/form/a11y), Storybook stories/examples, and Figma Code Connect.
- Adds extensive specification/planning docs under
specs/001-checkbox-card/and updates eslint ignores.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| typecheck.txt | Adds a checked-in typecheck output artifact (should not be committed). |
| specs/001-checkbox-card/tasks.md | Implementation task breakdown for the component. |
| specs/001-checkbox-card/styling-guide.md | Styling guide / token mapping / SCSS patterns. |
| specs/001-checkbox-card/spec.md | Full requirements/specification for checkbox card. |
| specs/001-checkbox-card/research.md | Research notes on existing patterns/components. |
| specs/001-checkbox-card/plan.md | Implementation plan summary. |
| specs/001-checkbox-card/examples/variants.tsx | Spec examples demonstrating visual variants. |
| specs/001-checkbox-card/examples/interactive-states.tsx | Spec examples for interactive states and selection patterns. |
| specs/001-checkbox-card/examples/edge-cases.tsx | Spec examples for edge cases. |
| specs/001-checkbox-card/examples/basic-usage.tsx | Spec examples for basic usage and disabled states. |
| specs/001-checkbox-card/examples/accessibility.tsx | Spec examples for keyboard/form accessibility scenarios. |
| specs/001-checkbox-card/checklists/requirements.md | Spec quality checklist. |
| specs/001-checkbox-card/api-design.md | Proposed API design and usage examples. |
| packages/bpk-component-checkbox-card/src/themeAttributes.ts | Theme attributes + theme factory for BpkThemeProvider. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCardSimple/BpkCheckboxCardSimple.tsx | Simple wrapper API around the compound component. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/subcomponents/Root.tsx | Core state/ARIA handling and interaction logic. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/subcomponents/Label.tsx | Label slot component using BpkText. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/subcomponents/Indicator.tsx | Optional corner indicator slot. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/subcomponents/Description.tsx | Description slot component using BpkText. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/subcomponents/Control.tsx | Hidden checkbox input for form submission. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/subcomponents/Content.tsx | Content wrapper slot. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/subcomponents/CheckboxCardContext.tsx | React context powering the compound API. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/index.ts | Re-exports compound component API. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/form-test.tsx | Form integration tests. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/common-types.ts | Public constants/types for variants/radius/size. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/accessibility-test.tsx | jest-axe accessibility tests. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/snapshots/BpkCheckboxCard-test.tsx.snap | Snapshots for compound component. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/BpkCheckboxCard.tsx | Compound component composition/export surface. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/BpkCheckboxCard.module.scss | CSS Modules styling for the checkbox card. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/BpkCheckboxCard.figma.tsx | Figma Code Connect integration. |
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/BpkCheckboxCard-test.tsx | Unit + interaction tests (click/keyboard/uncontrolled). |
| packages/bpk-component-checkbox-card/index.ts | Package-level exports (compound, simple wrapper, types, theming). |
| packages/bpk-component-checkbox-card/README.md | Component README (currently documents an outdated API). |
| examples/bpk-component-checkbox-card/stories.tsx | Storybook stories wiring. |
| examples/bpk-component-checkbox-card/examples.tsx | Storybook example implementations incl. theming and indicator. |
| .eslintignore | Ignores specs/ from eslint. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| disabled | ||
| />, | ||
| ); | ||
| const results = await axe(container); |
There was a problem hiding this comment.
In the “when disabled” test, axe is called without AXE_OPTIONS, while the other cases disable the nested-interactive rule. This inconsistency can cause flaky failures (or hide real issues in other cases). Use the same AXE_OPTIONS consistently across all the tests, or document why the disabled case is intentionally different.
| const results = await axe(container); | |
| const results = await axe(container, AXE_OPTIONS); |
| const computedAriaLabelledby = ariaLabelledby ?? labelId; | ||
| const computedAriaDescribedby = ariaDescribedby ?? descriptionId; | ||
|
|
||
| return ( | ||
| <CheckboxCardContext.Provider value={contextValue}> | ||
| <div | ||
| role="checkbox" | ||
| aria-checked={checked} | ||
| aria-disabled={disabled || undefined} | ||
| tabIndex={disabled ? -1 : 0} | ||
| aria-label={ariaLabel} | ||
| aria-labelledby={ariaLabel ? undefined : computedAriaLabelledby} | ||
| aria-describedby={computedAriaDescribedby} | ||
| className={rootClassName} |
There was a problem hiding this comment.
aria-describedby and (when aria-label isn’t provided) aria-labelledby default to generated IDs even when no corresponding Description/Label element is rendered. This produces invalid ARIA IDREFs (and an unlabeled checkbox in the “empty content” case). Consider only setting aria-describedby/aria-labelledby when the consumer explicitly provides them, or introduce a mechanism to know whether the Label/Description slots are present (e.g., register via context/useEffect) before wiring up these attributes; otherwise require aria-label for cards without a visible label.
packages/bpk-component-checkbox-card/src/BpkCheckboxCardSimple/BpkCheckboxCardSimple.tsx
Outdated
Show resolved
Hide resolved
typecheck.txt
Outdated
|
|
||
| > backpack@0.0.1 typecheck | ||
| > tsc | ||
|
|
||
| examples/bpk-component-checkbox-card/examples.tsx(22,30): error TS7016: Could not find a declaration file for module '../../packages/bpk-theming'. '/Users/gert-janvercauteren/conductor/workspaces/backpack/copenhagen/packages/bpk-theming/index.js' implicitly has an 'any' type. | ||
| examples/bpk-component-checkbox-card/stories.tsx(28,8): error TS2307: Cannot find module './new-api-examples' or its corresponding type declarations. | ||
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/BpkCheckboxCard-test.tsx(82,41): error TS2339: Property 'withBackground' does not exist on type '{ readonly onCanvasDefault: "on-canvas-default"; readonly onCanvasContrast: "on-canvas-contrast"; readonly onSurfaceContrast: "on-surface-contrast"; }'. | ||
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/BpkCheckboxCard-test.tsx(94,41): error TS2339: Property 'noBackground' does not exist on type '{ readonly onCanvasDefault: "on-canvas-default"; readonly onCanvasContrast: "on-canvas-contrast"; readonly onSurfaceContrast: "on-surface-contrast"; }'. | ||
| packages/bpk-component-checkbox-card/src/BpkCheckboxCard/BpkCheckboxCardPrice.tsx(19,22): error TS7016: Could not find a declaration file for module '../../../bpk-component-price'. '/Users/gert-janvercauteren/conductor/workspaces/backpack/copenhagen/packages/bpk-component-price/index.js' implicitly has an 'any' type. | ||
| packages/bpk-component-checkbox-card/src/BpkCheckboxCardSimple/BpkCheckboxCardSimple.tsx(156,32): error TS2322: Type '{ children: (false | "" | 0 | Element | null | undefined)[]; orientation: string; align: string; gap: string; }' is not assignable to type 'IntrinsicAttributes & BpkCheckboxCardContentProps'. | ||
| Property 'orientation' does not exist on type 'IntrinsicAttributes & BpkCheckboxCardContentProps'. | ||
| packages/bpk-component-checkbox-card/src/BpkCheckboxCardSimple/BpkCheckboxCardSimple.tsx(157,19): error TS2741: Property 'icon' is missing in type '{ children: ReactElement<any, string | JSXElementConstructor<any>>; }' but required in type 'BpkCheckboxCardIconProps'. | ||
| packages/bpk-component-checkbox-card/src/BpkCheckboxCardSimple/BpkCheckboxCardSimple.tsx(167,20): error TS2741: Property 'price' is missing in type '{ children: string | number | true | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal; }' but required in type 'BpkCheckboxCardPriceProps'. | ||
| packages/bpk-component-checkbox-card/src/BpkCheckboxCardSimple/BpkCheckboxCardSimple.tsx(169,24): error TS2339: Property 'Indicator' does not exist on type '(({ children, checked: controlledChecked, defaultChecked, onCheckedChange, disabled, required, name, value, variant, radius, width, height, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, }: BpkCheckboxCardRootProps) => Element) & { Root: ({ children, checked: control...'. |
There was a problem hiding this comment.
This PR adds typecheck.txt containing local tsc output/errors. This looks like a build artifact rather than a source file, and committing it risks masking the real expectation that the repo should typecheck cleanly. Please remove this file from the PR (and ensure typecheck output is handled by CI rather than checked in).
|
Visit https://backpack.github.io/storybook-prs/4256 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4256 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4256 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4256 to see this build running in a browser. |
Gert-Jan Vercauteren (gert-janvercauteren)
left a comment
There was a problem hiding this comment.
|
kerrie-wu can we also find a way to make sure our demo's retain colour contrast when the checkbox card is active?
|
| if (!context) { | ||
| throw new Error( | ||
| 'CheckboxCard compound components must be used within BpkCheckboxCard.Root. ' + | ||
| 'Make sure you have wrapped your components in <BpkCheckboxCard.Root>.' |
There was a problem hiding this comment.
Hey I wonder how this hook is used. It's quite practical but I tried to remove BpkCheckboxCard.Root in examples, there's no error.
There was a problem hiding this comment.
Hey IrinaWei good catch! You're right that removing BpkCheckboxCard.Root from the existing examples doesn't throw — because none of the built-in subcomponents actually call useCheckboxCardContext internally. The hook is exported as a public API for consumers who want to build custom subcomponents that need to read variant or radius from context. The guard only fires when someone calls the hook directly outside of a Root.
I've added a Storybook story (WithContextOutsideRoot) that demonstrates this — a custom VariantBadge component calls useCheckboxCardContext(), and when rendered outside BpkCheckboxCard.Root, an ErrorBoundary catches and displays the error message.2b57552
|
Visit https://backpack.github.io/storybook-prs/4256 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4256 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4256 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4256 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4256 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4256 to see this build running in a browser. |




Summary
Introduces
bpk-component-checkbox-card— a new Backpack component providing a selectable card UI pattern currently implemented ad-hoc across Hotels, Flights, and Car Hire verticals.What's included
Compound component API (primary)
BpkCheckboxCard.Root<div role="checkbox">— state provider, handles click/keyboardBpkCheckboxCard.Control<input>for native form submission (aria-hidden)BpkCheckboxCard.ContentBpkCheckboxCard.Labelaria-labelledbyBpkCheckboxCard.Descriptionaria-describedbyBpkCheckboxCard.IndicatorSimple API
BpkCheckboxCardSimple— props-based wrapper for common icon + label + price layouts.Variants & customisation
onCanvasDefault,onCanvasContrast,onSurfaceContrastsm,mdrounded,squarecreateCheckboxCardTheme/CHECKBOX_CARD_THEME_ATTRIBUTESchecked/onCheckedChange) and uncontrolled (defaultChecked) modesTests & documentation
Accessibility
role="checkbox"witharia-checked,aria-disabled,aria-requiredSpaceandEntertoggle selectionaria-labelledby/aria-describedbyauto-wired from Label/Description subcomponentstabIndex={-1}andaria-disabled(remain in DOM for AT announcements)Figma
https://www.figma.com/design/ITvypOGdga42nM2ipBM4uk/Bpk-2.0?node-id=90-7627&m=dev
Remember to include the following changes:
[Clover-123][BpkButton] Updating the colourREADME.md(If you have created a new component)README.md