Skip to Content

Sana Canvas Kit v16 Upgrade Guide

A guide for developers on what's new in Sana Canvas Kit v16 and how to make the upgrade.

This guide contains an overview of the changes in Canvas Kit v16. Please reach out  if you have any questions.

Why You Should Upgrade

Canvas Kit v16 is a 1:1 component update that aligns every Canvas component to Sana’s visual styling. Components ship that styling regardless of theme — you do not need to opt into the Sana Canvas theme for components to look correct.

The Sana Canvas theme (CSS variables + data-theme="sana-canvas") is a separate, opt-in step that updates brand colors, neutrals, surfaces, and shapes at the application level. See Sana Canvas Theme below.

What You Need

  • Canvas Tokens v4.4.0: Introduces the new Sana Canvas variables.
  • Canvas System Icons v5.0.0: Introduces the new Sana Canvas icons.
  • Canvas Kit v16: The new version of Canvas Kit that includes the new Sana Canvas components and utilities.

Note: Canvas Tokens and Canvas System Icons are not included in the Canvas Kit package. Install them separately, then upgrade Canvas Kit itself:

# Separate packages required by Canvas Kit v16 yarn add @workday/canvas-tokens-web@4.4.0 @workday/canvas-system-icons-web@5.0.0 # Upgrade Canvas Kit packages (adjust package names to what your app uses) yarn add @workday/canvas-kit-react@^16

Sana Canvas Theme

Your application is Sana-aligned out of the box when the Sana Canvas theme is applied. To opt in — brand neutrals, surfaces, and shapes — import the Sana CSS variables and set data-theme="sana-canvas" on <html>.

Note: All visual updates in this guide apply to both the Default Canvas theme and the Sana Canvas theme unless specified otherwise.

Opting In

Import the Sana variables last in your root CSS entry point. Sana’s [data-theme="sana-canvas"] selector and :root have equal specificity — source order determines the winner.

/* index.css — order matters */ @import '@workday/canvas-tokens-web/css/base/_variables.css'; @import '@workday/canvas-tokens-web/css/brand/_variables.css'; @import '@workday/canvas-tokens-web/css/component/_variables.css'; @import '@workday/canvas-tokens-web/css/system/_variables.css'; /* Sana last: [data-theme="sana-canvas"] and :root have equal specificity (0,1,0), so when both match <html> the cascade falls back to source order. */ @import '@workday/canvas-tokens-web/css/sana/_variables.css';

Set data-theme="sana-canvas" on <html> when you control the document root (preferred). Nested elements are not enough for portaled popups (including all Canvas Kit popups such as menus, selects, modals, and toasts) — those render under document.body via React portals, outside the parent component’s DOM hierarchy, and only inherit theme from <html>, or from sanaCanvasProviderTheme on CanvasProvider when <html> is unavailable.

<html lang="en" data-theme="sana-canvas"></html>

Gotcha: There is no [data-theme="canvas"] rule. The sana file only defines [data-theme="sana-canvas"] overrides, so removing the attribute is how you get classic Canvas — there’s nothing to undo it with.

Scoped / no document-root control: if you cannot set data-theme on <html> (embedded apps, microfrontends, third-party shells), pass both data-theme="sana-canvas" and sanaCanvasProviderTheme to CanvasProvider. The preset supplies Sana’s brand variables, and Canvas Kit forwards the data-theme attribute onto the popup stack container — so portaled menus, modals, and dialogs match the same [data-theme="sana-canvas"] selector and pick up the rest of the theme (shape, depth, type, non-brand system colors) through normal cascade:

import {CanvasProvider, sanaCanvasProviderTheme} from '@workday/canvas-kit-react/common'; <CanvasProvider theme={sanaCanvasProviderTheme} data-theme="sana-canvas"> <App /> </CanvasProvider>

What Changes When You Opt In

Things that change are primary brand consumers and Sana’s neutral color scale (--cnvs-brand-neutral-*, which replaces classic slate neutrals). Verified against @workday/canvas-tokens-web/css/sana/_variables.css:

  • Brand primary consumers flip. --cnvs-sys-color-brand-accent-primary and -accent-action re-point from blue to --cnvs-brand-neutral-975, and -brand-fg-primary-default/-strong to --cnvs-brand-neutral-a900/-a950. PrimaryButton, brand links, and selected states are what visibly change.
  • Focus does not. --cnvs-sys-color-brand-focus-primary and -border-primary stay blue-500.
  • --cnvs-brand-primary-600 is intentionally not redefined — it stays the consumer’s brand hook.
  • The full neutral color scale is replaced (--cnvs-brand-neutral-*, all steps plus alphas), plus selected critical, caution, positive, shapes (sm, xs, xxl, xxxl), surfaces/overlays, and chart ramps.

Scoped Theming

The CanvasProvider theming updates in #4060  are for scoped use cases — embedding Canvas in another brand, multi-tenant sections, and popup parity when you cannot set data-theme="sana-canvas" on <html>. Application teams that control <html> and import the Sana variables globally do not need a theme prop for app-wide Sana.

For the scoped theming API, see our Theming documentation .

Default Scope Change for Legacy Themes

Important: If you previously used canvas.palette.<color>.main only (without other palette properties) to scope-theme your application, the default behavior has changed. In v16:

  • Before v16: Setting only palette.primary.main would automatically generate a full color ramp (lightest, lighter, light, dark, darkest, contrast) and apply broad system.color.brand.* forwarding.
  • In v16: Setting only palette.primary.main defaults to 'brand' scope, which applies a narrower set of variables (PrimaryButton and selected states only).

To restore the previous behavior, explicitly set themeScope: 'full':

// v15 behavior (implicit full scope) <CanvasProvider theme={{canvas: {palette: {primary: {main: '#FF00FF'}}}}} /> // v16 - to get the same behavior as v15 <CanvasProvider theme={{canvas: {palette: {primary: {main: '#FF00FF'}}}}} themeScope="full" />

This change provides more control over theming scope and prevents unintended overrides, but teams relying on the auto-generated color ramp need to explicitly opt into 'full' scope.

Simplified Sana Canvas Setup

If you can set data-theme="sana-canvas" on <html>, you no longer need to pass sanaCanvasProviderTheme to CanvasProvider. Popups are called out specifically because Canvas Kit renders them through React portals under document.body — outside the parent component’s DOM hierarchy — so they inherit CSS variables from <html>, not from a nested wrapper.

Before:

import {CanvasProvider, sanaCanvasProviderTheme} from '@workday/canvas-kit-react/common'; <CanvasProvider theme={sanaCanvasProviderTheme}> <App /> </CanvasProvider>

After (when you control <html>):

import {CanvasProvider} from '@workday/canvas-kit-react/common'; // <html data-theme="sana-canvas"> <CanvasProvider> <App /> </CanvasProvider>

Still required — cannot set data-theme on <html>: embedded apps, microfrontends, and third-party shells often cannot set attributes on the document root. Nested data-theme does not reach portaled popups (including all Canvas Kit popups such as menus, selects, modals, and toasts). Pass both data-theme="sana-canvas" (in-tree UI) and sanaCanvasProviderTheme (popup forwarding):

import {CanvasProvider, sanaCanvasProviderTheme} from '@workday/canvas-kit-react/common'; <CanvasProvider theme={sanaCanvasProviderTheme} data-theme="sana-canvas"> <App /> </CanvasProvider>

If you control <html> and see a console warning about unnecessary theme usage, you can safely remove the theme prop.

Table of Contents

Codemod

We’ve provided a codemod  to automatically update your code to work with most of the breaking changes in v16. Breaking changes handled by the codemod are marked with 🤖 in the Upgrade Guide.

A codemod is a script that makes programmatic transformations on your codebase by traversing the AST, identifying patterns, and making prescribed changes. This greatly decreases opportunities for error and reduces the number of manual updates, which allows you to focus on changes that need your attention. We highly recommend you use the codemod for these reasons.

If you’re new to running codemods or if it’s been a minute since you’ve used one, there are a few things you’ll want to keep in mind.

  • Our codemods are meant to be run sequentially. For example, if you’re using v14 of Canvas Kit, you’ll need to run the v15 codemod before you run v16.
  • The codemod will update your code to be compatible with the specified version, but it will not remove outdated dependencies or upgrade dependencies to the latest version. You’ll need to upgrade dependencies on your own.
    • We recommend upgrading dependencies before running the codemod.
    • Always review your package.json files to make sure your dependency versions look correct.
  • The codemod will not handle every breaking change in v16. You will likely need to make some manual changes to be compatible. Use our Upgrade Guide as a checklist.
  • Codemods are not bulletproof.
    • Conduct a thorough PR and QA review of all changes to ensure no regressions were introduced.
    • As a safety precaution, we recommend committing the changes from the codemod as a single isolated commit (separate from other changes) so you can roll back more easily if necessary.

We’re here to help! Automatic changes to your codebase can feel scary. You can always reach out to our team. We’d be very happy to walk you through the process to set you up for success.

Instructions

The easiest way to run our codemod is to use npx in your terminal.

npx @workday/canvas-kit-codemod v16 [path]

Be sure to provide specific directories that need to be updated via the [path] argument. This decreases the amount of AST the codemod needs to traverse and reduces the chances of the script having an error. For example, if your source code lives in src/, use src/ as your [path]. Or, if you have a monorepo with three packages using Canvas Kit, provide those specific packages as your [path].

Alternatively, if you’re unable to run the codemod successfully using npx, you can install the codemod package as a dev dependency, run it with yarn, and then remove the package after you’re finished.

yarn add @workday/canvas-kit-codemod --dev yarn canvas-kit-codemod v16 [path] yarn remove @workday/canvas-kit-codemod

Note: The codemod only works on .js, .jsx, .ts, and .tsx files. You’ll need to manually edit other file types (.json, .mdx, .md, etc.). You may need to run your linter after executing the codemod, as its resulting formatting (spacing, quotes, etc.) may not match your project conventions.

Codemod Transformations for v16

The following automated transformations are available for upgrading to v16:

  • Update Hyperlink Props: updateHyperlinkProps

Run the codemod with: npx @workday/canvas-kit-codemod v16 [path]

Codemod Transformations for Icons

Icon Migration Codemod

For v16, there is a separate codemod called icon-migration that updates @workday/canvas-system-icons-web usage across your codebase. Upgrade the icon package to v5 before you run it so deprecation and fallback mappings match what the transform expects.

yarn add @workday/canvas-system-icons-web@^5

The codemod reads system.deprecated.metadata.json from the installed icon package and rewrites each deprecated export to its fallback icon. This codemod will:

  • Update named imports and their references (for example, uploadIcon to arrowUpToLineIcon).
  • Preserve import aliases and point them at the fallback export.
  • Update namespace imports member access (for example, systemIcons.uploadIcon to systemIcons.arrowUpToLineIcon).
  • Resolve chained fallbacks when a fallback icon is itself deprecated.
  • Remove duplicate imports when the fallback is already imported in the same declaration.

Because it applies every mapping in the metadata file, a single run covers both the v4 naming convention migrations and the v5 Sana Canvas Assets renames and deprecations. Icons that are not deprecated are left unchanged.

To run the codemod:

npx @workday/canvas-kit-codemod icon-migration [path]

Tip: Provide the specific directory or directories you want to update as [path] to speed up migration.

This codemod only touches @workday/canvas-system-icons-web. For accent, applet, and expressive icons, use the v15-icons transform covered in the v15 upgrade guide. For details on what changed in v5 and the full list of deprecated icons, see the Sana Canvas Assets overview and the Icon Migration codemod guide.

Note: 🤖 The icon-migration codemod automates the majority of system icon migration work, but always review the PR for any remaining icons the codemod could not address, especially dynamic icon selection, non-code assets, and heavily customized usages.

New Components

KBD

PR: #4001 

The new KBD component allows you to display keyboard inputs in your UI using accessible, visually consistent keyboard key representations. This is useful for documenting keyboard shortcuts, instructional prompts, or any UI patterns that reference specific keys.

Highlights:

  • Provides a semantic (kbd element) way to visually present keyboard keys and shortcuts to users.
  • Supports various variants (default for prominence, plain for use on colored surfaces), as well as size options (small, medium, large).
  • Easy to use nested KBD.Item components for sequences or combinations.
  • RTL support.

Example:

import {KBD} from '@workday/canvas-kit-labs-react'; <KBD> <KBD.Item aria-label="Command">⌘</KBD.Item> <KBD.Item>C</KBD.Item> </KBD>;

For more details and advanced usage, including accessibility guidance for symbolic keys and functional shortcuts, refer to the storybook documentation and our example stories.

Tabs

PR: #4062 

A new Tabs component has been added to @workday/canvas-kit-preview-react, aligned with the Sana Canvas visual language. It supports filled and outlined variants.

import {Tabs} from '@workday/canvas-kit-preview-react/tabs'; <Tabs> <Tabs.List> <Tabs.Item>First Tab</Tabs.Item> <Tabs.Item>Second Tab</Tabs.Item> </Tabs.List> <Tabs.Panels> <Tabs.Panel>Contents of First Tab</Tabs.Panel> <Tabs.Panel>Contents of Second Tab</Tabs.Panel> </Tabs.Panels> </Tabs>;

MCP

Canvas Kit’s MCP server (@workday/canvas-kit-mcp) helps agents migrate icons and look up tokens for v16 / tokens 4.4.0.

Icon migration — get-canvas-kit-icon-migration

Use this tool when upgrading @workday/canvas-system-icons-web to v5 (Sana Canvas Assets), finding replacements for deprecated icons, or running the icon-migration codemod.

  1. Install / enable the Canvas Kit MCP server (see the MCP docs).
  2. Ask your agent to call get-canvas-kit-icon-migration.
  3. Follow the returned guides:
npx @workday/canvas-kit-codemod icon-migration [path]

Token references — get-canvas-kit-tokens

Token docs were updated for @workday/canvas-tokens-web 4.4.0. Call get-canvas-kit-tokens and start with the v4.4 token reference served by the MCP (docs://tokens/v4/v4.4-token-reference) for the current token surface (Sana Canvas, t-shirt scales, deprecations).

Component Updates

Action Bar

PR: #4048 

Visual Updates

  • ActionBar padding has been updated to padding.md (16px).

Buttons

PR: #4000 

All buttons have had the following updates:

  • fontWeight has been updated from system.fontWeight.bold to system.fontWeight.medium.
  • minWidth has been updated to the following:
Default SizeminWidth v15minWidth v16
extraSmallunchangedunchanged
small5rem4.5rem
medium6rem5.5rem
large7rem6.5rem
  • paddingInline has been updated to the following:
SizepaddingInline v15paddingInline v16
extraSmall0.75rem0.5rem
small1rem0.75rem
medium1.5rem1rem
large2rem1.25rem

Delete Button Outline Variant

We’ve added an outline variant to the DeleteButton component. This variant will reverse the button’s styling with a border and transparent background.

import {DeleteButton} from '@workday/canvas-kit-react/button'; <DeleteButton variant="outline">Delete</DeleteButton>;

Secondary Button

SecondaryButton now has a transparent backgroundColor and will not show on interactive states. The interactive states will now show on the border.

Container Components

PR: #4088 

Alt Variant Support

Several container components now support an alt variant that provides an elevated surface appearance:

  • Menu.Card - Can use variant="alt" for elevated menu styling
  • Dialog.Card - Can use variant="alt" for elevated dialog styling
  • Popup.Card - Can use variant="alt" for elevated popup styling
  • Toast - Can use variant="alt" for elevated toast styling
  • Tooltip - Can use variant="alt" for elevated tooltip styling

The alt variant uses system.sana.color.surface.elevated background with inverse borders, providing visual hierarchy and depth to these components when placed on an alt background.

// Examples of using the alt variant <Menu.Card variant="alt"> <Menu.List>...</Menu.List> </Menu.Card> <Dialog.Card variant="alt"> <Dialog.Heading>Dialog Title</Dialog.Heading> <Dialog.Body>Content</Dialog.Body> </Dialog.Card> <Toast variant="alt"> <Toast.Body> <Toast.Message>Notification message</Toast.Message> </Toast.Body> </Toast>

Card

PR: #4014 , #4088 

Variant Changes

The Card component’s variants have been updated:

  • Removed: The borderless variant has been removed and replaced with alt
  • New: The alt variant provides an elevated surface with inverse borders
  • Updated: The default variant now has a visible border
  • The tonal variant now uses a system.legacy.color.surface.alt.strong background.

Note: If you were previously using variant="borderless", you should use variant="alt" instead. While the styles are different, both where intended to be used on an alt background.

🤖 The codemod will automatically update variant="borderless" to variant="alt".

// v15 <Card variant="borderless"> <Card.Body>Content</Card.Body> </Card> // v16 <Card variant="alt"> <Card.Body>Content</Card.Body> </Card>
Visual Updates
  • Card.Heading now defaults to body.small type level (previously body.large). If your design requires a larger heading, you can override the typeLevel prop on Card.Heading.
  • The gap between Card.Heading and Card.Body has been reduced (previously gap.lg, now padding.sm).
  • Card’s border radius is now managed via the new cornerShapeStencil (see New Utilities).
  • The default variant now includes a visible border (system.color.border.default).
  • The alt variant uses an elevated surface color (system.sana.color.surface.elevated) with inverse borders.

Expandable

Visual Updates
  • Expandable.Target border radius changed from shape.xxl (24px, pill) to shape.lg (12px), now managed via cornerShapeStencil (see New Utilities).
  • Gap between Expandable.Target and Expandable.Content is now gap.sm (8px), set on the Expandable container (previously handled entirely via Expandable.Content’s top padding).
  • Expandable.Content padding is now uniform padding.xs (8px) on all sides (previously padding.md top / padding.xs sides and bottom).

Side Panel

PR: #4107 

Breaking Changes

The variant prop on SidePanel no longer accepts alternate. It has been renamed to overlay to describe what the variant is for — a panel that floats above page content — because a second non-overlay variant now occupies the “alternative surface” role.

import {SidePanel} from '@workday/canvas-kit-react/side-panel'; // Before <SidePanel variant="alternate">{/* ... */}</SidePanel> // After <SidePanel variant="overlay">{/* ... */}</SidePanel>

🤖 The v16 codemod will rename variant="alternate" to variant="overlay" as shown above. It only transforms SidePanel imported from @workday/canvas-kit-react; the deprecated SidePanel in @workday/canvas-kit-preview-react is left alone.

If you were using alternate for a panel that sits inline with page content rather than over it, switch to the new alternative variant instead of overlay to avoid picking up the elevation shadow.

New API
  • variant="alternative" — uses a raised surface (system.legacy.color.surface.raised) with no depth. Use it for panels that need to stand out from the page background while remaining part of the page layout.

The full set of variants is now:

VariantSurfaceDepth
standardsystem.legacy.color.surface.navigationNone
alternativesystem.legacy.color.surface.raisedNone
overlaysystem.legacy.color.surface.default6
Visual Updates
  • The overlay variant’s box-shadow updated from system.depth[3] to system.depth[6].
  • SidePanel.ToggleButton’s default icon changed from extendIcon to sidebarLeftIcon. The icon no longer rotates as the panel expands and collapses: it keeps a single orientation in LTR and is mirrored horizontally in RTL.
  • SidePanel.ToggleButton’s width changed from gap.lg (24px) to size.md (40px), and its top offset changed from gap.lg (24px) to 12px.
  • SidePanel.Heading padding changed from a symmetric padding.xs (8px) to padding.sm (12px) with a paddingInlineStart of padding.md (16px), and it now has a minHeight of size.xxl (64px) so the heading lines up with the toggle button and the collapsed panel width.
  • SidePanel.Heading now lays out its children with flexbox and centers them vertically, so an icon passed alongside the heading text aligns without extra styling.

Table

Visual Updates
  • BaseTable / Table outer border color updated from system.legacy.color.border.strong to system.legacy.color.border.default.
  • Table.Caption divider (borderBlockEnd) updated from system.legacy.color.border.strong to system.legacy.color.border.default.
  • Container corner radius is now 12px (shape.lg, previously shape.md / 8px), managed via cornerShapeStencil (see New Utilities).
  • Table.Header (and BaseTable.Header) cell padding is now uniform padding.md (16px) on all sides (previously asymmetric padding.xs vertically and padding.md horizontally).
  • Column header label text color is now system.color.fg.strong (previously inherited fg.default from the table container).

Indicators

Avatar

PR: #3981 

We’ve updated Avatar color variants to align with Sana Canvas. These changes are only visual — the component API is unchanged except for two new variant options.

Updated Variants

Existing variants use darker background colors and updated foreground colors for improved contrast:

VariantChange
blueBackground updated from blue300 to blue600; text now uses system.color.fg.inverse
amberBackground updated from amber200 to amber300; text updated from amber700 to amber800
purpleBackground updated from purple300 to purple600; text now uses system.color.fg.inverse

The teal variant is unchanged.

New Variants

Two new color variants are available:

  • magenta — magenta600 background with system.color.fg.inverse text
  • green — green600 background with system.color.fg.inverse text
import {Avatar} from '@workday/canvas-kit-react/avatar'; <Avatar name="Mary Jane" variant="magenta" /> <Avatar name="Green Arrow" variant="green" />

PR: #4087 

Visual Updates
  • Font weight changed from system.fontWeight.medium to system.fontWeight.bold.
  • Padding updated to paddingInline of padding.sm/padding.md (12px/16px) and paddingBlock of 10px (previously a symmetric padding.xs/padding.md, 8px/16px).
  • Border radius is now managed via the new cornerShapeStencil (see New Utilities), using shape.lg (12px) instead of individual corner-radius properties set to shape.sm (4px).
  • A box shadow (system.depth[5]) has been added to the container.
  • The focus ring now uses system.legacy.color.brand.border.primary for its outer color.
  • Hover background now uses colorSpace.hover() instead of colorSpace.darken(), making the hover direction theme-aware (lightens in the Sana Canvas theme, darkens in the default/legacy theme).
  • The default Banner.Icon size has been reduced from 24px to 20px.

Count Badge

PR: #4087 

Visual Updates
  • Typography now uses the system.legacy.type.subtext.md type preset (previously individually set fontFamily/fontSize/lineHeight/letterSpacing); fontWeight is now system.fontWeight.medium (previously system.fontWeight.bold).
  • Horizontal padding changed from a hardcoded 6.5px to system.legacy.padding.xxs (4px).
  • Default background color changed from system.legacy.color.accent.danger to system.legacy.color.brand.accent.primary. This is a visual color change — the default badge is no longer red/danger-colored and now uses the brand primary accent color.
  • The inverse variant’s text color changed from system.legacy.color.fg.info.strong to system.color.fg.strong.

Information Highlight

PR: #4087 

Breaking Changes
  • The default variant changed from informational to default. If you rely on the implicit default, add variant="informational" explicitly to preserve the previous appearance.
New API
  • variant="default" — general-purpose variant with gray background (surface.alt.default), flat layersIcon (no colored circle), and icon color system.color.fg.default.
  • actionPlacement="bottom" | "end" — bottom (default) stacks the link below heading/body; end visually places the link at the inline end beside content when the container is wide enough, and stacks it below the body when narrower. This only affects visual/grid placement. DOM order, keyboard tab order, and screen reader order remain unchanged (heading, body, then link).
Visual Updates
  • The left accent border (borderInlineStart) has been removed.
  • Border radius changed from shape.sm (4px) to shape.xxl (20px) via cornerShapeStencil (see New Utilities).
  • Icon-to-content horizontal gap changed from gap.md (16px) to 12px.
  • Vertical spacing between text blocks: 4px between heading and body (gap.xs), 16px between body and link (gap.md).
  • The default InformationHighlight.Icon size has been reduced from 24px to 20px (systemIcon size md).
  • Heading typography now uses system.legacy.type.subtext.lg with fontWeight.medium (previously body.sm scale with fontWeight.bold). Color changed from fg.default to fg.strong.
  • Body typography consolidated to system.legacy.type.subtext.lg spread. Color changed from fg.default to fg.muted.default.
  • Link typography consolidated to system.legacy.type.subtext.lg spread; fontWeight.bold override removed (weight is now regular).
  • Link now uses the newly introduced secondary Hyperlink variant (see Hyperlink).
  • Background surface tokens per variant/emphasis are unchanged.

Pill

PR: #4087 

Visual Updates
  • Typography now uses the system.legacy.type.subtext.md type preset (previously individually set fontFamily/fontSize/lineHeight/letterSpacing/fontWeight).
  • Background color tier changed from surface.alt.strong to surface.alt.default (a lighter background) for the base, disabled state, and the disabled Pill.Count background. Hover/active states now mix surface.overlay.mixin into surface.alt.default at 3.92% (hover) and 7.45% (active) via colorSpace.darken(). In Sana, surface.overlay.mixin resolves to neutral black; mix percentages were recalibrated from the v15 defaults (8%/18%) for the lighter surface.alt.default base.
  • Active state label color changed from fg.strong to fg.stronger, now matching the hover state.
  • The readOnly variant now explicitly sets its label color to fg.default (previously inherited fg.strong).
  • The disabled state now dims the whole container via system.opacity.disabled instead of using a distinct label color, matching the convention used by PrimaryButton/DeleteButton.
  • Pill.IconButton’s default icon color changed from fg.strong to fg.default, and its disabled icon color changed from fg.disabled to fg.strong, matching the “dim via container opacity” convention above.
  • Pill.IconButton’s close icon hit box shrunk from size.xxs (20px) to size.xxxs (16px), and the icon glyph itself shrunk from systemIcon.size.sm (18px) to systemIcon.size.xs (16px) to fill the smaller hit box exactly.
  • Pill.IconButton’s border radius is now driven by buttonStencil.vars.borderRadius set to shape.sm, matching the Pill container’s own corner radius (previously a hardcoded 2px).
  • Pill.IconButton’s focus indicator is no longer a separate focusRing() box-shadow. It’s now a border drawn on the button’s existing larger (invisible) hit-target overlay, so the ring appears flush with the ~24px click target instead of hugging the smaller 16px icon box. It uses system.legacy.color.brand.border.primary, matching the Pill container’s own focus color.
  • Pill.Icon’s glyph size shrunk from systemIcon.size.sm (18px) to systemIcon.size.xs (16px).

Skeleton

Visual Updates
  • Default fill on Skeleton.Header, Skeleton.Text, and Skeleton.Shape changed from flat surface.loading to a two-stop shimmer gradient (surface.alt.strong → surface.loading, to left).
  • Skeleton.Header border radius changed from shape.md (8px) to shape.sm (6px).
  • Skeleton.Text line placeholder border radius changed from shape.md (8px) to shape.xxxl (28px), producing fully pill-shaped lines at the default 16px line height.
  • Skeleton.Shape border radius remains consumer-configurable via the borderRadius prop or cs (default 0); only the default fill changed.

The backgroundColor prop still accepts solid color overrides (for example via cs). The default stencil uses the background CSS property for the gradient instead of backgroundColor.

  • Placeholder shapes use a 1px transparent border so boundaries remain visible in Windows High Contrast when gradient fills are suppressed (border-color: CanvasText under forced-colors).

Status Indicator

PR: #4087 

Visual Updates
  • Container height increased from size.xxs (20px) to size.xs (24px).
  • Border radius changed from shape.sm (4px) to shape.md (8px), now managed via cornerShapeStencil (see New Utilities).
  • Padding changed from asymmetric 0/padding.xs (0px/8px) to padding.xxs/padding.xs (4px/8px).
  • The default icon size has been reduced from 20px to 16px.
  • Label typography now uses the subtext.medium type level (12px, previously subtext.large, 14px) with fontWeight.medium (previously fontWeight.bold).

Inputs

Accessibility: Checked-State Contrast

Important: The checked states of Checkbox, Radio, and Switch now derive their background/fill color from system.color.brand.accent.positive (which maps to your theme’s brand.success.base), while the foreground (the check icon, radio dot, and switch thumb) remains an inverse/white color. This pairing relies on brand.success.base being a sufficiently dark color.

This change is safe for the default Canvas themes, where brand.success.base is dark enough to maintain adequate contrast against the white foreground. However, consumer themes that override brand.success.base with a light color may produce insufficient contrast between the checked background and the white check icon, radio dot, or switch thumb. This can cause the checked state to fail WCAG 1.4.11 Non-text Contrast (3:1) .

If you customize brand.success.base in your theme, please verify the following:

  • Maintain a minimum 3:1 contrast ratio between your brand.success.main value and the white (#ffffff) foreground used for the check icon, radio dot, and switch thumb.
  • Prefer a brand.success.base value that is dark enough to pair with a light foreground. If your brand requires a light success color, you may need to override the component styles so the foreground uses a darker, paired contrast color instead of white.

Checkbox

PR: #3984 

Visual Updates
  • The default Checkbox size is now 20px (previously 24px).
  • Checked state uses system.color.brand.accent.positive instead of system.color.brand.accent.primary for the background.
  • Error and alert states no longer display a status background color; only the standard background is shown.
  • The hover ring is smaller to better align with the smaller size. The hover ring background color uses neutral instead of slate in sana-canvas theme.

Color Input

PR: #3992 

Visual Updates
  • Shape is now 12px (previously 8px).

Color Picker

PR: #3992 

Visual Updates
  • Sana Canvas: swatch shape is now 6px (previously 4px).

Form Field

PR: #3992 

Visual Updates
  • Shape for grouped inputs (Radio groups and Checkbox groups) is now 12px (previously 8px). As in v15, this shape is primarily visible for error and caution states.
  • Error and caution states no longer display a status background color; only the standard background is shown.

Radio

PR: #3984 

Visual Updates
  • The default Radio size is now 20px (previously 24px).
  • Checked state uses system.color.brand.accent.positive instead of system.color.brand.accent.primary for the background.
  • Error and alert states no longer display a status background color; only the standard background is shown.
  • The hover ring is smaller to better align with the smaller size. The hover ring background color uses a neutral color instead of slate in sana-canvas theme.

Switch

PR: #3984 

Visual Updates
  • The Switch thumb is now 16px (was 14px). The icon-thumb container width remains unchanged.
  • The checked state background now uses system.color.brand.accent.positive instead of system.color.brand.accent.primary.
  • The default (unchecked) background color now uses system.color.surface.muted.default instead of system.color.surface.muted.soft. Background color uses a neutral color instead of slate in sana-canvas theme.

Text Area

PR: #3992 

Visual Updates
  • Shape is now 12px (previously 8px).
  • Error and caution states no longer display a status background color; only the standard background is shown.

Text Input

PR: #3992 

Visual Updates
  • Shape is now 12px (previously 8px).
  • Error and caution states no longer display a status background color; only the standard background is shown.

Select

PR: #4085 

Visual Updates
  • The dropdown indicator icon now uses chevronDownSmallIcon and chevronUpSmallIcon instead of caretDownSmallIcon. The icon toggles direction when the menu is open.
  • Select.Input inherits the TextInput shape update (12px, previously 8px) and menu styling updates from Menu.
  • Gap between the input and dropdown menu updated from 4px to 8px via Menu.Popper offset.

No API changes. No migration is required unless you render a custom dropdown indicator or override selectInputStencil caret styles.

MultiSelect

PR: #4085 

Visual Updates
  • Shape is now 12px (shape.lg, previously shape.md / 8px) via cornerShapeStencil.
  • The dropdown indicator icon now uses chevronDownSmallIcon and chevronUpSmallIcon instead of caretDownSmallIcon. The icon toggles direction when the menu is open.
  • MultiSelect menus inherit the Menu styling updates.
  • Gap between the input and dropdown menu updated from 4px to 8px via Menu.Popper offset.

No API changes. No migration is required unless you render a custom dropdown indicator or override multiSelectInputStencil styles.

Popups

PR: #3987 

Visual Updates

Menu.Card

  • Text color updated from system.color.fg.default to system.color.fg.strong.
  • Padding updated from padding.xxs (4px) to padding.xs (8px).
  • Border radius is now managed via cornerShapeStencil (still uses shape.xxl).

Menu.Popper

  • Offset between the trigger and dropdown menu updated from 4px to 8px. This affects Select, MultiSelect, Combobox, and other components that use Menu.Popper.

Menu.List

  • Gap between items updated from gap.xs (4px) to base.legacy.size25 (2px).

Menu.Item

  • Icon-to-label gap updated from gap.md (16px) to gap.sm (8px).
  • Padding updated from symmetric padding.sm/padding.md (12px/16px) to asymmetric padding.xs/padding.xs/padding.xs/padding.sm (8px top/right/bottom, 12px left).
  • Item border radius updated from shape.xxl (24px) to shape.lg (12px).
  • Text color updated from system.color.fg.default to system.color.fg.strong.
  • Icon size updated to md (20px / 1.25rem).

Menu icons are now smaller to align with Sana Canvas specs. In v15, Menu.Item.Icon did not set a size prop and fell back to the SystemIcon default of lg (24px / 1.5rem). In v16, all icons rendered through Menu.Item.Icon and Menu.Option.Icon use size="md" (20px / 1.25rem).

The selected checkmark in Menu.Option now uses checkIcon at size="md" instead of checkSmallIcon at the previous 24px default. The nested submenu chevron in Submenu.TargetItem also changed from chevronRightSmallIcon to chevronRightIcon at size="md".

Passing a custom size to Menu.Item.Icon or Menu.Option.Icon will still override the default. Select and Combobox menus inherit the Menu.Option checkmark change. If you are rendering custom chevron or checkmark icons inside of menu items, update them to size="md" and swap *SmallIcon assets for their standard equivalents where appropriate.

Menu.Group.Heading

  • Padding updated to use paddingBlock: padding.xs with paddingInline: padding.sm / padding.md.
State Style Updates

Menu.Item and Menu.Option share the same underlying item styles via menuItemStencil. For guidance on when to use Menu.Item vs Menu.Option, including accessibility requirements for selectable menus, see the Menu accessibility documentation .

Statev15v16
Active/pressedNot styledsurface.overlay.pressed background
FocusSolid blue fill (brand.accent.primary) and white textInset outline using brand.border.primary
Disabled + Focus (aria-disabled="true")Solid blue fill and white text with disabled opacity (dims focus style)Light blue background and blue text (brand.surface.selected / brand.fg.selected) with disabled opacity (dims focus indicator)
Expanded submenu parent + FocusSame blue fill and white text focus style as other focused itemsFocus ring removed; ancestor-trail styling shown instead (Submenu.TargetItem)

Used in Select, Combobox, and other listbox menus. Selected states apply when aria-selected="true".

Statev15v16
Active/pressedNot styledsurface.overlay.pressed background
FocusSolid blue fill (brand.accent.primary) and white textInset outline using brand.border.primary
Selected (aria-selected="true")Solid blue fill and white text (brand.surface.primary.strong / brand.fg.primary.strong)Light blue background and blue text (brand.surface.selected / brand.fg.selected)
Selected + FocusSolid blue fill and white textInset outline with selected background preserved
Disabled + Focus (aria-disabled="true")Solid blue fill and white text with disabled opacity (dims focus style)Light blue background and blue text (brand.surface.selected / brand.fg.selected) with disabled opacity (dims focus indicator)
Disabled + SelectedSolid blue fill and white text with disabled opacityLight blue background and blue text (brand.surface.selected / brand.fg.selected) with disabled opacity
Visual Updates
  • Modal.Card box-shadow updated from system.depth[5] to system.depth[6].
Visual Updates
  • Popup.Card box-shadow updated from system.depth[3] to system.depth[4].

Toast

PR: #4053 

Visual Updates
  • Padding is now applied to the Toast container itself instead of being distributed across Toast.Body and Toast.Icon.
  • Gap between the icon, body, and close icon is now base.size150 (12px).
  • Shape uses system.shape.xl.
  • Box-shadow updated from system.depth[3] to system.depth[5].
  • Toast.Body no longer has a gap between text and link.
  • Toast.CloseIcon is set to have absolute positioning and aligned with the top-right corner (in LTR).
  • Toast.CloseIcon was changed to be extraSmall tertiary button.

Tooltip

PR: #4053 

Visual Updates
  • The default tooltip has been restyled from a dark surface to a light surface.
  • A visible border (system.color.border.default) is now applied to the container, replacing the previous transparent outline.
  • Tooltip boundaries remain visible in Windows High Contrast mode: when High Contrast is detected, Tooltip uses a solid outline (outline: solid 1px CanvasText) to ensure clear visibility.
  • Shape is now fully rounded using system.shape.full.
  • Box-shadow updated from system.depth[2] to system.depth[3].
  • Type level is now subtext.lg (previously subtext.md).
  • Padding and spacing have been adjusted to accommodate the new styling.
Visual Updates
  • Default link color uses new link tokens (system.color.fg.link.default / .hover). Hover and active text is now the darker link-hover blue.
  • Hover and active backgrounds use surface overlay tokens instead of surface.alt.
New Secondary Variant and linkType Prop

A new “secondary” variant and linkType prop have been added to Hyperlink and ExternalHyperlink. Color and underline are now controlled by separate props:

NeedAPI
Blue, underlined (default)(no props)
Blue, no underlinelinkType="standalone"
Inverse, underlinedvariant="inverse"
Inverse, no underlinevariant="inverse" linkType="standalone"
Dark gray, underlinedvariant="secondary"
Dark gray, no underlinevariant="secondary" linkType="standalone"

variant is now either ‘inverse’ or ‘secondary’. Underline is controlled by linkType which is either ‘inline’ or ‘standalone’, defaulting to ‘inline’. The compound variants standalone and standaloneInverse are removed.

import {ExternalHyperlink, Hyperlink} from '@workday/canvas-kit-react/button'; <Hyperlink variant="secondary" href="#secondary-hyperlink"> Secondary Hyperlink </Hyperlink> <Hyperlink linkType="standalone" href="#standalone-hyperlink"> Standalone Hyperlink </Hyperlink> <ExternalHyperlink variant="secondary" linkType="standalone" href="#secondary-standalone-external-hyperlink" iconLabel="Opens new window" > Secondary Standalone External Hyperlink </ExternalHyperlink>
Breaking Change Migration

Run the Canvas Kit v16 codemod (updateHyperlinkProps) to migrate automatically:

BeforeAfter
variant="standalone"linkType="standalone"
variant="standaloneInverse"variant="inverse" linkType="standalone"
Visual Updates
  • Trail links (Breadcrumbs.Link) use Hyperlink variant="secondary" and linkType="standalone". Trail text uses system.color.fg.default; hover and active states use surface overlay tokens with underline on hover.
  • The current item (Breadcrumbs.CurrentItem) uses system.color.fg.muted.default.
  • Overflow and separator chevrons use muted/strong foreground colors with component.legacy.systemIcon.size.md.

Pagination

Visual Updates
  • Page buttons use overlay hover/pressed backgrounds and a focus ring (aligned with the TertiaryButton pattern). The current page uses system.color.brand.accent.primary with inverse label text.
  • Control chevrons (previous/next and jump controls) are sized to 16px via component.legacy.systemIcon.size.xs.
  • GoTo label uses size="large" (14px). GoTo input text is centered; border radius inherits from TextInput shape.lg (12px).
  • GoToForm spacing uses marginInlineStart: gap.xs alongside Controls gap.xs.

Segmented Control

PR: #4048 

Visual Updates

  • Container background changed from surface.alt.strong to surface.alt.default (a lighter background color).
  • Border radius changed to shape.full (full rounded corners) on the container.
  • Grid gap reduced from gap.sm (8px) to gap.xs (4px).
  • Selected/pressed item now uses boxShadow: system.depth[2] instead of a visible border.
  • Font weight changed from bold to medium (base and small sizes);
  • Medium size now uses semi-bold font weight.
  • Size adjustments: large height shrunk to size.sm (32px) (was size.md (40px));
  • Medium size is now 28px in height (was 32px).
  • Hover and pressed states now use surface.overlay.hover.default and surface.overlay.pressed.default respectively for the item.
  • Container padding changed to padding.md (16px) including for mobile devices.
  • On mobile devices, the container padding remains padding.md (16px).

New Utilities

cornerShapeStencil

We’ve added a new stencil called cornerShapeStencil to @workday/canvas-kit-react/common. It applies the CSS corner-shape property as a progressive enhancement for components using border radius that aren’t circular. Browsers that don’t support corner-shape fall back to border-radius alone, as border-radius is declared first in the cascade.

The stencil exposes a shape variable for setting the border radius. Extend it in your own stencils to get consistent rounded corners:

import {cornerShapeStencil} from '@workday/canvas-kit-react/common'; import {createStencil} from '@workday/canvas-kit-styling'; import {system} from '@workday/canvas-tokens-web'; export const myStencil = createStencil({ extends: cornerShapeStencil, base: { [cornerShapeStencil.vars.shape]: system.legacy.shape.xxl, }, });

Card, Expandable (Expandable.Target), Menu (Menu.Card, Menu.List, Menu.Item), MultiSelect, and Table (BaseTable, Table) extend this stencil for their rounded corners.

Fonts

Canvas Kit does not bundle fonts. In v16, which font you load depends on whether you opt into the Sana Canvas theme.

ThemeTypefacePackage
Sana Canvas (data-theme="sana-canvas")Sana Sans (+ IBM Plex Mono)@workday/canvas-kit-react/fonts
Classic Canvas (default)Roboto (+ Roboto Mono)@workday/canvas-kit-react-fonts

Sana Canvas Theme — Switch to Sana Sans

When you opt into Sana, --cnvs-sys-font-family-default (system.fontFamily.default) points at 'Sana Sans VF'. Load the matching @font-face rules from the new fonts module (requires @workday/canvas-tokens-web 4.4.0 or later):

import {fonts} from '@workday/canvas-kit-react/fonts'; import {injectGlobal} from '@workday/canvas-kit-styling'; import {system} from '@workday/canvas-tokens-web'; injectGlobal({ ...fonts, 'html, body': { fontFamily: system.fontFamily.default, margin: 0, minHeight: '100vh', }, });

If your environment already provides Sana Sans, you can skip injecting @font-face and keep using system.fontFamily.default.

Note: Prefer @workday/canvas-kit-react/fonts over @workday/canvas-kit-react-fonts when the Sana Canvas theme is enabled. The older package still loads Roboto and will not match the Sana font-family token.

Classic Canvas — Keep Roboto

If you are not setting data-theme="sana-canvas", continue using @workday/canvas-kit-react-fonts as before:

import {fonts} from '@workday/canvas-kit-react-fonts'; import {injectGlobal} from '@workday/canvas-kit-styling'; import {system} from '@workday/canvas-tokens-web'; injectGlobal({ ...fonts, 'html, body': { fontFamily: system.fontFamily.default, }, });

For full installation details, variable-font notes, and monospace coverage, see the Fonts guide .

Deprecations

We add the @deprecated  JSDoc tag to code we plan to remove in a future major release. This signals consumers to migrate to a more stable alternative before the deprecated code is removed.

Glossary

For an overview of the different packages we provide, please view our docs here .

Main

Components in the Main package are stable and ready for production use.

Preview

Components in the Preview package are mostly stable but may still receive breaking changes before being promoted to Main.

Labs

Components in the Labs package are experimental and may receive significant changes or be removed entirely.