Skip to content

fix: Add backwards compatability with canvas provider#3773

Open
mannycarrera4 wants to merge 12 commits intoWorkday:prerelease/majorfrom
mannycarrera4:mc-forward-theming
Open

fix: Add backwards compatability with canvas provider#3773
mannycarrera4 wants to merge 12 commits intoWorkday:prerelease/majorfrom
mannycarrera4:mc-forward-theming

Conversation

@mannycarrera4
Copy link
Contributor

@mannycarrera4 mannycarrera4 commented Feb 18, 2026

Summary

Fixes: #3747

Release Category

Components

Release Note

We've added backwards compatibility to CanvasProvider to customize theming via the theme prop even though our components now use semantic brandable tokens. Consumers should only use this approach when creating a scoped theme intended to break away from global theming.


Checklist

For the Reviewer

  • PR title is short and descriptive
  • PR summary describes the change (Fixes/Resolves linked correctly)
  • PR Release Notes describes additional information useful to call out in a release message or removed if not applicable
  • Breaking Changes provides useful information to upgrade to this code or removed if not applicable

Where Should the Reviewer Start?

Areas for Feedback? (optional)

  • Code
  • Documentation
  • Testing
  • Codemods

Testing Manually

Screenshots or GIFs (if applicable)

Thank You Gif (optional)

@cypress
Copy link

cypress bot commented Feb 18, 2026

Workday/canvas-kit    Run #10408

Run Properties:  status check passed Passed #10408  •  git commit 3aa9717b6f ℹ️: Merge c3a07f05b95bba4d740942978195dc0dfe8dc460 into 14731fab3cd0973f0149317a0cea...
Project Workday/canvas-kit
Branch Review mc-forward-theming
Run status status check passed Passed #10408
Run duration 02m 26s
Commit git commit 3aa9717b6f ℹ️: Merge c3a07f05b95bba4d740942978195dc0dfe8dc460 into 14731fab3cd0973f0149317a0cea...
Committer Manuel Carrera
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 15
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 790
View all changes introduced in this branch ↗︎
UI Coverage  19.45%
  Untested elements 1522  
  Tested elements 365  
Accessibility  99.38%
  Failed rules  5 critical   5 serious   0 moderate   2 minor
  Failed elements 72  

@mannycarrera4 mannycarrera4 moved this to 🏗 In progress in Canvas Kit Feb 23, 2026
@mannycarrera4 mannycarrera4 added the ready for review Code is ready for review label Feb 24, 2026
@mannycarrera4 mannycarrera4 marked this pull request as ready for review February 24, 2026 20:57
@jaclynjessup jaclynjessup moved this from 🏗 In progress to 👀 In review in Canvas Kit Feb 25, 2026
@RayRedGoose RayRedGoose requested a review from Copilot February 25, 2026 17:48
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds backwards compatibility to CanvasProvider for customizing theming via the theme prop. It addresses issue #3747 where the old theme format (e.g., brand.primary.base) doesn't work with the new semantic brand token system (e.g., brand.primary.600 and system.color.brand.*). The implementation creates mappings from deprecated theme palette keys to both legacy brand tokens and new numerical/system brand tokens.

Changes:

  • Added comprehensive token mapping logic in CanvasProvider to forward theme values from old palette format to new brand and system tokens
  • Updated popup theming to apply CSS variables earlier and remove cleanup to prevent theme flashing
  • Updated documentation and examples to demonstrate scoped theming with the new backwards-compatible approach

Reviewed changes

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

Show a summary per file
File Description
modules/react/common/lib/CanvasProvider.tsx Core backwards compatibility logic with mappings from deprecated palette keys to numerical brand tokens and system tokens
modules/react/popup/lib/hooks/usePopupStack.ts Refactored theme application timing and removed cleanup to prevent cascade barriers and theme flashing
modules/react/popup/stories/visual-testing/Popup.stories.tsx Updated PopupThemed story to properly test themed popup with trigger button
modules/react/common/stories/mdx/examples/Theming.tsx Updated example to demonstrate scoped theming with CanvasProvider theme prop
modules/react/common/stories/mdx/Theming.mdx Extensive documentation updates explaining v14/v15 theming approach and backwards compatibility
modules/docs/mdx/15.0-UPGRADE-GUIDE.mdx Added section explaining system brand tokens and backwards compatibility
modules/docs/llm/upgrade-guides/15.0-UPGRADE-GUIDE.md Added section explaining system brand tokens and backwards compatibility

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

const commonTokenMapping = {
focusOutline: brand.common.focus, // maps to brand.primary500
alertInner: brand.common.caution.inner, // maps to brand.caution400
alertOuter: brand.common.caution.outer, // maps to brand.caution500
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent token naming format in comment. This should use dot notation "brand.caution.500" to match the format used in the documentation comments above.

Suggested change
alertOuter: brand.common.caution.outer, // maps to brand.caution500
alertOuter: brand.common.caution.outer, // maps to brand.caution.500

Copilot uses AI. Check for mistakes.
Comment on lines +271 to +288
} else if (key === 'dark') {
// system.color.brand.fg.{color}.strong -> brand.{color}.700
// @ts-ignore
const systemFgStrongToken = system.color.brand.fg[newBrandColor]?.strong;
if (systemFgStrongToken) {
// @ts-ignore
style[systemFgStrongToken] = value;
}

// system.color.brand.fg.selected -> brand.primary.700 (for primary only)
if (newBrandColor === 'primary') {
// @ts-ignore
const selectedToken = system.color.brand.fg.selected;
if (selectedToken) {
// @ts-ignore
style[selectedToken] = value;
}
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing implementation for system token forwarding. According to the documentation at lines 65, 71-72, system.color.brand.focus.critical and system.color.brand.border.critical should be controlled by palette.error.dark, and system.color.brand.border.caution should be controlled by palette.alert.dark. However, in the implementation at lines 271-288, when key === 'dark', only system.color.brand.fg.{color}.strong and system.color.brand.fg.selected are forwarded. The focus and border tokens for critical and caution colors are not being set, which means theming palette.error.dark or palette.alert.dark won't affect these system tokens as documented.

Copilot uses AI. Check for mistakes.
## Global vs Scoped Theming

Canvas Kit v14 supports two theming strategies: **global theming** and **scoped theming**. Understanding the difference is important to avoid unexpected behavior.
Canvas Kit v14 and v15 supports two theming strategies: **global theming** and **scoped theming**. Understanding the difference is important to avoid unexpected behavior.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subject-verb agreement error. "Canvas Kit v14 and v15" is plural, so it should use "support" (plural verb) instead of "supports" (singular verb).

Suggested change
Canvas Kit v14 and v15 supports two theming strategies: **global theming** and **scoped theming**. Understanding the difference is important to avoid unexpected behavior.
Canvas Kit v14 and v15 support two theming strategies: **global theming** and **scoped theming**. Understanding the difference is important to avoid unexpected behavior.

Copilot uses AI. Check for mistakes.
*/
const commonTokenMapping = {
focusOutline: brand.common.focus, // maps to brand.primary500
alertInner: brand.common.caution.inner, // maps to brand.caution400
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent token naming format in comment. Similar to line 104, this should use dot notation "brand.caution.400" to match the format used in the documentation comments above (lines 64-101).

Suggested change
alertInner: brand.common.caution.inner, // maps to brand.caution400
alertInner: brand.common.caution.inner, // maps to brand.caution.400

Copilot uses AI. Check for mistakes.

### System Brand Tokens and Brand Tokens

The relationship between **system brand tokens** (e.g. `system.color.brand.accent.primary`) and **brand tokens** (e.g. `brand.primary600`) has changed. Teams can still set palette values such as `base`, `dark`, `darkest`, `light`, `lightest`, and `lighter` via the `CanvasProvider` theme prop. The mapping inside `CanvasProvider` exists for **backwards compatibility**: when you pass a theme object, we forward those values to both the legacy brand tokens and the system brand tokens so existing usage continues to work.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent token naming format. The token is shown as "brand.primary600" but this is inconsistent with the documentation format used in CanvasProvider.tsx (lines 64-101) where tokens are shown with dots for readability (e.g., "brand.primary.600"). Consider using "brand.primary.600" for consistency with the rest of the documentation.

Suggested change
The relationship between **system brand tokens** (e.g. `system.color.brand.accent.primary`) and **brand tokens** (e.g. `brand.primary600`) has changed. Teams can still set palette values such as `base`, `dark`, `darkest`, `light`, `lightest`, and `lighter` via the `CanvasProvider` theme prop. The mapping inside `CanvasProvider` exists for **backwards compatibility**: when you pass a theme object, we forward those values to both the legacy brand tokens and the system brand tokens so existing usage continues to work.
The relationship between **system brand tokens** (e.g. `system.color.brand.accent.primary`) and **brand tokens** (e.g. `brand.primary.600`) has changed. Teams can still set palette values such as `base`, `dark`, `darkest`, `light`, `lightest`, and `lighter` via the `CanvasProvider` theme prop. The mapping inside `CanvasProvider` exists for **backwards compatibility**: when you pass a theme object, we forward those values to both the legacy brand tokens and the system brand tokens so existing usage continues to work.

Copilot uses AI. Check for mistakes.
Comment on lines +259 to +270
// system.color.brand.focus.primary (maps to brand.primary.500 per docs)
// For primary only, update focus border when 'main' changes
if (newBrandColor === 'primary') {
// Calculate a reasonable focus color based on the main color
// We'll use the main value since brand.primary.500 derives from it
// @ts-ignore
const focusToken = system.color.brand.focus.primary;
if (focusToken) {
// @ts-ignore
style[focusToken] = value;
}
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential token override conflict. The code sets system.color.brand.focus.primary in two places: when palette.common.focusOutline is customized (lines 184-191) and when palette.primary.main is customized (lines 259-270). Since the color iteration order is 'common' first then 'primary' (line 169), if a user sets both palette.common.focusOutline and palette.primary.main, the palette.primary.main value will override palette.common.focusOutline for system.color.brand.focus.primary. This precedence should be either documented in the comment at line 64, or the code should check if focusOutline was already customized before overriding it with palette.primary.main.

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +107
focusOutline: brand.common.focus, // maps to brand.primary500
alertInner: brand.common.caution.inner, // maps to brand.caution400
alertOuter: brand.common.caution.outer, // maps to brand.caution500
errorInner: brand.common.critical, // maps to brand.critical500
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent token naming format in comment. This should use dot notation "brand.critical.500" to match the format used in the documentation comments above.

Suggested change
focusOutline: brand.common.focus, // maps to brand.primary500
alertInner: brand.common.caution.inner, // maps to brand.caution400
alertOuter: brand.common.caution.outer, // maps to brand.caution500
errorInner: brand.common.critical, // maps to brand.critical500
focusOutline: brand.common.focus, // maps to brand.primary.500
alertInner: brand.common.caution.inner, // maps to brand.caution.400
alertOuter: brand.common.caution.outer, // maps to brand.caution.500
errorInner: brand.common.critical, // maps to brand.critical.500

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +70
* - `system.color.brand.focus.primary` → `brand.primary.500` → Controlled by `palette.primary.main` or `focusOutline`
* - `system.color.brand.focus.critical` → `brand.critical.500` → Controlled by `palette.error.dark` or `errorInner`
* - `system.color.brand.focus.caution.inner` → `brand.caution.400` → Controlled by `palette.alert.main` or `alertInner`
* - `system.color.brand.focus.caution.outer` → `brand.caution.500` → Controlled by `palette.alert.dark` or `alertOuter`
*
* ### Border Tokens
* - `system.color.brand.border.primary` → `brand.primary.500` → Controlled by `palette.primary.main` or `focusOutline`
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect token mapping in documentation. The comment states that palette.primary.main maps to brand.primary.500, but according to the numericalTokenMapping at line 38, main maps to '600' which would produce brand.primary.600, not brand.primary.500. The comment should indicate that focusOutline controls brand.primary.500 separately from palette.primary.main which controls brand.primary.600.

Suggested change
* - `system.color.brand.focus.primary` `brand.primary.500` Controlled by `palette.primary.main` or `focusOutline`
* - `system.color.brand.focus.critical` `brand.critical.500` Controlled by `palette.error.dark` or `errorInner`
* - `system.color.brand.focus.caution.inner` `brand.caution.400` Controlled by `palette.alert.main` or `alertInner`
* - `system.color.brand.focus.caution.outer` `brand.caution.500` Controlled by `palette.alert.dark` or `alertOuter`
*
* ### Border Tokens
* - `system.color.brand.border.primary` `brand.primary.500` Controlled by `palette.primary.main` or `focusOutline`
* - `system.color.brand.focus.primary` `brand.primary.500` Controlled by `focusOutline` (separately from `palette.primary.main`, which controls `brand.primary.600`)
* - `system.color.brand.focus.critical` `brand.critical.500` Controlled by `palette.error.dark` or `errorInner`
* - `system.color.brand.focus.caution.inner` `brand.caution.400` Controlled by `palette.alert.main` or `alertInner`
* - `system.color.brand.focus.caution.outer` `brand.caution.500` Controlled by `palette.alert.dark` or `alertOuter`
*
* ### Border Tokens
* - `system.color.brand.border.primary` `brand.primary.500` Controlled by `focusOutline` (separately from `palette.primary.main`, which controls `brand.primary.600`)

Copilot uses AI. Check for mistakes.
## Overview

Canvas Kit v14 introduces a significant shift in our approach to theming: we've moved away from
Canvas Kit v14 and v15 introduces a significant shift in our approach to theming: we've moved away from
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subject-verb agreement error. "Canvas Kit v14 and v15" is plural, so it should use "introduce" (plural verb) instead of "introduces" (singular verb).

Suggested change
Canvas Kit v14 and v15 introduces a significant shift in our approach to theming: we've moved away from
Canvas Kit v14 and v15 introduce a significant shift in our approach to theming: we've moved away from

Copilot uses AI. Check for mistakes.
// Forward to all relevant system.color.brand.* tokens
// These system tokens reference the numerical brand tokens, so updating them ensures full compatibility
if (key === 'main') {
// system.color.brand.accent.{color} -> brand.{color}.600 (except caution -> 400)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment is potentially misleading. The comment says "except caution -> 400" suggesting caution is an exception, but based on the documentation at line 88, system.color.brand.accent.caution does map to brand.caution.400, not brand.caution.600. The code handles this correctly by forwarding palette.alert.main to the accent token, but the comment should clarify that for caution/alert, the mapping is intentionally to .400 instead of .600, not that it's an exception that needs special handling in the code.

Suggested change
// system.color.brand.accent.{color} -> brand.{color}.600 (except caution -> 400)
// system.color.brand.accent.{color} -> corresponding brand.{color} token
// (per docs, most map to .600; caution/alert intentionally maps to .400 via the token definition)

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +58
/**
* Mapping from deprecated common palette keys to new brand.common tokens.
*
* ## Brandable System Tokens
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we keeping all of these comments?

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

Labels

ready for review Code is ready for review

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

3 participants