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@^16Sana 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-primaryand-accent-actionre-point from blue to--cnvs-brand-neutral-975, and-brand-fg-primary-default/-strongto--cnvs-brand-neutral-a900/-a950.PrimaryButton, brand links, and selected states are what visibly change. - Focus does not.
--cnvs-sys-color-brand-focus-primaryand-border-primarystayblue-500. --cnvs-brand-primary-600is 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 selectedcritical,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.mainwould automatically generate a full color ramp (lightest, lighter, light, dark, darkest, contrast) and apply broadsystem.color.brand.*forwarding. - In v16: Setting only
palette.primary.maindefaults 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
- Sana Canvas Theme
- Codemod
- MCP
- New Components
- Component Updates
- New Utilities
- Fonts
- Deprecations
- Glossary
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.jsonfiles 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-codemodNote: The codemod only works on
.js,.jsx,.ts, and.tsxfiles. 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@^5The 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,
uploadIcontoarrowUpToLineIcon). - Preserve import aliases and point them at the fallback export.
- Update namespace imports member access (for example,
systemIcons.uploadIcontosystemIcons.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 thev15-iconstransform 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-migrationcodemod 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 (
kbdelement) way to visually present keyboard keys and shortcuts to users. - Supports various variants (
defaultfor prominence,plainfor use on colored surfaces), as well as size options (small,medium,large). - Easy to use nested
KBD.Itemcomponents 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.
- Install / enable the Canvas Kit MCP server (see the MCP docs).
- Ask your agent to call
get-canvas-kit-icon-migration. - Follow the returned guides:
- Sana Canvas Assets overview (full deprecated → replacement table)
- Icon Migration codemod
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
ActionBarpadding has been updated topadding.md(16px).
Buttons
PR: #4000
All buttons have had the following updates:
fontWeighthas been updated fromsystem.fontWeight.boldtosystem.fontWeight.medium.minWidthhas been updated to the following:
| Default Size | minWidth v15 | minWidth v16 |
|---|---|---|
| extraSmall | unchanged | unchanged |
| small | 5rem | 4.5rem |
| medium | 6rem | 5.5rem |
| large | 7rem | 6.5rem |
paddingInlinehas been updated to the following:
| Size | paddingInline v15 | paddingInline v16 |
|---|---|---|
| extraSmall | 0.75rem | 0.5rem |
| small | 1rem | 0.75rem |
| medium | 1.5rem | 1rem |
| large | 2rem | 1.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 usevariant="alt"for elevated menu stylingDialog.Card- Can usevariant="alt"for elevated dialog stylingPopup.Card- Can usevariant="alt"for elevated popup stylingToast- Can usevariant="alt"for elevated toast stylingTooltip- Can usevariant="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
Variant Changes
The Card component’s variants have been updated:
- Removed: The
borderlessvariant has been removed and replaced withalt - New: The
altvariant provides an elevated surface with inverse borders - Updated: The default variant now has a visible border
- The
tonalvariant now uses asystem.legacy.color.surface.alt.strongbackground.
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.Headingnow defaults tobody.smalltype level (previouslybody.large). If your design requires a larger heading, you can override thetypeLevelprop onCard.Heading.- The gap between
Card.HeadingandCard.Bodyhas been reduced (previouslygap.lg, nowpadding.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
altvariant uses an elevated surface color (system.sana.color.surface.elevated) with inverse borders.
Expandable
Visual Updates
Expandable.Targetborder radius changed fromshape.xxl(24px, pill) toshape.lg(12px), now managed viacornerShapeStencil(see New Utilities).- Gap between
Expandable.TargetandExpandable.Contentis nowgap.sm(8px), set on theExpandablecontainer (previously handled entirely viaExpandable.Content’s top padding). Expandable.Contentpadding is now uniformpadding.xs(8px) on all sides (previouslypadding.mdtop /padding.xssides 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
v16codemod will renamevariant="alternate"tovariant="overlay"as shown above. It only transformsSidePanelimported from@workday/canvas-kit-react; the deprecatedSidePanelin@workday/canvas-kit-preview-reactis 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:
| Variant | Surface | Depth |
|---|---|---|
standard | system.legacy.color.surface.navigation | None |
alternative | system.legacy.color.surface.raised | None |
overlay | system.legacy.color.surface.default | 6 |
Visual Updates
- The
overlayvariant’s box-shadow updated fromsystem.depth[3]tosystem.depth[6]. SidePanel.ToggleButton’s default icon changed fromextendIcontosidebarLeftIcon. 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 fromgap.lg(24px) tosize.md(40px), and its top offset changed fromgap.lg(24px) to12px.SidePanel.Headingpadding changed from a symmetricpadding.xs(8px) topadding.sm(12px) with apaddingInlineStartofpadding.md(16px), and it now has aminHeightofsize.xxl(64px) so the heading lines up with the toggle button and the collapsed panel width.SidePanel.Headingnow 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/Tableouter border color updated fromsystem.legacy.color.border.strongtosystem.legacy.color.border.default.Table.Captiondivider (borderBlockEnd) updated fromsystem.legacy.color.border.strongtosystem.legacy.color.border.default.- Container corner radius is now 12px (
shape.lg, previouslyshape.md/ 8px), managed viacornerShapeStencil(see New Utilities). Table.Header(andBaseTable.Header) cell padding is now uniformpadding.md(16px) on all sides (previously asymmetricpadding.xsvertically andpadding.mdhorizontally).- Column header label text color is now
system.color.fg.strong(previously inheritedfg.defaultfrom 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:
| Variant | Change |
|---|---|
blue | Background updated from blue300 to blue600; text now uses system.color.fg.inverse |
amber | Background updated from amber200 to amber300; text updated from amber700 to amber800 |
purple | Background 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—magenta600background withsystem.color.fg.inversetextgreen—green600background withsystem.color.fg.inversetext
import {Avatar} from '@workday/canvas-kit-react/avatar';
<Avatar name="Mary Jane" variant="magenta" />
<Avatar name="Green Arrow" variant="green" />Banner
PR: #4087
Visual Updates
- Font weight changed from
system.fontWeight.mediumtosystem.fontWeight.bold. - Padding updated to
paddingInlineofpadding.sm/padding.md(12px/16px) andpaddingBlockof10px(previously a symmetricpadding.xs/padding.md, 8px/16px). - Border radius is now managed via the new
cornerShapeStencil(see New Utilities), usingshape.lg(12px) instead of individual corner-radius properties set toshape.sm(4px). - A box shadow (
system.depth[5]) has been added to the container. - The focus ring now uses
system.legacy.color.brand.border.primaryfor its outer color. - Hover background now uses
colorSpace.hover()instead ofcolorSpace.darken(), making the hover direction theme-aware (lightens in the Sana Canvas theme, darkens in the default/legacy theme). - The default
Banner.Iconsize has been reduced from24pxto20px.
Count Badge
PR: #4087
Visual Updates
- Typography now uses the
system.legacy.type.subtext.mdtype preset (previously individually setfontFamily/fontSize/lineHeight/letterSpacing);fontWeightis nowsystem.fontWeight.medium(previouslysystem.fontWeight.bold). - Horizontal padding changed from a hardcoded
6.5pxtosystem.legacy.padding.xxs(4px). - Default background color changed from
system.legacy.color.accent.dangertosystem.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
inversevariant’s text color changed fromsystem.legacy.color.fg.info.strongtosystem.color.fg.strong.
Information Highlight
PR: #4087
Breaking Changes
- The default
variantchanged frominformationaltodefault. If you rely on the implicit default, addvariant="informational"explicitly to preserve the previous appearance.
New API
variant="default"— general-purpose variant with gray background (surface.alt.default), flatlayersIcon(no colored circle), and icon colorsystem.color.fg.default.actionPlacement="bottom" | "end"—bottom(default) stacks the link below heading/body;endvisually 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) toshape.xxl(20px) viacornerShapeStencil(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.Iconsize has been reduced from24pxto20px(systemIconsizemd). Headingtypography now usessystem.legacy.type.subtext.lgwithfontWeight.medium(previouslybody.smscale withfontWeight.bold). Color changed fromfg.defaulttofg.strong.Bodytypography consolidated tosystem.legacy.type.subtext.lgspread. Color changed fromfg.defaulttofg.muted.default.Linktypography consolidated tosystem.legacy.type.subtext.lgspread;fontWeight.boldoverride removed (weight is now regular).Linknow uses the newly introducedsecondaryHyperlink variant (see Hyperlink).- Background surface tokens per variant/emphasis are unchanged.
Pill
PR: #4087
Visual Updates
- Typography now uses the
system.legacy.type.subtext.mdtype preset (previously individually setfontFamily/fontSize/lineHeight/letterSpacing/fontWeight). - Background color tier changed from
surface.alt.strongtosurface.alt.default(a lighter background) for the base, disabled state, and the disabledPill.Countbackground. Hover/active states now mixsurface.overlay.mixinintosurface.alt.defaultat 3.92% (hover) and 7.45% (active) viacolorSpace.darken(). In Sana,surface.overlay.mixinresolves to neutral black; mix percentages were recalibrated from the v15 defaults (8%/18%) for the lightersurface.alt.defaultbase. - Active state label color changed from
fg.strongtofg.stronger, now matching the hover state. - The
readOnlyvariant now explicitly sets its label color tofg.default(previously inheritedfg.strong). - The disabled state now dims the whole container via
system.opacity.disabledinstead of using a distinct label color, matching the convention used byPrimaryButton/DeleteButton. Pill.IconButton’s default icon color changed fromfg.strongtofg.default, and its disabled icon color changed fromfg.disabledtofg.strong, matching the “dim via container opacity” convention above.Pill.IconButton’s close icon hit box shrunk fromsize.xxs(20px) tosize.xxxs(16px), and the icon glyph itself shrunk fromsystemIcon.size.sm(18px) tosystemIcon.size.xs(16px) to fill the smaller hit box exactly.Pill.IconButton’s border radius is now driven bybuttonStencil.vars.borderRadiusset toshape.sm, matching the Pill container’s own corner radius (previously a hardcoded2px).Pill.IconButton’s focus indicator is no longer a separatefocusRing()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 usessystem.legacy.color.brand.border.primary, matching the Pill container’s own focus color.Pill.Icon’s glyph size shrunk fromsystemIcon.size.sm(18px) tosystemIcon.size.xs(16px).
Skeleton
Visual Updates
- Default fill on
Skeleton.Header,Skeleton.Text, andSkeleton.Shapechanged from flatsurface.loadingto a two-stop shimmer gradient (surface.alt.strong→surface.loading,to left). Skeleton.Headerborder radius changed fromshape.md(8px) toshape.sm(6px).Skeleton.Textline placeholder border radius changed fromshape.md(8px) toshape.xxxl(28px), producing fully pill-shaped lines at the default 16px line height.Skeleton.Shapeborder radius remains consumer-configurable via theborderRadiusprop orcs(default0); 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
1pxtransparent border so boundaries remain visible in Windows High Contrast when gradient fills are suppressed (border-color: CanvasTextunderforced-colors).
Status Indicator
PR: #4087
Visual Updates
- Container height increased from
size.xxs(20px) tosize.xs(24px). - Border radius changed from
shape.sm(4px) toshape.md(8px), now managed viacornerShapeStencil(see New Utilities). - Padding changed from asymmetric
0/padding.xs(0px/8px) topadding.xxs/padding.xs(4px/8px). - The default icon size has been reduced from
20pxto16px. - Label typography now uses the
subtext.mediumtype level (12px, previouslysubtext.large, 14px) withfontWeight.medium(previouslyfontWeight.bold).
Inputs
Accessibility: Checked-State Contrast
Important: The checked states of
Checkbox,Radio, andSwitchnow derive their background/fill color fromsystem.color.brand.accent.positive(which maps to your theme’sbrand.success.base), while the foreground (the check icon, radio dot, and switch thumb) remains an inverse/white color. This pairing relies onbrand.success.basebeing 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.mainvalue and the white (#ffffff) foreground used for the check icon, radio dot, and switch thumb. - Prefer a
brand.success.basevalue 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.positiveinstead ofsystem.color.brand.accent.primaryfor 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-canvastheme.
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.positiveinstead ofsystem.color.brand.accent.primaryfor 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-canvastheme.
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.positiveinstead ofsystem.color.brand.accent.primary. - The default (unchecked) background color now uses
system.color.surface.muted.defaultinstead ofsystem.color.surface.muted.soft. Background color uses a neutral color instead of slate insana-canvastheme.
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
chevronDownSmallIconandchevronUpSmallIconinstead ofcaretDownSmallIcon. The icon toggles direction when the menu is open. Select.Inputinherits theTextInputshape update (12px, previously 8px) and menu styling updates from Menu.- Gap between the input and dropdown menu updated from
4pxto8pxviaMenu.Popperoffset.
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, previouslyshape.md/ 8px) viacornerShapeStencil. - The dropdown indicator icon now uses
chevronDownSmallIconandchevronUpSmallIconinstead ofcaretDownSmallIcon. The icon toggles direction when the menu is open. MultiSelectmenus inherit the Menu styling updates.- Gap between the input and dropdown menu updated from
4pxto8pxviaMenu.Popperoffset.
No API changes. No migration is required unless you render a custom dropdown indicator or override
multiSelectInputStencil styles.
Popups
Menu
PR: #3987
Visual Updates
Menu.Card
- Text color updated from
system.color.fg.defaulttosystem.color.fg.strong. - Padding updated from
padding.xxs(4px) topadding.xs(8px). - Border radius is now managed via
cornerShapeStencil(still usesshape.xxl).
Menu.Popper
- Offset between the trigger and dropdown menu updated from
4pxto8px. This affectsSelect,MultiSelect,Combobox, and other components that useMenu.Popper.
Menu.List
- Gap between items updated from
gap.xs(4px) tobase.legacy.size25(2px).
Menu.Item
- Icon-to-label gap updated from
gap.md(16px) togap.sm(8px). - Padding updated from symmetric
padding.sm/padding.md(12px/16px) to asymmetricpadding.xs/padding.xs/padding.xs/padding.sm(8px top/right/bottom, 12px left). - Item border radius updated from
shape.xxl(24px) toshape.lg(12px). - Text color updated from
system.color.fg.defaulttosystem.color.fg.strong. - Icon size updated to
md(20px / 1.25rem).
Menu icon sizing
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.xswithpaddingInline: 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 .
Menu.Item
| State | v15 | v16 |
|---|---|---|
| Active/pressed | Not styled | surface.overlay.pressed background |
| Focus | Solid blue fill (brand.accent.primary) and white text | Inset 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 + Focus | Same blue fill and white text focus style as other focused items | Focus ring removed; ancestor-trail styling shown instead (Submenu.TargetItem) |
Menu.Option
Used in Select, Combobox, and other listbox menus. Selected states apply when
aria-selected="true".
| State | v15 | v16 |
|---|---|---|
| Active/pressed | Not styled | surface.overlay.pressed background |
| Focus | Solid blue fill (brand.accent.primary) and white text | Inset 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 + Focus | Solid blue fill and white text | Inset 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 + Selected | Solid blue fill and white text with disabled opacity | Light blue background and blue text (brand.surface.selected / brand.fg.selected) with disabled opacity |
Modal
Visual Updates
Modal.Cardbox-shadow updated fromsystem.depth[5]tosystem.depth[6].
Popup
Visual Updates
Popup.Cardbox-shadow updated fromsystem.depth[3]tosystem.depth[4].
Toast
PR: #4053
Visual Updates
- Padding is now applied to the
Toastcontainer itself instead of being distributed acrossToast.BodyandToast.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]tosystem.depth[5]. Toast.Bodyno longer has a gap between text and link.Toast.CloseIconis set to have absolute positioning and aligned with the top-right corner (in LTR).Toast.CloseIconwas changed to beextraSmalltertiary 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]tosystem.depth[3]. - Type level is now
subtext.lg(previouslysubtext.md). - Padding and spacing have been adjusted to accommodate the new styling.
Navigation Components
Hyperlink
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:
| Need | API |
|---|---|
| Blue, underlined (default) | (no props) |
| Blue, no underline | linkType="standalone" |
| Inverse, underlined | variant="inverse" |
| Inverse, no underline | variant="inverse" linkType="standalone" |
| Dark gray, underlined | variant="secondary" |
| Dark gray, no underline | variant="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:
| Before | After |
|---|---|
variant="standalone" | linkType="standalone" |
variant="standaloneInverse" | variant="inverse" linkType="standalone" |
Breadcrumbs
Visual Updates
- Trail links (
Breadcrumbs.Link) use Hyperlinkvariant="secondary"andlinkType="standalone". Trail text usessystem.color.fg.default; hover and active states use surface overlay tokens with underline on hover. - The current item (
Breadcrumbs.CurrentItem) usessystem.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.primarywith 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 TextInputshape.lg(12px). - GoToForm spacing uses
marginInlineStart: gap.xsalongside Controlsgap.xs.
Segmented Control
PR: #4048
Visual Updates
- Container background changed from
surface.alt.strongtosurface.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) togap.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.defaultandsurface.overlay.pressed.defaultrespectively 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.
| Theme | Typeface | Package |
|---|---|---|
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/fontsover@workday/canvas-kit-react-fontswhen 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.