From 7fa531b6fe2bf85d3af96ed81c4009c4734fdd60 Mon Sep 17 00:00:00 2001 From: Alexandr Zelenin Date: Tue, 13 Jan 2026 07:51:48 -0800 Subject: [PATCH 1/9] fix: Use getBoundingClientRect for popup width measurement (#3686) Resolve issue with incorrect calculations in non-chrome browsers. getBoundingClientRect take into account: width + padding + border + scroll clientWidth: - In firefox does not take padding into account - Does not take border into account (that why there was +2 hack previously I assume) It should not lead to any notable visual by most of users in Chrome. Popup will match size of the input (+2px for the border). In other browsers behavior will match. Since border-box used for all sizing using getBoundingClientRect looks like a correct approach for coherent sizing across browsers and devices. Samples: Before: Firefox: image Edge: image After: Firefox: image Edge: image [category:SelectMenu ComboBox (and derived ones)] --- modules/preview-react/select/lib/SelectMenu.tsx | 2 +- modules/react/combobox/lib/hooks/useComboboxInput.ts | 2 +- .../combobox/lib/hooks/useComboboxInputOpenWithArrowKeys.ts | 2 +- modules/react/combobox/lib/hooks/useSetPopupWidth.ts | 2 +- .../react/combobox/stories/visual-testing/testing.stories.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/preview-react/select/lib/SelectMenu.tsx b/modules/preview-react/select/lib/SelectMenu.tsx index 532d903565..d099cf27f4 100644 --- a/modules/preview-react/select/lib/SelectMenu.tsx +++ b/modules/preview-react/select/lib/SelectMenu.tsx @@ -253,7 +253,7 @@ export const SelectMenu = ({ const handleWidthChange = useCallback(() => { if (buttonRef && buttonRef.current && visibility !== 'closed') { - const newMenuWidth = buttonRef.current.clientWidth + 2; + const newMenuWidth = buttonRef.current.getBoundingClientRect().width; setWidth(newMenuWidth); } }, [buttonRef, visibility]); diff --git a/modules/react/combobox/lib/hooks/useComboboxInput.ts b/modules/react/combobox/lib/hooks/useComboboxInput.ts index adda0cb646..2d17b6e947 100644 --- a/modules/react/combobox/lib/hooks/useComboboxInput.ts +++ b/modules/react/combobox/lib/hooks/useComboboxInput.ts @@ -61,7 +61,7 @@ export const useComboboxInput = composeHooks( }, onClick(event: React.MouseEvent) { if (model.state.visibility === 'hidden') { - model.events.setWidth(event.currentTarget.clientWidth); + model.events.setWidth(event.currentTarget.getBoundingClientRect().width); } }, value: model.state.value, diff --git a/modules/react/combobox/lib/hooks/useComboboxInputOpenWithArrowKeys.ts b/modules/react/combobox/lib/hooks/useComboboxInputOpenWithArrowKeys.ts index fde5836f8e..a6556d87fb 100644 --- a/modules/react/combobox/lib/hooks/useComboboxInputOpenWithArrowKeys.ts +++ b/modules/react/combobox/lib/hooks/useComboboxInputOpenWithArrowKeys.ts @@ -14,7 +14,7 @@ export const useComboboxInputOpenWithArrowKeys = createElemPropsHook(useCombobox (event.key === 'ArrowUp' && model.state.visibility !== 'visible') ) { model.events.show(event); - model.events.setWidth(event.currentTarget.clientWidth); + model.events.setWidth(event.currentTarget.getBoundingClientRect().width); } }, }; diff --git a/modules/react/combobox/lib/hooks/useSetPopupWidth.ts b/modules/react/combobox/lib/hooks/useSetPopupWidth.ts index 50f8b11ab1..5c5fd1be76 100644 --- a/modules/react/combobox/lib/hooks/useSetPopupWidth.ts +++ b/modules/react/combobox/lib/hooks/useSetPopupWidth.ts @@ -9,7 +9,7 @@ export const useSetPopupWidth = createElemPropsHook(useComboboxModel)(model => { const visible = model.state.visibility !== 'hidden'; React.useLayoutEffect(() => { if (visible) { - model.events.setWidth(model.state.targetRef.current?.clientWidth || 0); + model.events.setWidth(model.state.targetRef.current?.getBoundingClientRect().width || 0); } }, [visible, model.events, model.state.targetRef]); return {}; diff --git a/modules/react/combobox/stories/visual-testing/testing.stories.tsx b/modules/react/combobox/stories/visual-testing/testing.stories.tsx index 27cf98a324..06b9e00559 100644 --- a/modules/react/combobox/stories/visual-testing/testing.stories.tsx +++ b/modules/react/combobox/stories/visual-testing/testing.stories.tsx @@ -40,7 +40,7 @@ export const ComboboxStates = { // eslint-disable-next-line react-hooks/rules-of-hooks React.useLayoutEffect(() => { if (visibility === 'visible') { - model.events.setWidth(model.state.inputRef.current.clientWidth); + model.events.setWidth(model.state.inputRef.current.getBoundingClientRect().width); } }, [visibility, model.events, model.state.inputRef]); return ( From 882c5dac101bf61cd3b7a7dd449378d06823da05 Mon Sep 17 00:00:00 2001 From: alanbsmith Date: Tue, 13 Jan 2026 15:53:45 +0000 Subject: [PATCH 2/9] chore: Release v14.2.6 [skip release] --- CHANGELOG.md | 7 +++++++ lerna.json | 2 +- modules/codemod/package.json | 2 +- modules/css/package.json | 2 +- modules/docs/package.json | 10 +++++----- modules/labs-css/package.json | 2 +- modules/labs-react/package.json | 6 +++--- modules/mcp/package.json | 2 +- modules/popup-stack/package.json | 2 +- modules/preview-css/package.json | 2 +- modules/preview-react/package.json | 6 +++--- modules/react-fonts/package.json | 2 +- modules/react/package.json | 8 ++++---- modules/styling-transform/package.json | 4 ++-- modules/styling/package.json | 4 ++-- 15 files changed, 34 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24c3dd1359..84bcea1b36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [v14.2.6](https://github.com/Workday/canvas-kit/releases/tag/v14.2.6) (2026-01-13) + +### Components + +- fix: Use getBoundingClientRect for popup width measurement ([#3686](https://github.com/Workday/canvas-kit/pull/3686)) ([@Zav39](https://github.com/Zav39)) + + ## [v14.2.5](https://github.com/Workday/canvas-kit/releases/tag/v14.2.5) (2026-01-08) diff --git a/lerna.json b/lerna.json index 42b45de03a..65b0e2cc25 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "modules/**" ], - "version": "14.2.5", + "version": "14.2.6", "npmClient": "yarn", "command": { "version": { diff --git a/modules/codemod/package.json b/modules/codemod/package.json index c3cca9eb0b..748073a6e9 100644 --- a/modules/codemod/package.json +++ b/modules/codemod/package.json @@ -2,7 +2,7 @@ "name": "@workday/canvas-kit-codemod", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", - "version": "14.2.5", + "version": "14.2.6", "description": "A collection of codemods for use on Workday Canvas Kit packages.", "main": "dist/es6/index.js", "sideEffects": false, diff --git a/modules/css/package.json b/modules/css/package.json index 72e51520b7..83556b985a 100644 --- a/modules/css/package.json +++ b/modules/css/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-css", - "version": "14.2.5", + "version": "14.2.6", "description": "The parent module that contains all Workday Canvas Kit CSS components", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", diff --git a/modules/docs/package.json b/modules/docs/package.json index b612953b01..c049de5e8d 100644 --- a/modules/docs/package.json +++ b/modules/docs/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-docs", - "version": "14.2.5", + "version": "14.2.6", "description": "Documentation components of Canvas Kit components", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", @@ -45,10 +45,10 @@ "@emotion/styled": "^11.6.0", "@stackblitz/sdk": "^1.11.0", "@storybook/csf": "0.0.1", - "@workday/canvas-kit-labs-react": "^14.2.5", - "@workday/canvas-kit-preview-react": "^14.2.5", - "@workday/canvas-kit-react": "^14.2.5", - "@workday/canvas-kit-styling": "^14.2.5", + "@workday/canvas-kit-labs-react": "^14.2.6", + "@workday/canvas-kit-preview-react": "^14.2.6", + "@workday/canvas-kit-react": "^14.2.6", + "@workday/canvas-kit-styling": "^14.2.6", "@workday/canvas-system-icons-web": "^3.0.36", "@workday/canvas-tokens-web": "^3.1.1", "markdown-to-jsx": "^7.2.0", diff --git a/modules/labs-css/package.json b/modules/labs-css/package.json index 8cc59fb3d7..3786624cb9 100644 --- a/modules/labs-css/package.json +++ b/modules/labs-css/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-labs-css", - "version": "14.2.5", + "version": "14.2.6", "description": "The parent module that contains all Workday Canvas Kit Labs CSS components", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", diff --git a/modules/labs-react/package.json b/modules/labs-react/package.json index 97db89ce34..e204776c73 100644 --- a/modules/labs-react/package.json +++ b/modules/labs-react/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-labs-react", - "version": "14.2.5", + "version": "14.2.6", "description": "Canvas Kit Labs is an incubator for new and experimental components. Since we have a rather rigorous process for getting components in at a production level, it can be valuable to make them available earlier while we continuously iterate on the API/functionality. The Labs modules allow us to do that as needed.", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", @@ -48,8 +48,8 @@ "dependencies": { "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", - "@workday/canvas-kit-react": "^14.2.5", - "@workday/canvas-kit-styling": "^14.2.5", + "@workday/canvas-kit-react": "^14.2.6", + "@workday/canvas-kit-styling": "^14.2.6", "@workday/canvas-system-icons-web": "^3.0.36", "@workday/canvas-tokens-web": "^3.1.1", "@workday/design-assets-types": "^0.2.10", diff --git a/modules/mcp/package.json b/modules/mcp/package.json index 3fa76eb3a9..b61f3f8fe0 100644 --- a/modules/mcp/package.json +++ b/modules/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-mcp", - "version": "14.2.5", + "version": "14.2.6", "description": "MCP package for Canvas Kit", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", diff --git a/modules/popup-stack/package.json b/modules/popup-stack/package.json index b106edf2d2..d077520abf 100644 --- a/modules/popup-stack/package.json +++ b/modules/popup-stack/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-popup-stack", - "version": "14.2.5", + "version": "14.2.6", "description": "Stack for managing popup UIs to coordinate global concerns like escape key handling and rendering order", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", diff --git a/modules/preview-css/package.json b/modules/preview-css/package.json index 4d31763f8a..34dfc2a49e 100644 --- a/modules/preview-css/package.json +++ b/modules/preview-css/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-preview-css", - "version": "14.2.5", + "version": "14.2.6", "description": "The parent module that contains all Workday Canvas Kit Preview CSS components", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", diff --git a/modules/preview-react/package.json b/modules/preview-react/package.json index 1ac2b7ab6d..74a90b7a04 100644 --- a/modules/preview-react/package.json +++ b/modules/preview-react/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-preview-react", - "version": "14.2.5", + "version": "14.2.6", "description": "Canvas Kit Preview is made up of components that have the full design and a11y review, are part of the DS ecosystem and are approved for use in product. The API's could be subject to change, but not without strong communication and migration strategies.", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", @@ -48,8 +48,8 @@ "dependencies": { "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", - "@workday/canvas-kit-react": "^14.2.5", - "@workday/canvas-kit-styling": "^14.2.5", + "@workday/canvas-kit-react": "^14.2.6", + "@workday/canvas-kit-styling": "^14.2.6", "@workday/canvas-system-icons-web": "^3.0.36", "@workday/canvas-tokens-web": "^3.1.1", "@workday/design-assets-types": "^0.2.10" diff --git a/modules/react-fonts/package.json b/modules/react-fonts/package.json index 9581482af2..52de56e71b 100644 --- a/modules/react-fonts/package.json +++ b/modules/react-fonts/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-react-fonts", - "version": "14.2.5", + "version": "14.2.6", "description": "Fonts for canvas-kit-react", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", diff --git a/modules/react/package.json b/modules/react/package.json index 29b7e0b50a..9cb71f8350 100644 --- a/modules/react/package.json +++ b/modules/react/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-react", - "version": "14.2.5", + "version": "14.2.6", "description": "The parent module that contains all Workday Canvas Kit React components", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", @@ -52,9 +52,9 @@ "@popperjs/core": "^2.5.4", "@tanstack/react-virtual": "^3.13.9", "@workday/canvas-colors-web": "^2.0.0", - "@workday/canvas-kit-popup-stack": "^14.2.5", - "@workday/canvas-kit-preview-react": "^14.2.5", - "@workday/canvas-kit-styling": "^14.2.5", + "@workday/canvas-kit-popup-stack": "^14.2.6", + "@workday/canvas-kit-preview-react": "^14.2.6", + "@workday/canvas-kit-styling": "^14.2.6", "@workday/canvas-system-icons-web": "^3.0.36", "@workday/canvas-tokens-web": "^3.1.1", "@workday/design-assets-types": "^0.2.10", diff --git a/modules/styling-transform/package.json b/modules/styling-transform/package.json index fdb0f2270d..d8a5e2a518 100644 --- a/modules/styling-transform/package.json +++ b/modules/styling-transform/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-styling-transform", - "version": "14.2.5", + "version": "14.2.6", "description": "The custom CSS in JS solution that takes JS styles and turns them into static CSS", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", @@ -38,7 +38,7 @@ ], "dependencies": { "@emotion/serialize": "^1.0.2", - "@workday/canvas-kit-styling": "^14.2.5", + "@workday/canvas-kit-styling": "^14.2.6", "@workday/canvas-tokens-web": "^3.1.1", "stylis": "4.0.13", "ts-node": "^10.9.1", diff --git a/modules/styling/package.json b/modules/styling/package.json index 0a056c093e..361225a0c5 100644 --- a/modules/styling/package.json +++ b/modules/styling/package.json @@ -1,6 +1,6 @@ { "name": "@workday/canvas-kit-styling", - "version": "14.2.5", + "version": "14.2.6", "description": "The custom CSS in JS solution that takes JS styles and turns them into static CSS", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", @@ -53,7 +53,7 @@ "@emotion/react": "^11.7.1", "@emotion/serialize": "^1.0.2", "@emotion/styled": "^11.6.0", - "@workday/canvas-kit-react": "^14.2.5", + "@workday/canvas-kit-react": "^14.2.6", "@workday/canvas-system-icons-web": "^3.0.36", "@workday/canvas-tokens-web": "^3.1.1", "typescript": "5.0" From 88c6452eb8d484c53fd71fbc58b4145b26b833d0 Mon Sep 17 00:00:00 2001 From: Manuel Carrera Date: Thu, 15 Jan 2026 09:14:39 -0700 Subject: [PATCH 3/9] fix: Add variant type to insights (#3685) It's useful information when a component has `variant="inverse"` adding this to insights tracking. [category:Components] Co-authored-by: manuel.carrera Co-authored-by: Alan Smith --- modules/react/common/lib/utils/components.ts | 2 +- modules/react/common/lib/utils/insights.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/react/common/lib/utils/components.ts b/modules/react/common/lib/utils/components.ts index 63215f92be..b9b824b84f 100644 --- a/modules/react/common/lib/utils/components.ts +++ b/modules/react/common/lib/utils/components.ts @@ -542,7 +542,7 @@ export const createComponent = ({as: asOverride, ...props}, ref) => { return Component( { - ...setCanvasKitTags(displayName), + ...setCanvasKitTags(displayName, props), ...props, } as any, ref as ExtractRef, diff --git a/modules/react/common/lib/utils/insights.ts b/modules/react/common/lib/utils/insights.ts index 45f99ab8c3..cb6f4d773d 100644 --- a/modules/react/common/lib/utils/insights.ts +++ b/modules/react/common/lib/utils/insights.ts @@ -10,14 +10,15 @@ const versionTag = process.env.NODE_ENV === 'production' ? version : version.spl * This function returns data attributes for tagging * We use this to track when and where our components render. It also allows us to see what version is rendering. */ -export function setCanvasKitTags(displayName = '') { +export function setCanvasKitTags(displayName = '', props: any = {}) { // Do not add tags for subcomponents. E.g. Card.Text const shouldAddTag = displayName.length && !displayName.includes('.'); - if (shouldAddTag) { + // Append variant name to tag, if available: `primary-button-inverse` + const componentTypeTag = props.variant ? `${displayName} ${props.variant}` : displayName; return { ['data-uxi-canvas-kit-version']: versionTag, - ['data-uxi-canvas-kit-component-type']: slugify(displayName), + ['data-uxi-canvas-kit-component-type']: slugify(componentTypeTag), }; } From f7a163a56309dc43450f1df43777bddb43fe7cbb Mon Sep 17 00:00:00 2001 From: Youry Date: Thu, 15 Jan 2026 16:15:10 +0000 Subject: [PATCH 4/9] chore: Add llm cursor rules mdc (#3684) Fixes: https://github.com/Workday/canvas-kit/issues/3682 Adds a new `.mdc` (Markdown with Cursor rules) file to provide Canvas Kit best practices for developers using AI assistants like Cursor and Claude. This file helps teams ensure consistent adherence to Canvas Kit patterns by providing: - Token usage guidelines (system tokens, import patterns) - Styling best practices (`createStyles`, `createStencil`, `cs` prop) - Component patterns (compound components, controlled components) - Accessibility guidelines (semantic HTML, ARIA, keyboard navigation) - Theming guidelines (global vs scoped theming) - Code style conventions The content is derived from existing Canvas Kit documentation in `modules/docs/` and synthesized into a single, always-applied rule file. [category:Documentation] Release Note: Added `modules/docs/llm/canvas-kit.mdc` - a Cursor/Claude rules file containing Canvas Kit best practices. Teams can add this to their `.cursor/rules/` directory to have AI assistants follow Canvas Kit conventions automatically. Co-authored-by: Youry Stancatte --- modules/docs/llm/canvas-kit.mdc | 580 ++++++++++++++++++++++++++++++++ 1 file changed, 580 insertions(+) create mode 100644 modules/docs/llm/canvas-kit.mdc diff --git a/modules/docs/llm/canvas-kit.mdc b/modules/docs/llm/canvas-kit.mdc new file mode 100644 index 0000000000..a3ca5beaa7 --- /dev/null +++ b/modules/docs/llm/canvas-kit.mdc @@ -0,0 +1,580 @@ +--- +description: Best practices for Workday Canvas Kit - tokens, styling, components, and accessibility +globs: ['**/*.tsx', '**/*.ts', '**/*.jsx', '**/*.js', '**/*.css'] +alwaysApply: true +--- + +# Canvas Kit Best Practices + +This file contains best practices for working with Workday Canvas Kit. Use these guidelines when +building components, styling, and implementing accessibility features. + +## Token Usage Guidelines + +### Always Use Design Tokens + +- **Prefer system tokens** from `@workday/canvas-tokens-web` over raw CSS values +- Use semantic token names (e.g., `system.color.bg.primary.default`) instead of hardcoded colors + (e.g., `#333`) +- Token hierarchy: base tokens → brand tokens → system tokens +- System tokens provide better theming support than base tokens + +### Token Import Pattern + +```tsx +import {system, brand, base} from '@workday/canvas-tokens-web'; + +// Import CSS variables once at app root level +import '@workday/canvas-tokens-web/css/base/_variables.css'; +import '@workday/canvas-tokens-web/css/system/_variables.css'; +import '@workday/canvas-tokens-web/css/brand/_variables.css'; +``` + +### Token Usage Examples + +```tsx +// ✅ Good - Use system tokens +const styles = createStyles({ + color: system.color.text.default, + margin: system.space.x4, + backgroundColor: system.color.bg.primary.default, +}); + +// ❌ Avoid - Hardcoded values +const styles = createStyles({ + color: '#333', + margin: '16px', + backgroundColor: 'blue', +}); +``` + +## Styling Best Practices + +### Core Styling APIs + +Canvas Kit uses a custom CSS-in-JS solution for static CSS generation and token integration: + +- **`createStyles`** - Define reusable, static CSS objects +- **`createStencil`** - Define reusable, dynamic component styles with parts, vars, and modifiers +- **`cs` prop** - Apply multiple styles and handle merges consistently to Canvas Kit components + +### Define Styles Outside Render Functions + +**Critical:** Always declare styles at the module level. Creating styles inside render functions +causes performance issues. + +```tsx +// ✅ Good - Module level +const buttonStyles = createStyles({ + backgroundColor: system.color.bg.primary.default, + color: system.color.text.inverse, +}); + +export const MyButton = () => ; + +// ❌ Bad - Inside render function +export const MyButton = () => { + const buttonStyles = createStyles({backgroundColor: 'red'}); // Performance hit! + return ; +}; +``` + +### When to Use `createStyles` + +Use `createStyles` for simple, reusable style objects that do **not** depend on dynamic data or +props: + +- Defining base styles +- Applying static overrides +- Styling tokens-based components + +```tsx +import {createStyles} from '@workday/canvas-kit-styling'; +import {system} from '@workday/canvas-tokens-web'; +import {Text} from '@workday/canvas-kit-react/text'; + +const uppercaseTextStyles = createStyles({ + textTransform: 'uppercase', + margin: system.space.x4, +}); + +My uppercased text; +``` + +### When to Use `createStencil` + +Use `createStencil` when styles depend on **props**, **variants**, or **component parts**: + +- Size or color variants (`primary`, `secondary`) +- Compound state combinations (`size=small`, `iconPosition=end`) +- Multi-part components (e.g., `Button`, `Card`, `MenuItem`) + +```tsx +const buttonStencil = createStencil({ + vars: {color: '', backgroundColor: ''}, + base: ({color, backgroundColor}) => ({ + color: cssVar(color, system.color.text.default), + backgroundColor: cssVar(backgroundColor, system.color.bg.default), + }), + modifiers: { + variant: { + primary: {backgroundColor: system.color.bg.primary.default}, + secondary: {backgroundColor: system.color.bg.muted.default}, + }, + }, +}); +``` + +### Using the `cs` Prop + +The `cs` prop accepts styles created by `createStyles`, `createStencil`, or a class name. Canvas Kit +components already handle style merging internally via `handleCsProp`: + +```tsx +// ✅ Good - Pass styles to cs prop on Canvas Kit components + + +// ✅ Good - Multiple styles via array + +``` + +**Important:** When building custom components, use `handleCsProp` to properly merge `className`, +`style`, and `cs` props. Do not manually concatenate class names: + +```tsx +// ✅ Good - Use handleCsProp in custom components +const MyComponent = elemProps => { + return
; +}; + +// ❌ Avoid - Manual className concatenation loses style merging +
; +``` + +### CSS Logical Properties for RTL Support + +Use CSS logical properties instead of physical properties for RTL compatibility: + +```tsx +// ✅ Good - Logical properties (adapt to RTL automatically) +const styles = createStyles({ + marginInlineStart: system.space.x4, + paddingInlineEnd: system.space.x2, + borderInlineStart: `1px solid ${system.color.border.default}`, +}); + +// ❌ Avoid - Physical properties (don't adapt to RTL) +const styles = createStyles({ + marginLeft: system.space.x4, + paddingRight: system.space.x2, + borderLeft: `1px solid ${system.color.border.default}`, +}); +``` + +### Utility Functions + +- **`px2rem()`** - Convert pixel values to rem units +- **`cssVar()`** - Wrap tokens in CSS variable syntax with optional fallback +- **`calc`** - Math operations with CSS calc() and variables + +```tsx +import {px2rem, cssVar, calc} from '@workday/canvas-kit-styling'; + +const styles = createStyles({ + margin: px2rem(8), // Converts 8px to rem + padding: calc.add(system.space.x1, '0.125rem'), + color: cssVar(system.color.text.default, '#000'), // With fallback +}); +``` + +### Avoid Emotion Runtime APIs + +- Avoid `styled()` from `@emotion/styled` for new code +- Avoid inline style objects in `cs` prop (use `createStyles` or `createStencil` instead) +- Don't mix Emotion and static styling approaches + +## Component Patterns + +### Compound Components + +Prefer the compound component pattern for flexible, semantic component APIs: + +```tsx +// ✅ Good - Compound component pattern + + + First Tab + Second Tab + + First Tab Contents + Second Tab Contents + + +// ❌ Avoid - Configuration component (less flexible) + +``` + +### Controlled Components + +- Prefer controlled components wherever possible +- Manage minimal state within components +- Use standard event handlers: `value` and `onChange` for inputs, `checked` and `onChange` for + checkboxes + +```tsx +// ✅ Good - Controlled component +const [value, setValue] = useState(''); + setValue(e.target.value)} />; +``` + +### Prop Spreading Pattern + +Use Canvas Kit utility functions to handle ref forwarding and HTML attribute extraction: + +**For simple components**, use `createComponent`: + +```tsx +import {createComponent} from '@workday/canvas-kit-react/common'; + +interface ButtonProps { + variant: 'primary' | 'secondary'; + size: 'small' | 'medium' | 'large'; + children: React.ReactNode; +} + +export const Button = createComponent('button')({ + displayName: 'Button', + Component: ({variant, size, children, ...elemProps}: ButtonProps, ref, Element) => { + return ( + + {children} + + ); + }, +}); +``` + +`createComponent` automatically handles: + +- Ref forwarding +- HTML attribute extraction based on the element type +- The `as` prop for changing the rendered element +- Proper TypeScript typing + +**For compound components with models**, use `createContainer` and `createSubComponent`: + +```tsx +import {createContainer, createSubComponent} from '@workday/canvas-kit-react/common'; + +// Container component +export const Disclosure = createContainer()({ + displayName: 'Disclosure', + modelHook: useDisclosureModel, + subComponents: { + Target: DisclosureTarget, + Content: DisclosureContent, + }, +})(({children}) => { + return <>{children}; +}); + +// Sub-component +export const DisclosureTarget = createSubComponent('button')({ + modelHook: useDisclosureModel, +})((elemProps, Element, model) => { + return model.events.toggle()} {...elemProps} />; +}); +``` + +These utilities handle ref forwarding, HTML attribute extraction, and model context automatically. + +### CanvasProvider + +Wrap your application in `` for proper styling context and Emotion cache: + +```tsx +import {CanvasProvider} from '@workday/canvas-kit-react/common'; + + + +; +``` + +### Using `handleCsProp` + +When building custom components with stencils, use `handleCsProp` to merge props: + +```tsx +import {handleCsProp} from '@workday/canvas-kit-styling'; + +const MyComponent = elemProps => { + return
; +}; +``` + +## Common Patterns + +### Style Definition Location + +**Always define styles at module level**, never inside render functions or component bodies: + +```tsx +// ✅ Good +const cardStyles = createStyles({ + padding: system.space.x4, +}); + +export const Card = () =>
Content
; + +// ❌ Bad +export const Card = () => { + const cardStyles = createStyles({padding: system.space.x4}); + return
Content
; +}; +``` + +### Modifiers Over Conditionals + +Use modifiers for variants/states instead of conditional logic: + +```tsx +// ✅ Good - Use modifiers +const badgeStencil = createStencil({ + modifiers: { + status: { + success: {background: system.color.bg.success.default}, + error: {background: system.color.bg.negative.default}, + }, + }, +}); + +// ❌ Avoid - Conditional logic in styles +const badgeStyles = status => + createStyles({ + background: status === 'success' ? 'green' : 'red', // Breaks static compilation + }); +``` + +### CSS Variables for Dynamic Values + +Use CSS variables (via stencil vars) for dynamic values instead of inline styles: + +```tsx +// ✅ Good - CSS variables +const buttonStencil = createStencil({ + vars: {backgroundColor: ''}, + base: ({backgroundColor}) => ({ + backgroundColor: cssVar(backgroundColor, system.color.bg.default), + }), +}); + +// ❌ Avoid - Inline styles +