Skip to content
This repository was archived by the owner on Aug 5, 2026. It is now read-only.

[BpkThemeProvider] Introduce typed theme system with BpkLegacyThemeProvider - #4911

Open
Gert-Jan Vercauteren (gert-janvercauteren) wants to merge 12 commits into
mainfrom
gert-janvercauteren/rethink-bpk-theme-provider
Open

[BpkThemeProvider] Introduce typed theme system with BpkLegacyThemeProvider#4911
Gert-Jan Vercauteren (gert-janvercauteren) wants to merge 12 commits into
mainfrom
gert-janvercauteren/rethink-bpk-theme-provider

Conversation

@gert-janvercauteren

Copy link
Copy Markdown
Contributor

Introduces a new typed theme provider alongside the existing one (now renamed to BpkLegacyThemeProvider).

New BpkThemeProvider (TypeScript, default export):

  • Typed BpkTheme — flat semantic tokens (corePrimary, textPrimary, …) + namespaced component tokens (button.colourBgPrimary, …) generated from the DTCG token tree
  • Partial-safe: any subset of keys works; no all-or-nothing failure
  • React Context + useBpkTheme() hook for portal/Shadow DOM consumers
  • Mode-specific { light, dark } values inject a scoped <style> tag
  • as prop for wrapper element override

BpkLegacyThemeProvider (renamed from BpkThemeProvider.js): unchanged behaviour, now exported as a named export { BpkLegacyThemeProvider }. All affected stories migrated to use this.

token-sync/src/typescript-theme-formatter.ts: generates src/generated/BpkTheme.ts from backpack.light.json — run with pnpm -w run build:theme-types.

Remember to include the following changes:

  • Tests
  • Storybook examples created/updated
  • README.md (If you have created a new component)
  • Component README.md
  • Accessibility tests
    • The following checks were performed:
      • Ability to navigate using a keyboard only
      • Zoom functionality (Deque University explanation):
        • The page SHOULD be functional AND readable when only the text is magnified to 200% of its initial size
        • Pages must reflow as zoom increases up to 400% so that content continues to be presented in only one column i.e. Content MUST NOT require scrolling in two directions (both vertically and horizontally)
      • Ability to navigate using a screen reader only

Copilot AI review requested due to automatic review settings July 20, 2026 07:46
@skyscanner-backpack-bot

skyscanner-backpack-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Browser support

If this is a visual change, make sure you've tested it in multiple browsers.

Generated by 🚫 dangerJS against c1ac1a5

Copilot AI 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.

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Introduces a new typed BpkThemeProvider (TS) alongside a renamed BpkLegacyThemeProvider, plus a token-sync generator that emits a strongly typed theme model and a themeToVars() mapper.

Changes:

  • Add a token-sync script to generate BpkTheme types and a runtime themeToVars() mapper from the DTCG token tree.
  • Introduce a new TS BpkThemeProvider with context/hook support and mode-specific theming via a scoped <style> tag.
  • Rename the existing JS provider to BpkLegacyThemeProvider, update stories/tests, and adjust coverage ignores for generated files.

Reviewed changes

Copilot reviewed 19 out of 21 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
token-sync/src/typescript-theme-formatter.ts New generator script to emit typed theme interfaces + themeToVars() from JSON tokens.
packages/backpack-web/src/bpk-theming/src/generated/BpkTheme.ts Generated theme typings and themeToVars() mapping for CSS custom properties.
packages/backpack-web/src/bpk-theming/src/snapshots/BpkThemeProvider-test.tsx.snap Snapshot updates for the new TS BpkThemeProvider.
packages/backpack-web/src/bpk-theming/src/snapshots/BpkThemeProvider-test.js.snap Snapshot renames to reflect BpkLegacyThemeProvider.
packages/backpack-web/src/bpk-theming/src/BpkThemeProvider.tsx New typed theme provider with context/hook and dark/light mode style injection.
packages/backpack-web/src/bpk-theming/src/BpkThemeProvider.js Renames exported legacy provider implementation to BpkLegacyThemeProvider (component name/export).
packages/backpack-web/src/bpk-theming/src/BpkThemeProvider-test.tsx New tests for typed provider behavior (partial themes, mode values, hook).
packages/backpack-web/src/bpk-theming/src/BpkThemeProvider-test.js Updates legacy provider tests to import/use BpkLegacyThemeProvider.
packages/backpack-web/src/bpk-theming/index.js New entrypoint exports for hook/types/default provider and legacy provider.
packages/backpack-web/src/bpk-theming/index.d.ts Updates handwritten typings to refer to the legacy provider.
packages/backpack-web/src/bpk-component-price/src/BpkPrice.stories.tsx Story updates to use BpkLegacyThemeProvider.
packages/backpack-web/src/bpk-component-chip/src/BpkChip.stories.tsx Story updates to use BpkLegacyThemeProvider.
packages/backpack-web/src/bpk-component-checkbox/src/BpkCheckboxV2/BpkCheckboxV2.stories.tsx Story updates to use BpkLegacyThemeProvider.
packages/backpack-web/src/bpk-component-checkbox-card/src/BpkCheckboxCard.stories.tsx Story updates to use BpkLegacyThemeProvider.
packages/backpack-web/src/bpk-component-chatbot-input/src/BpkChatbotInput.stories.tsx Story updates to use BpkLegacyThemeProvider and token imports.
packages/backpack-web/src/bpk-component-button/src/BpkButton.stories.tsx Story updates to use BpkLegacyThemeProvider and refactors theme constant placement.
packages/backpack-web/src/bpk-component-badge/src/BpkBadge.stories.tsx Story updates to use BpkLegacyThemeProvider.
packages/backpack-web/package.json Updates export map entry for ./bpk-theming.
packages/backpack-web/jest.config.js Ignores generated files from coverage.
package.json Adds build:theme-types script and ignores generated files from coverage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +48 to 49
"./bpk-theming": "./src/bpk-theming/index.ts",
"./*": "./src/*/index.ts"
Comment on lines +24 to +26
// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.

import { borderRadiusLg, corePrimaryDay } from '@skyscanner/bpk-foundations-web/tokens/base.es6';
Comment on lines 30 to 33
// @ts-ignore


import NewWindowIcon from '../../bpk-component-icon/sm/new-window';
Comment on lines +121 to +124
{styleTagContent && (
// eslint-disable-next-line react/no-danger
<style dangerouslySetInnerHTML={{ __html: styleTagContent }} />
)}
Comment on lines +145 to +160
Object.entries(t).forEach(([key, value]) => {
if (isModeValue(value)) {
// Flat semantic key (e.g. corePrimary, textPrimary)
const probe = themeToVars({ [key]: sentinel } as BpkTheme);
const cssVar = Object.keys(probe)[0];
if (cssVar) {
entries.push([cssVar, value.light, value.dark]);
}
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
// Namespaced component key (e.g. button.bgPrimary)
const nsObj = value as Record<string, BpkThemedValue>;
Object.entries(nsObj).forEach(([nsKey, nsValue]) => {
if (!isModeValue(nsValue)) return;
const probe = themeToVars({ [key]: { [nsKey]: sentinel } } as BpkTheme);
const cssVar = Object.keys(probe)[0];
if (cssVar) {
Comment on lines +85 to +86
/** Additional inline styles applied to the wrapper element. */
style?: Record<string, string>;
Comment on lines +63 to +68
function toCamelFragment(segment: string): string {
return segment
.replace(/[^a-zA-Z0-9]+(.)/g, (_, c: string) => c.toUpperCase())
.replace(/^(.)/, (c: string) => c.toLowerCase())
.replace(/[^a-zA-Z0-9]/g, '');
}
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser.

6 similar comments
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser.

…ovider

- Add new BpkThemeProvider.tsx: typed BpkTheme, partial-safe, React Context
  + useBpkTheme hook, { light, dark } mode-specific values via scoped <style>
  tag, `as` prop
- Add token-sync/src/typescript-theme-formatter.ts: generates BpkTheme.ts
  from DTCG backpack.light.json with flat semantic tokens + namespaced
  component tokens and themeToVars() runtime mapper
- Rename old BpkThemeProvider.js → BpkLegacyThemeProvider.js; export as
  { BpkLegacyThemeProvider } for existing consumers
- Update bpk-theming entry point to index.ts; remove hand-written index.d.ts
- Migrate stories to use { BpkLegacyThemeProvider } named import
- Add coveragePathIgnorePatterns for generated/ directory

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… Figma

Add token-sync/ghost-tokens.json: keyed by DTCG component name, each entry
declares a CSS var the component SCSS uses that has no Figma counterpart.
Wire into typescript-theme-formatter.ts so ghost entries are merged into the
generated BpkButtonTheme (and any future component interfaces) alongside
Figma-sourced tokens.

Initial Button ghosts: colourTextPrimary, colourTextPrimaryOnDark,
colourTextPrimaryOnLight, colourTextSecondary, colourTextSecondaryOnDark,
colourTextDestructivePressed, colourTextDisabled, colourTextLink,
dimensionMinHeightLarge.

To add ghosts for another component: append an entry to ghost-tokens.json
and run `pnpm -w run build:theme-types`.

Also fix stray @ts-expect-error placement in BpkChatbotInput.stories.tsx.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replace BpkLegacyThemeProvider + flat camelCase keys with the new typed
BpkThemeProvider and BpkTheme shape (button namespace). Removes the
allButtonThemeAttributes import — no themeAttributes prop needed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
THEMED_BUTTON_THEME now uses BpkThemedValue { light, dark } objects for all
colour tokens so the story responds to the Storybook colour-scheme toolbar.
Structural tokens (radius, min-height, padding) stay as plain strings.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…story

Replace placeholder colour values with exact token values from Figma:
  Light: node-id=16727-13420 — standard nav radius (8px), navy primary
  Dark:  node-id=16727-13421 — pill radius (999px), blue primary (#024daf),
         cyan featured (#d1f7ff/#84e9ff), dark canvas backgrounds

dimensionRadius now uses { light: '8px', dark: '999px' } to match the
shape change between the two Figma frames.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…c values

BpkThemedValue now accepts { light?, dark? } — either key is optional.
A value of { light: '#foo' } overrides only the light selector; { dark: '#foo' }
overrides only the dark selector. Both keys present behaves as before.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…eme="dark"])

{ light: '...' } without a dark value was bleeding into dark mode because the
unscoped #id selector applies in all modes. Now single-mode overrides use a
tight selector — light-only uses :root:not([data-theme="dark"]) #id, dark-only
uses :root[data-theme="dark"] #id. Both-modes behaviour is unchanged.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Reduce THEMED_BUTTON_THEME to the tokens that actually differ from the
Backpack defaults — radius (pill) and secondary colour overrides.
Removes the full colour spec; default CSS vars cover the rest.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: OpenCode <noreply@opencode.ai>
…typed provider to BpkThemeProviderV2

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Stories using BpkThemeProvider (legacy) need no import changes —
the default export still works. Reverts unintended formatting noise
from the ESLint auto-fix.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@gert-janvercauteren
Gert-Jan Vercauteren (gert-janvercauteren) force-pushed the gert-janvercauteren/rethink-bpk-theme-provider branch from 950f752 to c1ac1a5 Compare August 4, 2026 07:32
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

minor Non breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants