@workday/canvas-kit-mcp 13.2.41 → 14.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2346 @@
1
+ # Style Props Deprecation Overview
2
+
3
+ ### Purpose
4
+
5
+ As part of the Canvas Kit’s modernization process, we’re moving away from Emotion’s runtime styling
6
+ and promoting a custom CSS-in-JS solution: `@workday/canvas-kit-styling`. This change improves
7
+ **performance**, **consistency**, and **maintainability** across our codebase. For more information,
8
+ view our [Future of](https://github.com/Workday/canvas-kit/discussions/2265) discussion.
9
+
10
+ ### Goals
11
+
12
+ - **Reduce runtime overhead** by removing Emotion’s runtime from `@emotion/react`
13
+ - **Promote prescriptive, opinionated styling** across Workday
14
+ - **Enable static CSS compilation** for faster load times and smaller bundles
15
+ - **Support new design tokens and CSS Variables** for scalable theming
16
+ - **Ensure proper style merging** and stable selector behavior
17
+ - **Support advanced styling patterns** like compound styles, modifiers, and `data-parts`
18
+
19
+ > Emotion dynamically injects styles at runtime, causing costly re-renders and cache invalidations.
20
+ > The new system statically compiles styles at build time for optimal performance.
21
+
22
+ ### Timeline
23
+
24
+ - **Deprecation introduced:** Canvas Kit **v14.1**
25
+ - **Removal:** _Not immediate_ — style props and `styled()` will continue to function in upcoming
26
+ releases
27
+ - **Migration timeline:** Gradual; no immediate codebase-wide update required
28
+
29
+ ## LLM Assisted Migration <StorybookStatusIndicator type="ai" />
30
+
31
+ We've provided an **LLM migration mapping file** (`llm-style-props-migration.txt`) specifically
32
+ designed for use with LLM-based code assistants such as [Cursor](https://www.cursor.so/). It
33
+ contains a compiled LLM consumption version of this v14 Upgrade Guide. It is not intended for direct
34
+ human reference or team documentation, but rather as structured input for LLMs to automate and
35
+ assist with your migration process.
36
+
37
+ > **Important:** LLMs can make mistakes. Please verify changes using this Migration Guide.
38
+
39
+ **How to use:**
40
+
41
+ - **View raw file**: Open the file in a new tab to see the complete migration mapping
42
+ - **Download LLM File**: Save the file locally to upload or paste into your LLM/code assistant
43
+ - **Use with LLM**: Provide the raw content to your LLM/code assistant as context for automated
44
+ migration
45
+
46
+ <DownloadLLMFile
47
+ rawFileLink="https://raw.githubusercontent.com/Workday/canvas-kit/master/modules/docs/llm-txt/llm-style-props-migration.txt"
48
+ filename="llm-style-props-migration.txt"
49
+ />
50
+
51
+ ## Changes Overview
52
+
53
+ ### Replacements
54
+
55
+ Use the new **Canvas Kit Styling** utilities:
56
+
57
+ | Old API | New API | Purpose |
58
+ | -------------------------------------------------- | -------------------------------- | ------------------------------------------ |
59
+ | `styled()` | `createStyles` / `createStencil` | Define static or component-level styles |
60
+ | Inline style props, like `background` or `padding` | `cs` prop | Safely merge class names and styles |
61
+ | Dynamic values | `createVars` | Manage CSS variables for runtime overrides |
62
+ | Emotion modifiers | `modifiers`, `compound` | Define consistent appearance variants |
63
+
64
+ ## Canvas Kit Styling
65
+
66
+ <InformationHighlight className="sb-unstyled" cs={{p: {marginBlock: 0}}}>
67
+ <InformationHighlight.Icon />
68
+ <InformationHighlight.Heading>Canvas Kit Styling Docs</InformationHighlight.Heading>
69
+ For a detailed overview of our styling approach, view our styling docs.
70
+ <InformationHighlight.Link href="https://workday.github.io/canvas-kit/?path=/docs/styling-getting-started-overview--docs">
71
+ Read more
72
+ </InformationHighlight.Link>
73
+ </InformationHighlight>
74
+
75
+ Canvas Kit’s styling utilities are built for **static CSS generation**, **token integration**, and
76
+ **predictable composition**.
77
+
78
+ ### Core APIs
79
+
80
+ - **`createStyles`** — define reusable, static CSS objects.
81
+ - **`createStencil`** — define reusable, dynamic component styles with parts, vars, and modifiers
82
+ - **`cs` prop** — apply multiple styles and handle merges consistently to Canvas Kit components
83
+
84
+ ### Best Practices
85
+
86
+ These best practices ensure your components remain **performant**, **consistent**, and
87
+ **maintainable** under the new Canvas Kit Styling system.
88
+
89
+ #### Define Styles Outside the Render Function
90
+
91
+ Always declare styles at the module level. Creating styles inside the render or component function
92
+ will trigger component re-render.
93
+
94
+ ✅ **Do**
95
+
96
+ ```tsx
97
+ // `createStyles` returns a string of className
98
+ const buttonStyles = createStyles({
99
+ backgroundColor: system.color.bg.primary.default,
100
+ color: system.color.text.inverse,
101
+ });
102
+
103
+ export const MyButton = () => <button className={buttonStyles}>Click me</button>;
104
+ ```
105
+
106
+ ❌ **Don’t**
107
+
108
+ ```tsx
109
+ export const MyButton = () => {
110
+ const buttonStyles = createStyles({backgroundColor: 'red'}); // bad
111
+ return <button cs={buttonStyles}>Click me</button>;
112
+ };
113
+ ```
114
+
115
+ #### Use `createStyles` for Static Styling
116
+
117
+ Use `createStyles` for simple, reusable style objects that do **not** depend on dynamic data or
118
+ props.
119
+
120
+ ✅ Ideal for:
121
+
122
+ - Defining base styles
123
+ - Applying static overrides
124
+ - Styling tokens-based components
125
+
126
+ `createStyles` returns a string of className that can be applied to a React element. If you're
127
+ applying the class to a Canvas Kit component, you can use the `cs` prop.
128
+
129
+ ```tsx
130
+ import {BaseButton} from '@workday/canvas-kit-react/button';
131
+ import {createStyles} from '@workday/canvas-kit-styling';
132
+ // `createStyles` returns a string of className
133
+ const buttonStyles = createStyles({
134
+ backgroundColor: system.color.bg.primary.default,
135
+ color: system.color.text.inverse,
136
+ });
137
+
138
+ export const MyButton = () => <BaseButton cs={buttonStyles}>Click me</button>;
139
+ ```
140
+
141
+ #### Use `createStencil` for Dynamic or Complex Styling
142
+
143
+ Use `createStencil` when styles depend on **props**, **variants**, or **component parts**.
144
+
145
+ Examples:
146
+
147
+ - Size or color variants (`primary`, `secondary`)
148
+ - Compound state combinations (`size=small`, `iconPosition=end`)
149
+ - Multi-part components (e.g. `Button`, `Card`, `MenuItem`)
150
+
151
+ ✅ **Do**
152
+
153
+ ```tsx
154
+ const buttonStencil = createStencil({
155
+ vars: {color: '', background: ''},
156
+ base: ({color, backgroundColor}) => ({
157
+ color: cssVar(color, system.color.text.default),
158
+ backgroundColor: cssVar(backgroundColor, system.color.bg.default),
159
+ }),
160
+ modifiers: {
161
+ variant: {
162
+ primary: {background: system.color.bg.primary.default},
163
+ secondary: {background: system.color.bg.muted.default},
164
+ },
165
+ },
166
+ });
167
+ ```
168
+
169
+ - **vars**: If you initialize the variable with an empty string, it will allow the variable to
170
+ cascade and be defined.
171
+
172
+ ```tsx
173
+ const customButtonStencil = createStencil({
174
+ base: {
175
+ // Set the color variable to the primary color
176
+ [buttonStencil.vars.color]: system.color.fg.primary.default,
177
+ },
178
+ });
179
+ ```
180
+
181
+ - **cssVar**: The `cssVar` function is used when you want to add a default value if the CSS Variable
182
+ is not defined.
183
+ - **modifiers**: The `modifiers` property is used to define the styles for the different variants of
184
+ the component.
185
+
186
+ #### Use `cs` Prop to Merge Styles
187
+
188
+ The `cs` prop merges `className` and `style` attributes safely and consistently. Use this over using
189
+ style props or className concatenation.
190
+
191
+ ✅ **Do**
192
+
193
+ ```tsx
194
+ <PrimaryButton cs={[baseStyles, variantStyles]} />
195
+ ```
196
+
197
+ ❌ **Don’t**
198
+
199
+ ```tsx
200
+ <PrimaryButton className={`${baseStyles} ${variantStyles}`} />
201
+ ```
202
+
203
+ #### Use Variables for Dynamic Values
204
+
205
+ Instead of inline styles or runtime calculations, use stencil variables.
206
+
207
+ ✅ **Do**
208
+
209
+ ```tsx
210
+ const primaryButtonStencil = createStencil({
211
+ base: {
212
+ // Use the buttonStencil variable to set the background color
213
+ [buttonStencil.vars.background]: 'orange',
214
+ }
215
+ })
216
+ <PrimaryButton cs={primaryButtonStencil} />
217
+ ```
218
+
219
+ ❌ **Don’t**
220
+
221
+ ```tsx
222
+ <PrimaryButton cs={{backgroundColor: 'orange'}} /> // breaks static optimization
223
+ ```
224
+
225
+ #### Extend Existing Stencils Instead of Overriding Styles
226
+
227
+ When modifying Canvas Kit components, extend the provided `Stencil` instead of creating your own
228
+ from scratch.
229
+
230
+ ✅ **Do**
231
+
232
+ ```tsx
233
+ const customIconStencil = createStencil({
234
+ extends: systemIconStencil,
235
+ base: {
236
+ margin: system.space.x2,
237
+ },
238
+ });
239
+ ```
240
+
241
+ This will inherit both the styles and variables from the `systemIconStencil`.
242
+
243
+ #### Use Modifiers for Variants and States
244
+
245
+ Define component variations (size, color, emphasis) using **modifiers** rather than conditional
246
+ logic.
247
+
248
+ ✅ **Do**
249
+
250
+ ```tsx
251
+ const badgeStencil = createStencil({
252
+ modifiers: {
253
+ status: {
254
+ success: {background: system.color.bg.success.default},
255
+ error: {background: system.color.bg.negative.default},
256
+ },
257
+ },
258
+ });
259
+ ```
260
+
261
+ #### Use Compound Modifiers for Complex Conditions
262
+
263
+ When two or more modifiers combine to produce a new style, define a **compound modifier**.
264
+
265
+ ✅ **Do**
266
+
267
+ ```tsx
268
+ const myCustomStencil = createStencil({
269
+ base: {
270
+ //base styles
271
+ },
272
+ modifiers: {
273
+ variant: {
274
+ primary: {
275
+ // primary variant styles
276
+ },
277
+ },
278
+ size: {
279
+ large: {
280
+ // large size styles
281
+ },
282
+ },
283
+ },
284
+ compound: [
285
+ {
286
+ // apply styles when the variant is primary AND the size is large
287
+ modifiers: {variant: 'primary', size: 'large'},
288
+ styles: {paddingInline: system.space.x5},
289
+ },
290
+ ],
291
+ });
292
+ ```
293
+
294
+ #### Avoid Nested Stencils Unless Necessary
295
+
296
+ Each Stencil should map to one semantic component. Nested stencils can increase CSS specificity and
297
+ complexity. Use **parts** instead of deep nesting.
298
+
299
+ ✅ **Do**
300
+
301
+ ```tsx
302
+ const cardStencil = createStencil({
303
+ parts: {header: 'card-header', body: 'card-body'},
304
+ base: ({headerPart}) => ({
305
+ [headerPart]: {
306
+ fontWeight: 'bold',
307
+ },
308
+ }),
309
+ });
310
+
311
+ <Card cs={cardStencil}>
312
+ <Card.Heading {...cardStencil.parts.header}>Card Title</Card.Heading>
313
+ <Card.Body {...cardStencil.parts.body}>Card Body</Card.Body>
314
+ </Card>;
315
+ ```
316
+
317
+ #### Prefer Tokens and System Variables
318
+
319
+ Always use design tokens (`system`) for spacing, colors, typography, etc., instead of raw values.
320
+ View our System Tokens
321
+ [docs](https://workday.github.io/canvas-tokens/?path=/docs/docs-system-tokens-overview--docs) for
322
+ more information.
323
+
324
+ ✅ **Do**
325
+
326
+ ```tsx
327
+ color: system.color.text.default;
328
+ margin: system.space.x2;
329
+ ```
330
+
331
+ ❌ **Don’t**
332
+
333
+ ```tsx
334
+ color: '#333';
335
+ margin: '8px';
336
+ ```
337
+
338
+ #### Debugging and Static Compilation
339
+
340
+ - Enable static compilation during development to catch type or value errors early.
341
+ - Use `as const` for static objects to ensure values are type-locked for the compiler.
342
+
343
+ ✅ **Do**
344
+
345
+ ```tsx
346
+ const reusableStyles = {
347
+ position: 'absolute',
348
+ } as const;
349
+ ```
350
+
351
+ #### Don’t Mix Emotion and Static Styling
352
+
353
+ Avoid combining Emotion’s `styled` or `css` with `createStyles` or `createStencil`. It reintroduces
354
+ runtime style recalculations and negates static benefits.
355
+
356
+ ❌ **Don’t**
357
+
358
+ ```tsx
359
+ const StyledButton = styled(Button)(styles);
360
+ <StyledButton cs={createStyles({padding: 8})} />;
361
+ ```
362
+
363
+ ## Migration Example
364
+
365
+ ### Style Props
366
+
367
+ #### Before
368
+
369
+ ```typescript
370
+ import {Flex} from '@workday/canvas-kit-react/layout';
371
+
372
+ <Flex depth={1} marginX={10} background="frenchVanilla100" />;
373
+ ```
374
+
375
+ #### After
376
+
377
+ ```typescript
378
+ import {Flex} from '@workday/canvas-kit-react/layout';
379
+ import {system} from '@workday/canvas-tokens-web';
380
+ import {px2rem} from '@workday/canvas-kit-styling';
381
+
382
+ <Flex
383
+ cs={{
384
+ boxShadow: system.depth[1],
385
+ marginInline: px2rem(10),
386
+ background: system.color.bg.default,
387
+ }}
388
+ />;
389
+ ```
390
+
391
+ - **px2rem**: The `px2rem` function is used to convert a pixel value to a rem value.
392
+ - Use [CSS logical
393
+ properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values
394
+ - Use `system` tokens over `base` tokens for better theming support.
395
+
396
+ ### Emotion styled
397
+
398
+ #### Before (Emotion)
399
+
400
+ ```tsx
401
+ const StyledButton = styled('button')({
402
+ backgroundColor: 'blue',
403
+ color: 'white',
404
+ });
405
+ ```
406
+
407
+ #### After (Canvas Kit Styling)
408
+
409
+ ```tsx
410
+ import {createStyles} from '@workday/canvas-kit-styling';
411
+ import {PrimaryButton} from '@workday/canvas-kit-react/button';
412
+ import {system} from '@workday/canvas-tokens-web';
413
+
414
+ const primaryButtonStyles = createStyles({
415
+ backgroundColor: system.color.bg.primary.default,
416
+ color: system.color.text.inverse,
417
+ });
418
+
419
+ <PrimaryButton cs={primaryButtonStyles}>Click me</PrimaryButton>;
420
+ ```
421
+
422
+ # Canvas Kit Styling
423
+
424
+ ## Introduction
425
+
426
+ Canvas Kit styling is a custom CSS-in-JS solution that provides both a runtime for development and a static parsing process for build time. This system offers several key benefits:
427
+
428
+ ## Why Canvas Styling
429
+
430
+ This package contains everything needed to create CSS styling. This styling package contains a runtime for development and a static parsing process for build time.
431
+
432
+ Here are the goals for this project:
433
+
434
+ - TypeScript autocomplete of CSS object properties
435
+ - Low runtime for development
436
+ - Static CSS compilation for faster user experience
437
+ - Static CSS extraction for CSS only packages
438
+ - Dynamic styles using CSS Variables
439
+
440
+ If you're using Canvas Kit and not directly using this package, there is nothing extra to do on your end. The Canvas Kit packages are using the static compilation as part of the build process. If you want to use this package for your own styles, you don't need to do anything special to use in development. Included is a small runtime to get styling working. If you wish to statically compile your CSS from your TypeScript files for faster page loading, visit the [Getting Started](/docs/styling-getting-started--docs) page.
441
+
442
+ ### Why?
443
+
444
+ Canvas Kit no longer needs to support IE11 which allows us to use [CSS Custom Properties a.k.a. CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties). Dynamic style properties (properties that change during the lifecylce of the component) are the most costly in terms of performance in Emotion and should be avoided. Also, any conditionals in your Emotion style functions create unique hashes in the Emotion cache and makes that render frame pay an expensive [style recalculation](https://microsoftedge.github.io/DevTools/explainers/StyleTracing/explainer.html).
445
+
446
+ We can avoid most of the cost of Emotion's runtime by using [@emotion/css](https://www.npmjs.com/package/@emotion/css) instead and hoist all style definitions outside of a component's render function. All dynamic styling can be moved into "modifiers" (from [BEM](https://getbem.com/introduction/#modifer:~:text=%2C%20header%20title-,Modifier,-A%20flag%20on)).
447
+
448
+ There's still a runtime to select which styles should apply to an element and what the CSS Variable should be, but it is essentially only having to choose what CSS classes should apply to an element and changing the `style` property to set the CSS Variables. This does not require new [StyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/StyleSheet) inserts which cause expensive style recalculation. Think of the runtime as being the following:
449
+
450
+ ```jsx
451
+ <div
452
+ className={getClassNames(/* input from props */)}
453
+ style={getCSSVarValues(/* input from props */)}
454
+ />
455
+ ```
456
+
457
+ For further information, please read our GitHub discussion on [the future of styling](https://github.com/Workday/canvas-kit/discussions/2265)
458
+
459
+ ### What is Canvas Kit Styling?
460
+
461
+ Canvas Kit Styling includes two things:
462
+
463
+ 1. A utility function wrapper around `@emotion/css`
464
+ 2. An optional static compiler to remove most of the runtime
465
+
466
+ #### Utility Functions
467
+
468
+ This packages provides three utility functions to make it easier to style element-based components. The following is a brief description of each function. If you'd like to read a more in-depth discussion of each, our [API docs](/docs/features-styling-api--create-styles).
469
+
470
+ The primary utility function is the `createStyles` function. It makes a call to the `css` function from `@emotion/css`. Emotion still does most of the heavy lifting by handling the serialization, hashing, caching, and style injection.
471
+
472
+ The other two utility functions, `createVars` and `createModifiers`, provide supplemental styling functionality. `createVars` allows you to create temporary CSS variables within components to create dynamic properties. And `createModifiers` creates a modifier function to create dynamic groups of properties. If you're familiar with modifiers in [BEM](https://getbem.com/introduction/) (Block, Element, Modifier) CSS, you're well on your way to understanding this function's intent.
473
+
474
+ #### Static Compiler
475
+
476
+ The static compiler run as a TypeScript transform during TypeScript's transpilation phase. It requires the TypeScript type checker to determine the static value of any variables used. The transformer calls `@emotion/serialize` to pre-compute the serialized styles and hash so that `@emotion/css` can skip these steps. For example, there's the before/after of the code.
477
+
478
+ ```ts
479
+ // before
480
+ const myVars = createVars('textColor', 'backgroundColor');
481
+
482
+ const myStyles = createStyles({
483
+ fontSize: 12,
484
+ color: cssVar(myVars.textColor),
485
+ backgroundColor: cssVar(myVars.backgroundColor),
486
+ });
487
+
488
+ // after
489
+ const myVars = createVars('textColor', 'backgroundColor');
490
+
491
+ const myStyles = createStyles({
492
+ name: 'a8g234',
493
+ styles:
494
+ 'font-size: 12px; color: var(--css-my-vars-textColor); backgroundColor: var(--css-my-vars-backgroundColor);',
495
+ });
496
+ ```
497
+
498
+ Emotion has an internal shortcut that recognizes the `styles` property and [short-circuits interpolation](https://github.com/emotion-js/emotion/blob/f3b268f7c52103979402da919c9c0dd3f9e0e189/packages/serialize/src/index.js#L319).
499
+
500
+ ### Performance
501
+
502
+ `createStyles` is more performant than `styled` components or the `css` prop because the styles must be statically evaluated. The actual characters of a CSS property value cannot change at runtime. This means we do not need to recalculate a hash every render or inject new `StyleSheet` entries into the `StyleSheetList` in a render cycle. Injecting new `StyleSheets` causes slow [Style Recalculation](https://web.dev/articles/reduce-the-scope-and-complexity-of-style-calculations). What is not well known is browser engines maintain an internal selector cache to make style recalculations as fast as possible. Adding a CSS class to a DOM element will invoke a style recalculation, but if the the CSS selector is already in the `StyleSheetList`, the browser can optimize how those styles are applied to the current element.
503
+
504
+ In the runtime Emotion's case, a novel style will result in a new style hash which results in a new `StyleSheet` being injected into the `StyleSheetList`. To be safe, the browser's runtime engine will throw away any style recalculation cache and start from scratch. This happens if you render a new component on the page that hasn't been rendered yet, or if you make one of your style properties dynamic between render cycles. Eventually the Emotion cache gets warm and style recalcuation costs start to normalize and no longer invalidate the browser's selector cache.
505
+
506
+ On a page with over 1,000 elements and over 1,000 [CSSRules](https://developer.mozilla.org/en-US/docs/Web/API/CSSRule), (typical of a large web application), the difference between a &lt; 1ms for warmed selector cache and &gt; 100ms for a fresh selector cache. `createStyles` encourages a pattern similar to [BEM](https://getbem.com/) which works well with the browser's selector cache by not injecting new `StyleSheet`s during a page's normal operations. All styles are injected before any rendering takes place.
507
+
508
+ > **Note:** Since style props force Emotion's dynamic rendering, style props will fall back to
509
+ > Emotion's runtime performance characteristics and lose any benefits gained. Also if you use
510
+ > `styled` components or the `css` prop in a tree that uses `createStyles`, the styles created by
511
+ > the runtime APIs will still result in a selector cache invalidation. Even if you want to use
512
+ > `styled` or the `css` prop, consider using CSS Variables for dynamic CSS property values to reduce
513
+ > the performance overhead of Emotion.
514
+
515
+ ## Overview
516
+
517
+ The Canvas Kit styling system consists of two main packages:
518
+
519
+ - `@workday/canvas-kit-styling` - Core styling utilities for runtime use
520
+ - `@workday/canvas-kit-styling-transform` - Build-time optimization tools
521
+
522
+ These packages work together to provide a CSS-in-JS experience during development while enabling optimized static CSS in production.
523
+
524
+ ## Installation
525
+
526
+ ```sh
527
+ yarn add @workday/canvas-kit-styling
528
+ ```
529
+
530
+ ## Usage
531
+
532
+ ```tsx
533
+ import React from 'react';
534
+ import {createRoot} from 'react-dom/client';
535
+
536
+ import {createStyles} from '@workday/canvas-kit-styling';
537
+
538
+ const myStyles = createStyles({
539
+ backgroundColor: 'red',
540
+ }); // returns the CSS class name created for this style
541
+
542
+ myStyles; // something like "css-{hash}"
543
+
544
+ const domNode = document.getElementById('root');
545
+ const root = createRoot(domNode);
546
+
547
+ root.render(<div className={myStyles}>Hello!</div>);
548
+ ```
549
+
550
+ ## Style Merging
551
+
552
+ The `@workday/canvas-kit-styling` package uses `@emotion/css` to inject styles during JavaScript evaluation time rather than `@emotion/react` or `@emotion/styled` injecting when a component is rendered. This means the Emotion cache needs to be known before any style is created. In order to properly merge styles with components using either dynamic styling package, the Emotion cache must be changed on any React application. Without this, styles will not be merged correctly when static and dynamic styles are used on the same element.
553
+
554
+ If you're using Canvas Kit React, you should use the `<CanvasProvider>` which includes Emotion's `<CacheProvider>` with the proper cache already set. If you're not using Canvas Kit React, you should use the `<CacheProvider>`:
555
+
556
+ ```tsx
557
+ // ONLY use if not using the <CanvasProvider>
558
+ import {getCache} from '@workday/canvas-kit-styling';
559
+
560
+ // in your application bootstrap
561
+ const root = React.createRoot(document.getElementById('root'));
562
+
563
+ root.render(
564
+ <CacheProvider value={getCache()}>
565
+ <App />
566
+ </CacheProvider>
567
+ );
568
+ ```
569
+
570
+ ## Known issues
571
+
572
+ ### Hot Reloading
573
+
574
+ Style merging works by using CSS specificity rather than JavaScript runtime. This can cause problems during hot reloading. If you specify all styles in the same file, hot reloading shouldn't result in any style merging problems. But if you use `extends` in `createStencil` that references another file, you may run into style merge issues.
575
+
576
+ For example:
577
+
578
+ ```ts
579
+ // base.tsx file
580
+ export const baseStencil = createStencil({
581
+ base: {
582
+ color: 'red',
583
+ },
584
+ });
585
+
586
+ // extended.tsx file
587
+ import {baseStencil} from './base';
588
+
589
+ export const extendedStencil = createStencil({
590
+ extends: baseStencil,
591
+ base: {
592
+ color: 'blue',
593
+ },
594
+ });
595
+ ```
596
+
597
+ This will render correctly until you change `color` in `base.tsx` and get a hot reload:
598
+
599
+ ```tsx
600
+ // base.tsx file
601
+ export const baseStencil = createStencil({
602
+ base: {
603
+ color: 'purple',
604
+ },
605
+ });
606
+ ```
607
+
608
+ The hot reload will evaluate this update and inject a new style.
609
+
610
+ ## Development
611
+
612
+ Canvas Kit Styling comes with a runtime that doesn't need anything special for development. The runtime uses `@emotion/css` to include your styles on the page. If you plan to use static compilation, we recommend enabling in production as well so you can fix static compilation errors as you develop rather than get errors only in production builds.
613
+
614
+ ## Static compilation
615
+
616
+ The `@workday/canvas-kit-styling-transform` package can to pre-build styles. This process takes style objects and turns them into CSS strings. This process moves serialization and hashing to build time rather than browser runtime when `@emotion/css` is processing styles. This will speed up production builds at runtime.
617
+
618
+ Static compilation has stricter requirements than when doing runtime styling. The static compiler uses the TypeScript type system to statically analyze style values and thus requires value types to be known by TypeScript. See [Restrictions](#restrictions).
619
+
620
+ Static compilation may be required for server side rendering (SSR), especially when using React Server Components.
621
+
622
+ ### Hash generation
623
+
624
+ Emotion generates hashes based on the serialized style object. This means a style should always give the same hash. Static styling hashes differently. Every `createStyles` or `createStencil` call will generate a unique hash even if the style object is the same. This is required for proper style merging because static styling doesn't give single class names, but rather merges styles using CSS specificity.
625
+
626
+ For runtime development, the hash is always unique. For static compilation, the hash is based on the start and end character count in the source file of the style block. This is required for SSR so that the server and client agree on the same value during hydration. This means that while debugging, the hash depends on any code before it. If you add a `console.log` for example, the character index of a style block could shift which will generate a new hash.
627
+
628
+ ### Restrictions
629
+
630
+ The static compiler uses the TypeScript type checker. The easiest way to think of these restrictions is if TypeScript knows the exact value, the static compiler will also know. A simple example:
631
+
632
+ ```ts
633
+ // won't work - `value` is a type of `string` because `let` allows a value to be mutated
634
+ let value = 'absolute'; // `string`
635
+
636
+ const myStyles = createStyles({
637
+ position: value, // error - `string` isn't specific enough.
638
+ });
639
+
640
+ // will work - `value` is a type of `'absolute'` because `const` restricts to a string literal
641
+ const value = 'absolute'; // `'absolute'`
642
+
643
+ const myStyles = createStyles({
644
+ position: value, // works. If you mouse over `value` in your editor, you'll see the type is `'absolute'`
645
+ });
646
+ ```
647
+
648
+ More complex examples may be objects:
649
+
650
+ ```ts
651
+ // won't work. TypeScript will not understand that the position will only be `'absolute'` and makes it a `string` instead
652
+ const reusableStyles = {
653
+ position: 'absolute',
654
+ }; // `{ position: string }`
655
+
656
+ const myStyles = createStyles({
657
+ ...reusableStyles, // error - `position` is a `string` and not specific enough
658
+ });
659
+
660
+ // will work. Adding `as const` tells TypeScript the object is readonly and therefore no values can change
661
+ const reusableStyles = {
662
+ position: 'absolute',
663
+ } as const; // `{ readonly position: 'absolute' }`
664
+
665
+ const myStyles = createStyles({
666
+ ...reusableStyles, // works. If you mouse over, the position is a string literal `'absolute'`
667
+ });
668
+ ```
669
+
670
+ Functions are a little more tricky and may require generics.
671
+
672
+ ```ts
673
+ // generic makes the type be statically knowable
674
+ function getPosition<V extends 'relative' | 'absolute'>(value: V): {position: V} {
675
+ return {position: value};
676
+ }
677
+
678
+ // mouse over `position` in your editor an the type will be `{ position: 'absolute' }`
679
+ const position = getPosition('absolute'); // { position: 'absolute' }
680
+
681
+ const myStyles = createStyles({
682
+ ...getPosition('absolute'), // works - `{ position: 'absolute' }`
683
+ });
684
+ ```
685
+
686
+ ### Webpack
687
+
688
+ The `@webpack/canvas-kit-styling-transform` package comes with a webpack loader that can be added to development and/or production.
689
+
690
+ ```js
691
+ // import the transform - CJS and ESM are supported
692
+ import {StylingWebpackPlugin} from '@workday/canvas-kit-styling-transform';
693
+
694
+ // somewhere only once. For static webpack config files, this can be near the top.
695
+ // If inside Storybook, Gatsby, Next.js, etc configs, put inside the function that is called that
696
+ // returns a webpack config
697
+ const tsPlugin = const tsPlugin = new StylingWebpackPlugin({
698
+ tsconfigPath: path.resolve(__dirname, '../tsconfig.json'), // allows your TS config to be used
699
+ // A different tsconfig could be used if you want to use TS to transpile to something like ES2019 and
700
+ // also have Babel process the file.
701
+ });
702
+
703
+ // However you need to define rules.
704
+ // This is different for using webpack directly or in Storybook/Gatsby/Next.js/etc
705
+ {
706
+ rules: [
707
+ //...
708
+ {
709
+ test: /.\.tsx?$/,
710
+ use: [
711
+ {
712
+ loader: require.resolve('@workday/canvas-kit-styling-transform/webpack-loader'),
713
+ options: tsPlugin.getLoaderOptions(),
714
+ },
715
+ ],
716
+ enforce: 'pre'
717
+ },
718
+ ];
719
+ // We need to pass the plugin to Webpack's plugin list. Failure to do this will result in a
720
+ // production build hanging
721
+ plugins: [tsPlugin]
722
+ }
723
+ ```
724
+
725
+ ## Core Styling Approaches for Static Styling
726
+
727
+ For proper static styling there's two methods that you can use to apply styles.
728
+
729
+ 1. Using `createStyles` for simple object base styles.
730
+ 2. Using `createStencil` for dynamic styles and reusable components.
731
+
732
+ Both approaches are intended to be used in tandem with the `cs` prop when applying styles to our components.
733
+
734
+ ### `cs` Prop
735
+
736
+ The `cs` prop takes in a single, or an array of values that are created by the `cs` function, a string representing a CSS class name, or the return of the `createVars` function. It merges everything together and applies `className` and `style` attributes to a React element. Most of our components extend the `cs` prop so that you can statically apply styles to them.
737
+
738
+ > **Important**: While the `cs` prop accepts a style object, **this will not** be considered
739
+ > statically styling an element and you will lose the performance benefits. We plan on providing a
740
+ > babel plugin to extract these styles statically in a future version.
741
+
742
+ ```tsx
743
+ import {system} from '@workday/canvas-tokens-webs';
744
+ import {PrimaryButton} from '@workday/canvas-kit-react/button';
745
+
746
+ const styles = createStyles({color: system.color.static.red.default});
747
+
748
+ function MyComponent() {
749
+ return <PrimaryButton cs={styles}>Text</PrimaryButton>;
750
+ }
751
+ ```
752
+
753
+ ### `createStyles`
754
+
755
+ The primary utility function is the `createStyles` function. It makes a call to the `css` function from `@emotion/css`. Emotion still does most of the heavy lifting by handling the serialization, hashing, caching, and style injection.
756
+
757
+ In this example, the HTML will look like:
758
+
759
+ ```html
760
+ <div class="css-m39zwu"></div>
761
+ ```
762
+
763
+ The CSS will look like this:
764
+
765
+ ```css
766
+ .css-m39zwu {
767
+ background: var(--cnvs-sys-color-bg-primary-default);
768
+ color: var(--cnvs-sys-color-text-inverse);
769
+ }
770
+ ```
771
+
772
+ > Note:
773
+ > The `createStyles` function handles wrapping our Tokens in `var(--tokenName)`.
774
+
775
+ We're using `className` for simplicity here.
776
+
777
+ ```ts
778
+ const styles = createStyles({
779
+ background: system.color.bg.primary.default,
780
+ color: system.color.text.inverse,
781
+ });
782
+
783
+ export const CreateStyles = () => {
784
+ return <button className={styles}>Click Me</button>;
785
+ };
786
+ ```
787
+
788
+ > Caution: Performance Hit
789
+ > Do not inline the call to `createStyles` in the render function of a component.
790
+ > This will cause performance issues as a new style is inserted into the browser on every render.
791
+
792
+ ```tsx
793
+ // Bad example (inside render function)
794
+ import {system} from '@workday/canvas-tokens-webs';
795
+ import {PrimaryButton} from '@workday/canvas-kit-react/button';
796
+
797
+ function MyComponent() {
798
+ const styles = createStyles({color: system.color.static.red.default}); // Don't do this
799
+ return <PrimaryButton className={createStyles({color: system.color.static.red.default})}>Text</PrimaryButton>;
800
+ }
801
+ ```
802
+
803
+ #### When to Use `createStyles`
804
+
805
+ `createStyles` is a great way to generate static styles when styling our components that don't rely on dynamic styles. Use `createStyles` if you want to create re useable styles or need to apply simple style overrides to our components.
806
+
807
+ #### When to Use Something Else
808
+
809
+ You should use [stencils](/docs/styling-getting-started-stencils--docs) when styling our components that have complex styles and dynamic properties.
810
+
811
+ #### Proper Usage
812
+
813
+ ```tsx
814
+ // Bad example (inside render function)
815
+ import {system} from '@workday/canvas-tokens-webs';
816
+ import {PrimaryButton} from '@workday/canvas-kit-react/button';
817
+
818
+ function MyComponent() {
819
+ const styles = createStyles({color: system.color.static.red.default}); // Don't do this
820
+ return <PrimaryButton cs={styles}>Text</PrimaryButton>;
821
+ }
822
+
823
+ // Good example (outside render function)
824
+ import {system} from '@workday/canvas-tokens-webs';
825
+ import {PrimaryButton} from '@workday/canvas-kit-react/button';
826
+
827
+ const styles = createStyles({color: system.color.static.red.default});
828
+
829
+ function MyComponent() {
830
+ return <PrimaryButton cs={styles}>Text</PrimaryButton>;
831
+ }
832
+ ```
833
+
834
+ > Note:
835
+ > Most of our components support using the `cs` prop to apply the static styles.
836
+ > It merges everything together and applies `className` and `style` attributes to a React element
837
+
838
+ #### Performance Benefits
839
+
840
+ `createStyles` is performant because:
841
+
842
+ - Styles are statically evaluated when styles are defined outside the render function
843
+ - No new StyleSheets are injected during render
844
+ - It works well with the browser's selector cache
845
+
846
+ ### `createStencil`
847
+
848
+ `createStencil` a reusable function that returns `style` and `className` props in an object. A Stencil should apply to a single element. If your component has nested elements, you can youse `parts` to target those elements in the Stencil. If your component is a compound component, a stencil should be created for each subcomponent. If your component is a config component, a stencil can have nested styles.
849
+
850
+ We created Stencils as the reusable primitive of components. Stencils provide:
851
+
852
+ - `vars`: CSS variables for dynamic properties
853
+ - `base`: base styles to any component
854
+ - `modifier`: modifiers like “size = small,medium,large” or “color=red,blue,etc”
855
+ - `parts`: matching sub-elements that are part of a component
856
+ - `compound`: compound modifiers - styles that match multiple modifiers
857
+
858
+ #### Basic Example
859
+
860
+ In the example below, Stencils allow you to dynamically style elements or components based on properties.
861
+
862
+ ```tsx
863
+ const themedCardStencil = createStencil({
864
+ vars: {
865
+ // Create CSS variables for the color of the header
866
+ headerColor: '',
867
+ },
868
+ parts: {
869
+ // Allows for styling a sub element of the component that may not be exposed through the API
870
+ header: 'themed-card-header',
871
+ body: 'themed-card-body',
872
+ },
873
+ base: ({headerPart, headerColor}) => ({
874
+ padding: system.space.x4,
875
+ boxShadow: system.depth[2],
876
+ backgroundColor: system.color.bg.default,
877
+ color: system.color.text.default,
878
+ // Targets the header part via [data-part="themed-card-header"]"]
879
+ [headerPart]: {
880
+ color: headerColor,
881
+ },
882
+ }),
883
+ modifiers: {
884
+ isDarkTheme: {
885
+ // If the prop `isDarkTheme` is true, style the component and it's parts
886
+ true: ({headerPart, bodyPart}) => ({
887
+ backgroundColor: system.color.bg.contrast.default,
888
+ color: system.color.text.inverse,
889
+ [`${headerPart}, ${bodyPart}`]: {
890
+ color: system.color.text.inverse,
891
+ },
892
+ }),
893
+ },
894
+ },
895
+ });
896
+
897
+ export const CreateStencil = ({isDarkTheme, headerColor, elemProps}) => {
898
+ const [darkTheme, setIsDarkTheme] = React.useState(false);
899
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
900
+ setIsDarkTheme(event.target.checked);
901
+ };
902
+ return (
903
+ <div>
904
+ <FormField>
905
+ <FormField.Label>Toggle Dark Theme</FormField.Label>
906
+ <FormField.Input as={Switch} onChange={handleChange} checked={darkTheme} />
907
+ </FormField>
908
+
909
+ <Card cs={themedCardStencil({isDarkTheme: darkTheme, headerColor})} {...elemProps}>
910
+ <Card.Heading {...themedCardStencil.parts.header}>Canvas Supreme</Card.Heading>
911
+ <Card.Body {...themedCardStencil.parts.body}>
912
+ Our house special supreme pizza includes pepperoni, sausage, bell peppers, mushrooms,
913
+ onions, and oregano.
914
+ </Card.Body>
915
+ </Card>
916
+ </div>
917
+ );
918
+ };
919
+ ```
920
+
921
+ #### When to Use `createStencil`
922
+
923
+ - When you're styling parts of a component that rely on dynamic properties.
924
+ - When you want to create a reusable component with dynamic styles.
925
+
926
+ Use a Stencil when building reusable components that have dynamic styles and properties.
927
+
928
+ #### Concepts
929
+
930
+ **Base styles**
931
+
932
+ Base styles are always applied to a Stencil. All your default styles should go here. Base styles support psuedo selectors like `:focus-visible` or `:hover` as well as child selectors. Any selector supported by `@emotion/css` is valid here. All styles must be static and statically analyzable by the tranformer. If you need dynamic styling, look at Variables and Modifiers.
933
+
934
+ **Variables**
935
+
936
+ Variables allow some properties to be dynamic. They work by creating [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) with unique names and are applied using the [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) property of an element to locally scope an override. Since we don't have access to those names, we need a function wrapper around our style objects. This includes `base`, `modifiers`, and `compound` modifiers.
937
+
938
+ Here's a simplified example:
939
+
940
+ ```tsx
941
+ const myStencil = createStencil({
942
+ vars: {
943
+ defaultColor: 'red' // default value
944
+ nonDefaultedColor: '', // will allow for uninitialization
945
+ },
946
+ base: ({defaultColor}) => {
947
+ color: defaultColor // `defaultColor` is '--defaultColor-abc123', not 'red'
948
+ }
949
+ })
950
+
951
+ const elemProps = myStencil({color: 'blue'}) // {style: {'--defaultColor-abc123': 'blue'}}
952
+
953
+ <div {...elemProps} />
954
+ ```
955
+
956
+ This will produce the following HTML:
957
+
958
+ ```html
959
+ <style>
960
+ .css-abc123 {
961
+ --defaultColor-abc123: red;
962
+ color: var(--defaultColor-abc123);
963
+ }
964
+ </style>
965
+ <div class="css-123abc" style="--defaultColor-abc123: blue;"></div>
966
+ ```
967
+
968
+ The element will have a `color` property of `'blue'` because the element style is the highest specificity and wins over a local class name. In the "Styles" tab of developer tools, it will look like the following:
969
+
970
+ ```
971
+ element.style {
972
+ --defaultColor-abc123: blue;
973
+ }
974
+
975
+ .css-abc123 {
976
+ --defaultColor-abc123: red;
977
+ color: var(--defaultColor-abc123); // blue
978
+ }
979
+ ```
980
+
981
+ Variables are automatically added to the config of a Stencil. They share the same namespace as modifiers, so **do not have a modifier with the same name as a variable**.
982
+
983
+ > Note:
984
+ > Variables should be used sparingly. Style properties can be easily overridden without variables.
985
+ > Variables are useful if you want to expose changing properties regardless of selectors.
986
+ > For example, Buttons use variables for colors of all states (hover, active, focus, disabled, and nested icons).
987
+ > Without variables, overriding the focus color would require deeply nested selector overrides.
988
+
989
+ **Cascading Variables**
990
+
991
+ Notice the `nonDefaultedColor` is not included in the base styles like `defaultColor` was. If a variable has an empty string, it will can be uninitialized. Stencil variables with a default value will create a "cascade barrier". A cascade barrier prevents the variable from "leaking" into the component. For example, if a `Card` component was rendered within another `Card` component, the variables from the parent `Card` would not leak into the child `Card` component. But there are times where a component expects a parent component to set a CSS variable and that it should cascade to the component. An example of this is the relationship between `SystemIcon` and `Button`. The `Button` components set the `SystemIcon` variables and they should cascade into the `SystemIcon` component.
992
+
993
+ > Note:
994
+ > Non-cascade variables _could_ be initialized. If you use uninitialized variables, be sure to use a fallback in your styles.
995
+
996
+ ```tsx
997
+ const myStencil = createStencil({
998
+ vars: {
999
+ color: '', // uninitialized
1000
+ },
1001
+ base({color}) {
1002
+ return {
1003
+ // provide a fallback. A uninitialized CSS variable will fall back to `initial`.
1004
+ // for the `color` CSS property, that's most likely black (default text color)
1005
+ color: cssVar(color, 'red'),
1006
+ };
1007
+ },
1008
+ });
1009
+ ```
1010
+
1011
+ **Nested Variables**
1012
+
1013
+ Variables can be nested one level. This can be useful for colors with different psuedo selectors like `:hover` or `:focus`. Here's an example:
1014
+
1015
+ ```tsx
1016
+ const myStencil = createStencil({
1017
+ vars: {
1018
+ default: {
1019
+ color: 'red'
1020
+ },
1021
+ hover: {
1022
+ color: 'blue'
1023
+ },
1024
+ focus: {
1025
+ color: 'orange'
1026
+ }
1027
+ },
1028
+ base: ({default, hover, focus}) => {
1029
+ color: default.color,
1030
+ '&:hover': {
1031
+ color: hover.color
1032
+ },
1033
+ '&:focus': {
1034
+ color: focus.color
1035
+ }
1036
+ }
1037
+ })
1038
+ ```
1039
+
1040
+ **Modifiers**
1041
+
1042
+ Modifiers are modifications to base styles. It should be used to change the appearance of a base style. For example, a button may have a modifier for "primary" or "secondary" which may change the visual emphasis of the button. Each modifier has its own CSS class name and the stencil will return the correct CSS classes to apply to an element based on what modifiers are active.
1043
+
1044
+ ```tsx
1045
+ const buttonStencil = createStencil({
1046
+ base: {
1047
+ padding: 5
1048
+ // base styles
1049
+ },
1050
+ modifiers: {
1051
+ variant: { // modifier name
1052
+ primary: {
1053
+ background: 'blue'
1054
+ },
1055
+ secondary: {
1056
+ background: 'gray'
1057
+ }
1058
+ }
1059
+ },
1060
+ defaultModifiers: {
1061
+ variant: 'secondary'
1062
+ }
1063
+ })
1064
+
1065
+ const elemProps = myStencil({variant: 'primary'}) // {className: "css-a0 css-a1"}
1066
+
1067
+ <div {...elemProps} />
1068
+ ```
1069
+
1070
+ The HTML may look something like this:
1071
+
1072
+ ```html
1073
+ <style>
1074
+ .css-a0 {
1075
+ padding: 5px;
1076
+ }
1077
+ .css-a1 {
1078
+ background: 'blue';
1079
+ }
1080
+ .css-a2 {
1081
+ background: 'gray';
1082
+ }
1083
+ </style>
1084
+ <div class="css-a0 css-a1"></div>
1085
+ ```
1086
+
1087
+ The optional `defaultModifiers` config property will default modifiers to a value. If a modifier is not passed to the stencil, the default will be used.
1088
+
1089
+ ```tsx
1090
+ myStencil(); // className will be `'css-a0 css-a2'`
1091
+ ```
1092
+
1093
+ **Compound Modifiers**
1094
+
1095
+ A compound modifier creates a new CSS class for the intersection of two or more modifiers. Each modifier can have its own separate CSS class while the intersection is a different CSS class.
1096
+
1097
+ For example:
1098
+
1099
+ ```tsx
1100
+ const buttonStencil = createStencil({
1101
+ base: {
1102
+ padding: 10,
1103
+ // base styles
1104
+ },
1105
+ modifiers: {
1106
+ size: {
1107
+ // modifier name
1108
+ large: {
1109
+ padding: 20,
1110
+ },
1111
+ small: {
1112
+ padding: 5,
1113
+ },
1114
+ },
1115
+ iconPosition: {
1116
+ start: {
1117
+ paddingInlineStart: 5,
1118
+ },
1119
+ end: {
1120
+ paddingInlineEnd: 5,
1121
+ },
1122
+ },
1123
+ },
1124
+ compound: [
1125
+ {
1126
+ modifiers: {size: 'large', position: 'start'},
1127
+ styles: {
1128
+ paddingInlineStart: 15,
1129
+ },
1130
+ },
1131
+ {
1132
+ modifiers: {size: 'small', position: 'end'},
1133
+ styles: {
1134
+ paddingInlineEnd: 0,
1135
+ },
1136
+ },
1137
+ ],
1138
+ });
1139
+
1140
+ <div {...buttonStencil()} />
1141
+ <div {...buttonStencil({size: 'small'})} />
1142
+ <div {...buttonStencil({size: 'small', iconPosition: 'end'})} />
1143
+ ```
1144
+
1145
+ The HTML will look something like this:
1146
+
1147
+ ```html
1148
+ <style>
1149
+ .a0 {
1150
+ padding: 10px;
1151
+ }
1152
+ .a1 {
1153
+ padding: 20px;
1154
+ }
1155
+ .a2 {
1156
+ padding: 5px;
1157
+ }
1158
+ .a3 {
1159
+ padding-inline-start: 5px;
1160
+ }
1161
+ .a4 {
1162
+ padding-inline-end: 5px;
1163
+ }
1164
+ .a5 {
1165
+ padding-inline-start: 15px;
1166
+ }
1167
+ .a6 {
1168
+ padding-inline-start: 0px;
1169
+ }
1170
+ </style>
1171
+ <div class="a0"></div>
1172
+ <div class="a0 a2"></div>
1173
+ <div class="a0 a2 a4 a6"></div>
1174
+ ```
1175
+
1176
+ Notice the stencil adds all the class names that match the base, modifiers, and compound modifiers.
1177
+
1178
+ **Variables and Modifiers with same keys**
1179
+
1180
+ It is possible to have a variable and modifier sharing the same key. The Stencil will accept either the modifier option or a string. The value will be sent as a variable regardless while the modifer will only match if it is a valid modifer key.
1181
+
1182
+ ```tsx
1183
+ const buttonStencil = createStencil({
1184
+ vars: {
1185
+ width: '10px',
1186
+ },
1187
+ base({width}) {
1188
+ return {
1189
+ width: width,
1190
+ };
1191
+ },
1192
+ modifiers: {
1193
+ width: {
1194
+ zero: {
1195
+ width: '0', // overrides base styles
1196
+ },
1197
+ },
1198
+ },
1199
+ });
1200
+
1201
+ // `'zero'` is part of autocomplete
1202
+ myStencil({width: 'zero'});
1203
+ // returns {className: 'css-button css-button--width-zero', styles: { '--button-width': 'zero'}}
1204
+
1205
+ // width also accepts a string
1206
+ myStencil({width: '10px'});
1207
+ // returns {className: 'css-button', styles: { '--button-width': '10px'}}
1208
+ ```
1209
+
1210
+ #### Styling Elements via Component Parts
1211
+
1212
+ The goal of compound components is to expose one component per semantic element. Most of the time this means a 1:1 relationship of a component and DOM element. Sometimes a semantic element contains non-semantic elements for styling. An example might be a `<button>` with a icon for visual reinforcement, and a label for a semantic label. The semantic element is the `<button>` while the icon has no semantic value and the label automatically provides the semantic button with an accessible name. In order to style the icon and label elements, you have to know the DOM structure to target those specific elements in order to style it.
1213
+
1214
+ ```jsx
1215
+ import {createStencil} from '@workday/canvas-kit-styling';
1216
+
1217
+ const myButtonStencil = createStencil({
1218
+ base: {
1219
+ background: 'transparent',
1220
+ i: {
1221
+ // ...icon styles
1222
+ },
1223
+ span: {
1224
+ // ...label styles
1225
+ },
1226
+ ':hover': {
1227
+ // ...hover button styles
1228
+ i: {
1229
+ // ...hover icon styles
1230
+ },
1231
+ span: {
1232
+ // ...hover label styles
1233
+ },
1234
+ },
1235
+ },
1236
+ });
1237
+
1238
+ const MyButton = ({children, ...elemProps}) => {
1239
+ return (
1240
+ <button {...handleCsProp(elemProps, myButtonStencil())}>
1241
+ <i />
1242
+ <span>{children}</span>
1243
+ </button>
1244
+ );
1245
+ };
1246
+ ```
1247
+
1248
+ **Using Component Parts to Style Elements**
1249
+
1250
+ To style elements in the render function, we'll need to choose what elements to add the parts to. In the example below, we're able to spread the parts directly to elements. The Stencil will generate the type and value most appropriate for the context the part is used. In the Stencil, the part is represented by a string that looks like `[data-part="{partValue}"]` and in the render function, it is an object that looks like `{'data-part': partValue}`.
1251
+
1252
+ ```jsx
1253
+ import {createStencil, handleCsProp} from '@workday/canvas-kit-styling';
1254
+
1255
+ const myButtonStencil = createStencil({
1256
+ parts: {
1257
+ icon: 'my-button-icon',
1258
+ label: 'my-button-label',
1259
+ },
1260
+ base: ({iconPart, labelPart}) => ({
1261
+ background: 'transparent',
1262
+ [iconPart]: {
1263
+ // `[data-part="my-button-icon"]`
1264
+ // ...icon styles
1265
+ },
1266
+ [labelPart]: {
1267
+ // `[data-part="my-button-label"]`
1268
+ // ...label styles
1269
+ },
1270
+ '&:hover': {
1271
+ // ...hover styles for button element
1272
+ [iconPart]: {
1273
+ // ...hover styles for icon part
1274
+ },
1275
+ },
1276
+ }),
1277
+ });
1278
+
1279
+ const MyButton = ({children, ...elemProps}) => {
1280
+ return (
1281
+ <button {...handleCsProp(elemProps, myButtonStencil())}>
1282
+ <i {...myButtonStencil.parts.icon} /> {/* data-part={my-button-icon} */}
1283
+ <span {...myButtonStencil.parts.label}>{children}</span> {/* data-part={my-button-label} */}
1284
+ </button>
1285
+ );
1286
+ };
1287
+ ```
1288
+
1289
+ As a reusable component, you can use component parts to style elements that are not exposed in the API. Consumers can also use the type safe Stencil to target that element to style it as well. As a general rule, a Stencil maps to a component. Multiple Stencils per component usually means nested elements that are not targets for style overrides.
1290
+
1291
+ > Note
1292
+ > While component parts are a way to give access to elements in order to style, they should be used sparingly.
1293
+ > Using component parts increases CSS specificity.
1294
+ > A component part should not be used on a nested component that has its own Stencil. The result will be any style properties defined with a component part will have a higher specificity than other styles.
1295
+
1296
+ ## Converting from @emotion/styled
1297
+
1298
+ The most difficult part of understanding styling without Emotion's runtime is the mindset shift. You are using CSS to merge properties instead of JavaScript. This is essential to remove the runtime of Emotion. We'll use a contrived button example using `@emotion/styled` and our styling solution to step through the differences.
1299
+
1300
+ ### Button using `@emotion/styled`
1301
+
1302
+ ```tsx
1303
+ import React from 'react';
1304
+ import styled from '@emotion/styled';
1305
+
1306
+ interface ButtonProps {
1307
+ variant: 'primary' | 'secondary' | 'danger';
1308
+ size: 'large' | 'medium' | 'small';
1309
+ backgroundColor?: string;
1310
+ children?: React.ReactNode;
1311
+ }
1312
+
1313
+ const StyledButton = styled('button')<ButtonProps>(
1314
+ {
1315
+ // base styles
1316
+ fontSize: '1rem',
1317
+ display: 'flex',
1318
+ borderRadius: '1rem',
1319
+ },
1320
+ // variant styles
1321
+ ({variant, backgroundColor}) => {
1322
+ switch (variant) {
1323
+ case 'primary':
1324
+ return {
1325
+ background: backgroundColor || 'blue',
1326
+ color: 'white',
1327
+ };
1328
+ case 'secondary':
1329
+ return {
1330
+ background: backgroundColor || 'gray',
1331
+ };
1332
+ case 'danger':
1333
+ return {
1334
+ background: backgroundColor || 'red',
1335
+ };
1336
+ default:
1337
+ return {};
1338
+ }
1339
+ },
1340
+ // size styles
1341
+ ({size}) => {
1342
+ switch (size) {
1343
+ case 'large':
1344
+ return {
1345
+ fontSize: '1.4rem',
1346
+ height: '2rem',
1347
+ };
1348
+ case 'medium':
1349
+ return {
1350
+ fontSize: '1rem',
1351
+ height: '1.5rem',
1352
+ };
1353
+ case 'small':
1354
+ return {
1355
+ fontSize: '0.8rem',
1356
+ height: '1.2rem',
1357
+ };
1358
+ default:
1359
+ return {};
1360
+ }
1361
+ }
1362
+ );
1363
+
1364
+ export const EmotionButton = () => {
1365
+ return (
1366
+ <div style={{display: 'flex', flexDirection: 'column', gap: '1rem'}}>
1367
+ <div style={{display: 'flex', gap: '1rem'}}>
1368
+ <StyledButton variant="primary" size="large">
1369
+ Primary Large
1370
+ </StyledButton>
1371
+ <StyledButton variant="primary" size="medium">
1372
+ Primary Medium
1373
+ </StyledButton>
1374
+ <StyledButton variant="primary" size="small">
1375
+ Primary Small
1376
+ </StyledButton>
1377
+ </div>
1378
+ <div style={{display: 'flex', gap: '1rem'}}>
1379
+ <StyledButton variant="secondary" size="large">
1380
+ Secondary Large
1381
+ </StyledButton>
1382
+ <StyledButton variant="secondary" size="medium">
1383
+ Secondary Medium
1384
+ </StyledButton>
1385
+ <StyledButton variant="secondary" size="small">
1386
+ Secondary Small
1387
+ </StyledButton>
1388
+ </div>
1389
+ <div style={{display: 'flex', gap: '1rem'}}>
1390
+ <StyledButton variant="danger" size="large">
1391
+ Danger Large
1392
+ </StyledButton>
1393
+ <StyledButton variant="danger" size="medium">
1394
+ Danger Medium
1395
+ </StyledButton>
1396
+ <StyledButton variant="danger" size="small">
1397
+ Danger Small
1398
+ </StyledButton>
1399
+ </div>
1400
+ <div style={{display: 'flex', gap: '1rem'}}>
1401
+ <StyledButton variant="danger" size="large" backgroundColor="orange">
1402
+ Custom Large
1403
+ </StyledButton>
1404
+ <StyledButton variant="danger" size="medium" backgroundColor="orange">
1405
+ Custom Medium
1406
+ </StyledButton>
1407
+ <StyledButton variant="danger" size="small" backgroundColor="orange">
1408
+ Custom Small
1409
+ </StyledButton>
1410
+ </div>
1411
+ </div>
1412
+ );
1413
+ };
1414
+ ```
1415
+
1416
+ If we inspect each button, we'll notice each has a different class name. They all look like `css-{hash}`:
1417
+
1418
+ For example, the Primary buttons:
1419
+
1420
+ - Primary Large: `css-oqv33j`
1421
+ - Primary Medium: `css-1nhzlx`
1422
+ - Primary Small: `css-1ygk6q`
1423
+
1424
+ This means each button is a unique style sheet insert by Emotion. If we render each permutation at once, there will only be one expensive [style recalculation](https://microsoftedge.github.io/DevTools/explainers/StyleTracing/explainer.html)
1425
+
1426
+ Converting to use the Canvas Kit Styling solution means organizing a little different. In our example, it is already organized well, but conditionals might be anywhere in the style functions and will need to be organized in groups.
1427
+
1428
+ ### Button using only `createStyles`
1429
+
1430
+ What are we really trying to accomplish? [BEM](https://getbem.com/introduction) fits well with compound components. BEM stands for Block, Element, Modifer. In compound components, "Block" refers to a container component while "Element" refers to subcomponets. The "Modifer" refers to changing the appearance of a block.
1431
+
1432
+ In our example, all styles that are common to all appearances of our button. It might be `borderRadius`, `fontFamily`. We can use `createStyles` to define these styles:
1433
+
1434
+ ```ts
1435
+ const baseStyles = createStyles({
1436
+ fontSize: '1rem',
1437
+ display: 'flex',
1438
+ borderRadius: '1rem',
1439
+ });
1440
+ ```
1441
+
1442
+ The `variant` modifiers use a variable prop called `backgroundColor` which cannot be variable at runtime. We need to use a CSS Variable for this.
1443
+
1444
+ We can create modifers using `createStyles` and organize them in an object:
1445
+
1446
+ ```ts
1447
+ const modifierStyles = {
1448
+ variant: {
1449
+ primary: createStyles({
1450
+ background: `var(--background-color-button, blue)`,
1451
+ color: 'white',
1452
+ }),
1453
+ secondary: createStyles({
1454
+ background: `var(--background-color-button, gray)`,
1455
+ }),
1456
+ danger: createStyles({
1457
+ background: `var(--background-color-button, red)`,
1458
+ }),
1459
+ },
1460
+ size: {
1461
+ large: createStyles({
1462
+ fontSize: '1.4rem',
1463
+ height: '2rem',
1464
+ }),
1465
+ medium: createStyles({
1466
+ fontSize: '1rem',
1467
+ height: '1.5rem',
1468
+ }),
1469
+ small: createStyles({
1470
+ fontSize: '0.8rem',
1471
+ height: '1.2rem',
1472
+ }),
1473
+ },
1474
+ };
1475
+ ```
1476
+
1477
+ Each modifier value uses `createStyles` which returns a different class name. This means we can create a "Primary Large" button by applying these modifiers to the `className` prop of a React element:
1478
+
1479
+ ```jsx
1480
+ <button className={`${baseStyles} ${modifierStyles.variant.primary} ${modifierStyles.size.large}`}>
1481
+ Primary Large
1482
+ </button>
1483
+ ```
1484
+
1485
+ This will create a button with 3 separate class names applied. `@emotion/styled` only applies a single css class name.
1486
+
1487
+ ```html
1488
+ <!-- @emotion/styled -->
1489
+ <button class="css-108wq52">Primary Large</button>
1490
+
1491
+ <!-- createStyles -->
1492
+ <button class="css-puxv12 css-puxv13 css-puxv16">Primary Large</button>
1493
+ ```
1494
+
1495
+ If you want to change the background color, you'll have to pass it using `style`:
1496
+
1497
+ ```jsx
1498
+ <button
1499
+ className={`${baseStyles} ${modifierStyles.size.large}`}
1500
+ style={{'--color-background-button': 'orange'}}
1501
+ >
1502
+ Orange Large
1503
+ </button>
1504
+ ```
1505
+
1506
+ The output HTML will look like:
1507
+
1508
+ ```html
1509
+ <button class="css-puxv12 css-puxv16" style="--color-background-button: orange;">
1510
+ Orange Large
1511
+ </button>
1512
+ ```
1513
+
1514
+ This works because CSS Custom Properties cascade values. The `style` attribute defines styles on the element directly. This is a runtime in React that allows us to change a style without a new style block - the styles can be static, but we can still have variable property values.
1515
+
1516
+ ```tsx
1517
+ import React from 'react';
1518
+ import {createStyles} from '@workday/canvas-kit-styling';
1519
+
1520
+ interface ButtonProps {
1521
+ variant: 'primary' | 'secondary' | 'danger';
1522
+ size: 'large' | 'medium' | 'small';
1523
+ backgroundColor?: string;
1524
+ children?: React.ReactNode;
1525
+ }
1526
+
1527
+ const baseStyles = createStyles({
1528
+ fontSize: '1rem',
1529
+ display: 'flex',
1530
+ borderRadius: '1rem',
1531
+ });
1532
+
1533
+ const modifierStyles = {
1534
+ variant: {
1535
+ primary: createStyles({
1536
+ background: `var(--button-background-color, blue)`,
1537
+ color: 'white',
1538
+ }),
1539
+ secondary: createStyles({
1540
+ background: `var(--button-background-color, gray)`,
1541
+ }),
1542
+ danger: createStyles({
1543
+ background: `var(--button-background-color, red)`,
1544
+ }),
1545
+ },
1546
+ size: {
1547
+ large: createStyles({
1548
+ fontSize: '1.4rem',
1549
+ height: '2rem',
1550
+ }),
1551
+ medium: createStyles({
1552
+ fontSize: '1rem',
1553
+ height: '1.5rem',
1554
+ }),
1555
+ small: createStyles({
1556
+ fontSize: '0.8rem',
1557
+ height: '1.2rem',
1558
+ }),
1559
+ },
1560
+ };
1561
+
1562
+ const Button = ({variant, size, backgroundColor, children}: ButtonProps) => {
1563
+ const className = [baseStyles, modifierStyles.variant[variant], modifierStyles.size[size]].join(
1564
+ ' '
1565
+ );
1566
+ const style = {'--button-background-color': backgroundColor} as React.CSSProperties;
1567
+ return (
1568
+ <button className={className} style={style}>
1569
+ {children}
1570
+ </button>
1571
+ );
1572
+ };
1573
+
1574
+ export const ManualStylesButton = () => {
1575
+ return (
1576
+ <div style={{display: 'flex', flexDirection: 'column', gap: '1rem'}}>
1577
+ <div style={{display: 'flex', gap: '1rem'}}>
1578
+ <Button variant="primary" size="large">
1579
+ Primary Large
1580
+ </Button>
1581
+ <Button variant="primary" size="medium">
1582
+ Primary Medium
1583
+ </Button>
1584
+ <Button variant="primary" size="small">
1585
+ Primary Small
1586
+ </Button>
1587
+ </div>
1588
+ <div style={{display: 'flex', gap: '1rem'}}>
1589
+ <Button variant="secondary" size="large">
1590
+ Secondary Large
1591
+ </Button>
1592
+ <Button variant="secondary" size="medium">
1593
+ Secondary Medium
1594
+ </Button>
1595
+ <Button variant="secondary" size="small">
1596
+ Secondary Small
1597
+ </Button>
1598
+ </div>
1599
+ <div style={{display: 'flex', gap: '1rem'}}>
1600
+ <Button variant="danger" size="large">
1601
+ Danger Large
1602
+ </Button>
1603
+ <Button variant="danger" size="medium">
1604
+ Danger Medium
1605
+ </Button>
1606
+ <Button variant="danger" size="small">
1607
+ Danger Small
1608
+ </Button>
1609
+ </div>
1610
+ <div style={{display: 'flex', gap: '1rem'}}>
1611
+ <Button variant="danger" size="large" backgroundColor="orange">
1612
+ Custom Large
1613
+ </Button>
1614
+ <Button variant="danger" size="medium" backgroundColor="orange">
1615
+ Custom Medium
1616
+ </Button>
1617
+ <Button variant="danger" size="small" backgroundColor="orange">
1618
+ Custom Small
1619
+ </Button>
1620
+ </div>
1621
+ </div>
1622
+ );
1623
+ };
1624
+ ```
1625
+
1626
+ ### Button using all utilities
1627
+
1628
+ If we want variables that are hashed and make it easier to define and use, we have `createVars`. There are also edge cases for modifiers like allowing `undefined`, so we made a `createModifiers` function as well. Both `createModifiers` and `createVars` return a function that makes it easier to call with inputs and will return the correct output.
1629
+
1630
+ For example, `createModifiers`:
1631
+
1632
+ ```tsx
1633
+ const myModifiers = createModifiers({
1634
+ size: {
1635
+ large: 'button-large',
1636
+ small: 'button-small'
1637
+ }
1638
+ })
1639
+
1640
+ myModifiers.size.large // 'button-large'
1641
+
1642
+ // the function knows what config can be passed
1643
+ // and what restrictions each value has
1644
+ myModifiers({size: 'large'}) // 'button-large'
1645
+ myModifiers({size: 'small'}) // 'button-small'
1646
+ myModifiers() // ''
1647
+
1648
+ // in a component
1649
+ <div className={myModifiers({size: 'large'})} /> // <div class="button-large" />
1650
+ ```
1651
+
1652
+ `createVars`:
1653
+
1654
+ ```tsx
1655
+ const myVars = createVars('background', 'color')
1656
+
1657
+ myVars.color // something like `--color-{hash}`
1658
+
1659
+ // the function knows what keys are allowed
1660
+ myVars({color: 'red'}) // {'--color-{hash}': 'red'}
1661
+
1662
+ // in a component
1663
+ <div style={myVars({color: 'red'})} /> // <div style="--color-{hash}: red;">
1664
+ ```
1665
+
1666
+ ## How To Customize Styles
1667
+
1668
+ There are multiple ways to customize styles for components within Canvas Kit. The approach you choose will depend on use case.
1669
+
1670
+ ### Create Styles
1671
+
1672
+ #### Using `createStyles` with `cs` prop
1673
+
1674
+ Use `createStyles` in tandem with `cs` prop when you're overriding static styles and making small modifications to an existing Canvas Kit component like padding, color and flex properties. Take our `Text` component as an example.
1675
+
1676
+ ```tsx
1677
+ import {createStyles} from '@Workday/canvas-kit-styling';
1678
+ import {system} from '@Workday/canvas-tokens-web';
1679
+ import {Text} from '@Workday/canvas-kit-react/text';
1680
+
1681
+ const uppercaseTextStyles = createStyles({
1682
+ textTransform: 'uppercase',
1683
+ margin: system.space.x4
1684
+ })
1685
+ //...
1686
+ <Text cs={uppercaseTextStyles}>My uppercased text</Text>;
1687
+ ```
1688
+
1689
+ > **Note:** `createStyles` handles wrapping our token variables in `var(--${token})`
1690
+
1691
+ You can also apply styles created via `createStyles` via `className`.
1692
+
1693
+ ```tsx
1694
+ import {createStyles} from '@Workday/canvas-kit-styling';
1695
+ import {system} from '@Workday/canvas-tokens-web';
1696
+ import {Text} from '@Workday/canvas-kit-react/text';
1697
+
1698
+ const uppercaseTextStyles = createStyles({
1699
+ textTransform: 'uppercase',
1700
+ margin: system.space.x4
1701
+ })
1702
+ //...
1703
+ <Text className={uppercaseTextStyles}>My uppercased text</Text>;
1704
+ ```
1705
+
1706
+ If you need to dynamically apply styles based on some state or prop, use [Stencils](#stencils) instead.
1707
+
1708
+ ## Stencils
1709
+
1710
+ Stencils can be useful when applying dynamic styles or building your own reusable component.
1711
+
1712
+ ### Extending Stencils
1713
+
1714
+ [Stencils](https://workday.github.io/canvas-kit/?path=/docs/styling-getting-started-create-stencil--docs) help you organize the styling of reusable components into base styles, modifiers, and variables. The organization makes it more natural to produce static and clean CSS with optional extraction into CSS files.
1715
+
1716
+ Stencils that define variables, modifiers and base styles can be extended to create your own reusable component using Canvas Kit styles.
1717
+
1718
+ If we take `SystemIcon` component as an example, it defines `systemIconStencil` which defines styles for an icon. This stencil can be extended to build a custom icon component for your use case.
1719
+
1720
+ **Before v11** you'd have to use `systemIconStyles` function to overwrite styles for an icon:
1721
+
1722
+ ```tsx
1723
+ // Before v11
1724
+ import {systemIconStyles} from '@workday/canvas-kit-react';
1725
+ import {space} from '@workday/canvas-kit-react/tokens'; // old tokens
1726
+
1727
+ // old way of styling with Emotion styled
1728
+ const StyledNavIcon = styled('span')(({size, iconStyles}){
1729
+ display: 'inline-flex',
1730
+ pointerEvents: 'unset',
1731
+ margin: `${space.xxxs} ${space.xxxs} 0 0`,
1732
+ padding: '0',
1733
+ 'svg': {
1734
+ ...iconStyles,
1735
+ width: size,
1736
+ height: size,
1737
+ }
1738
+ });
1739
+
1740
+ const NavIcon = ({iconColor, iconHover, iconBackground, iconBackgroundHover, icon, size}) => {
1741
+ // old way of styling with systemIconStyles function
1742
+ // systemIconStyles is deprecated in v11
1743
+ const iconStyles = systemIconStyles({
1744
+ fill: iconColor,
1745
+ fillHover: iconHover,
1746
+ background: iconBackground,
1747
+ backgroundHover: iconBackgroundHover,
1748
+ });
1749
+
1750
+ // insert icon function used by platform or any other functionality here
1751
+
1752
+ return (
1753
+ <StyledNavIcon
1754
+ icon={icon}
1755
+ size={size}
1756
+ iconStyles={iconStyles}
1757
+ />
1758
+ );
1759
+ };
1760
+ ```
1761
+
1762
+ **From v11** you'd extend `systemIconStencil` to reuse its styles:
1763
+
1764
+ ```tsx
1765
+ // v11
1766
+ import {createStencil} from '@workday/canvas-kit-styling';
1767
+ import {system} from '@workday/canvas-tokens-web';
1768
+ import {systemIconStencil} from '@workday/canvas-kit-react/icon';
1769
+
1770
+ const navIconStencil = createStencil({
1771
+ // We extend `systemIconStencil` to inherit it's base styles, modifiers and variables so that we can customize it
1772
+ extends: systemIconStencil,
1773
+ vars: {
1774
+ // These variables support our styling iconHover and iconBackgroundHover
1775
+ // they can be removed later and overwritten by `cs`.
1776
+ // Also note the variables have no value. This allows for cascading styles.
1777
+ fillHover: '',
1778
+ backgroundHover: '',
1779
+ },
1780
+ base: ({fillHover, backgroundHover}) => ({
1781
+ display: 'inline-flex',
1782
+ pointerEvents: 'unset',
1783
+ // instead of using our old tokens it's better to use our new system tokens
1784
+ margin: `${system.space.x1} ${system.space.x1} 0 0`,
1785
+ padding: '0',
1786
+ '&:hover, &.hover': {
1787
+ // systemIconStencil doesn't have hover specific variables
1788
+ // so we reassigned color and backgroundColor variables using pseudo-selector
1789
+ [systemIconStencil.vars.color]: fillHover,
1790
+ [systemIconStencil.vars.backgroundColor]: backgroundHover,
1791
+ },
1792
+ }),
1793
+ });
1794
+
1795
+ // Your reusable NavIcon component using Stencils
1796
+ const NavIcon = ({
1797
+ iconColor,
1798
+ iconHover,
1799
+ iconBackground,
1800
+ iconBackgroundHover,
1801
+ icon,
1802
+ size,
1803
+ ...elemProps
1804
+ }) => {
1805
+ // insert icon function used by platform or any other functionality here
1806
+
1807
+ return (
1808
+ <span
1809
+ icon={icon}
1810
+ {...handleCsProp(
1811
+ elemProps,
1812
+ navIconStencil({
1813
+ // Because we're extending systemIconStencil, it already has a size prop and applies size to the svg's width and height
1814
+ // so we don't need to set these variables in our navIconStencil
1815
+ size,
1816
+ // systemIconStencil already has color (for icon fill) and backgroundColor variables
1817
+ // so we assigned them to our prop values
1818
+ color: iconColor,
1819
+ backgroundColor: iconBackground,
1820
+ fillHover: iconHover,
1821
+ backgroundHover: iconBackgroundHover,
1822
+ })
1823
+ )}
1824
+ />
1825
+ );
1826
+ };
1827
+ ```
1828
+
1829
+ Another example of Stencil extension and customization is our [CustomButton](https://workday.github.io/canvas-kit/?path=/story/components-buttons--docs#custom-styles) example. This example highlights the power of inheritance that you get from extending stencils.
1830
+
1831
+ ## Merging Styles
1832
+
1833
+ ### handleCsProp
1834
+
1835
+ But what about when using components that use `@emotion/react` or `@emotion/styled`? Those libraries use a different approach. Instead of multiple class names, they use a single, merged class name.
1836
+
1837
+ `handleCsProp` was created to handle integration with existing components that use the `css` prop from `@emotion/react` or the `styled` components from `@emotion/styled`. If a class name from one of those libraries is detected, style merging will follow the same rules as those libraries. Instead of multiple class names, a single class name with all matching properties is created. The `handleCsProp` also takes care of merging `style` props, `className` props, and can handle the `cs` prop:
1838
+
1839
+ ```tsx
1840
+ const myStencil = createStencil({
1841
+ // ...
1842
+ });
1843
+
1844
+ const MyComponent = elemProps => {
1845
+ return <div {...handleProps(elemProps, myStencil({}))} />;
1846
+ };
1847
+
1848
+ // All props will be merged for you
1849
+ <MyComponent style={{color: 'red'}} className="my-classname" cs={{position: 'relative'}} />;
1850
+ ```
1851
+
1852
+ `handleCsProp` will make sure the `style` prop is passed to the `div` and that the `my-classname` CSS class name appears on the `div`'s class list. Also the `cs` prop will add the appropriate styles to the element via a CSS class name. If your component needs to handle being passed a `className`, `style`, or `cs` prop, use `handleCsProp`.
1853
+
1854
+ ### mergeStyles (deprecated)
1855
+
1856
+ In v9, we used `@emotion/styled` or `@emotion/react` for all styling which is a runtime styling solution. Starting in v10, we're migrating our styling to a more static solution using `createStyles` and the `cs` prop.
1857
+
1858
+ For a transition period, we're opting for backwards compatibility. If style props are present, [styled components](https://emotion.sh/docs/styled) are used, or the [css prop](https://emotion.sh/docs/css-prop) is used in a component, Emotion's style merging will be invoked to make sure the following style precedence:
1859
+
1860
+ ```
1861
+ createStyles > CSS Prop > Styled Component > Style props
1862
+ ```
1863
+
1864
+ This will mean that any `css` prop or use of `styled` within the component tree _per element_ will cause style class merging. For example:
1865
+
1866
+ ```tsx
1867
+ import styled from '@emotion/styled';
1868
+ import {createStyles} from '@workday/canvas-kit-styling';
1869
+ import {mergeStyles} from '@workday/canvas-kit-react/layout';
1870
+
1871
+ const styles1 = createStyles({
1872
+ padding: 4,
1873
+ });
1874
+
1875
+ const styles2 = createStyles({
1876
+ padding: 12,
1877
+ });
1878
+
1879
+ const Component1 = props => {
1880
+ return <div {...mergeStyles(props, [styles1])} />;
1881
+ };
1882
+
1883
+ const Component2 = props => {
1884
+ return <Component1 cs={styles2} />;
1885
+ };
1886
+
1887
+ const Component3 = styled(Component1)({
1888
+ padding: 8,
1889
+ });
1890
+
1891
+ const Component4 = props => {
1892
+ return <Component3 cs={styles2} />;
1893
+ };
1894
+
1895
+ export default () => (
1896
+ <>
1897
+ <Component1 />
1898
+ <Component2 />
1899
+ <Component3 />
1900
+ <Component4 />
1901
+ </>
1902
+ );
1903
+ ```
1904
+
1905
+ The `styled` component API is forcing `mergeStyles` to go into Emotion merge mode, which removes the `style1` class name and creates a new class based on all the merged style properties. So `.component3` is a new class created by Emotion at render time that merges `.style1` and `{padding: 8px}`. `Component4` renders `Component3` with a `cs` prop, but `Component3` is already in merge mode and so `Component4` will also merge all styles into a new class name of `.component4` that has the styles from `.style1`, `.component3`, and `{padding: 12px}`:
1906
+
1907
+ ```html
1908
+ <head>
1909
+ <style>
1910
+ .styles1 {
1911
+ padding: 4px;
1912
+ }
1913
+ .styles2 {
1914
+ padding: 8px;
1915
+ }
1916
+ .component3 {
1917
+ padding: 4px;
1918
+ padding: 8px;
1919
+ }
1920
+ .component4 {
1921
+ padding: 4px;
1922
+ padding: 8px;
1923
+ padding: 12px;
1924
+ }
1925
+ </style>
1926
+ </head>
1927
+
1928
+ <div class="styles1"></div>
1929
+ <div class="styles1 styles2"></div>
1930
+ <div class="component3"></div>
1931
+ <div class="component4"></div>
1932
+ ```
1933
+
1934
+ The `css` prop and `styled` component APIs will rewrite the `className` React prop by iterating over all class names and seeing if any exist within the cache. If a class name does exist in the cache, the CSS properties are copied to a new style property map until all the class names are evaluated and removed from the `className` prop. Emotion will then combine all the CSS properties and inject a new `StyleSheet` with a new class name and add that class name to the element.
1935
+
1936
+ The following example shows this style merging.
1937
+
1938
+ ```tsx
1939
+ import * as React from 'react';
1940
+ import styled from '@emotion/styled';
1941
+ import {jsx} from '@emotion/react';
1942
+
1943
+ import {Flex} from '@workday/canvas-kit-react/layout';
1944
+ import {PrimaryButton} from '@workday/canvas-kit-react/button';
1945
+ import {base} from '@workday/canvas-tokens-web';
1946
+ import {createStyles, cssVar} from '@workday/canvas-kit-styling';
1947
+
1948
+ const backgroundColors = {
1949
+ cssProp: cssVar(base.orange500),
1950
+ styledComponent: cssVar(base.green500),
1951
+ styleProps: cssVar(base.magenta500),
1952
+ createStyles: cssVar(base.purple500),
1953
+ };
1954
+
1955
+ const StyledPrimaryButton = styled(PrimaryButton)({
1956
+ backgroundColor: backgroundColors.styledComponent,
1957
+ });
1958
+
1959
+ const styles = createStyles({
1960
+ backgroundColor: backgroundColors.createStyles,
1961
+ });
1962
+
1963
+ const CSSProp = () => (
1964
+ <div
1965
+ style={{
1966
+ color: 'white',
1967
+ padding: '0 4px',
1968
+ height: 40,
1969
+ width: 100,
1970
+ backgroundColor: backgroundColors.cssProp,
1971
+ }}
1972
+ >
1973
+ CSS Prop
1974
+ </div>
1975
+ );
1976
+ const StyledComponent = () => (
1977
+ <div
1978
+ style={{
1979
+ color: 'white',
1980
+ padding: '0 4px',
1981
+ height: 40,
1982
+ width: 100,
1983
+ backgroundColor: backgroundColors.styledComponent,
1984
+ }}
1985
+ >
1986
+ Styled Component
1987
+ </div>
1988
+ );
1989
+ const CreateStyles = () => (
1990
+ <div
1991
+ style={{
1992
+ color: 'white',
1993
+ padding: '0 4px',
1994
+ height: 40,
1995
+ width: 100,
1996
+ backgroundColor: backgroundColors.createStyles,
1997
+ }}
1998
+ >
1999
+ createStyles
2000
+ </div>
2001
+ );
2002
+ const StyleProps = () => (
2003
+ <div
2004
+ style={{
2005
+ color: 'white',
2006
+ padding: '0 4px',
2007
+ height: 40,
2008
+ width: 100,
2009
+ backgroundColor: backgroundColors.styleProps,
2010
+ }}
2011
+ >
2012
+ Style Props
2013
+ </div>
2014
+ );
2015
+
2016
+ // We use this object and cast to `{}` to keep TypeScript happy. Emotion extends the JSX interface
2017
+ // to include the `css` prop, but the `jsx` function type doesn't accept the `css` prop. Casting to
2018
+ // an empty object keeps TypeScript happy and the `css` prop is valid at runtime.
2019
+ const cssProp = {css: {backgroundColor: backgroundColors.cssProp}} as {};
2020
+
2021
+ export const StylingOverrides = () => {
2022
+ return (
2023
+ <Flex flexDirection="column" minHeight="100vh" gap="s">
2024
+ <Flex flexDirection="column" gap="s">
2025
+ <h2>Buttons</h2>
2026
+ <Flex flexDirection="row" gap="s">
2027
+ <PrimaryButton cs={styles}>createStyles</PrimaryButton>
2028
+ {jsx(PrimaryButton, {...cssProp}, 'CSS Prop')}
2029
+ <StyledPrimaryButton>Styled Component</StyledPrimaryButton>
2030
+ <PrimaryButton backgroundColor={backgroundColors.styleProps}>Style Props</PrimaryButton>
2031
+ </Flex>
2032
+ <div>
2033
+ {jsx(
2034
+ PrimaryButton,
2035
+ {
2036
+ ...cssProp,
2037
+ cs: styles,
2038
+ },
2039
+ 'createStyles + CSS Prop'
2040
+ )}
2041
+ </div>
2042
+ <div>
2043
+ <StyledPrimaryButton cs={styles}>createStyles + Styled Component</StyledPrimaryButton>
2044
+ </div>
2045
+ <div>
2046
+ <PrimaryButton cs={styles} backgroundColor={backgroundColors.styleProps}>
2047
+ createStyles + Style Props
2048
+ </PrimaryButton>
2049
+ </div>
2050
+ <div>
2051
+ <StyledPrimaryButton backgroundColor={backgroundColors.styleProps} cs={styles}>
2052
+ createStyles + Styled Component + Style Props
2053
+ </StyledPrimaryButton>
2054
+ </div>
2055
+ <div>
2056
+ {jsx(
2057
+ StyledPrimaryButton,
2058
+ {
2059
+ ...cssProp,
2060
+ backgroundColor: backgroundColors.styleProps,
2061
+ cs: styles,
2062
+ },
2063
+ 'createStyles + CSS Prop + Styled Component + Style Props'
2064
+ )}
2065
+ </div>
2066
+ <div>{jsx(StyledPrimaryButton, {...cssProp}, 'CSS Prop + Styled Component')}</div>
2067
+ <div>
2068
+ {jsx(
2069
+ PrimaryButton,
2070
+ {
2071
+ ...cssProp,
2072
+ backgroundColor: backgroundColors.styleProps,
2073
+ },
2074
+ 'CSS Prop + Style Props'
2075
+ )}
2076
+ </div>
2077
+ <div>
2078
+ <StyledPrimaryButton backgroundColor={backgroundColors.styleProps}>
2079
+ Styled Component + Style Props
2080
+ </StyledPrimaryButton>
2081
+ </div>
2082
+ </Flex>
2083
+ <div>
2084
+ <p>Legend:</p>
2085
+ <CreateStyles />
2086
+ <CSSProp />
2087
+ <StyledComponent />
2088
+ <StyleProps />
2089
+ </div>
2090
+ <p>
2091
+ Style Precedence: <strong>createStyles</strong> &gt; <strong>CSS Props</strong> &gt;{' '}
2092
+ <strong>Styled Component</strong> &gt; <strong>Style Props</strong>
2093
+ </p>
2094
+ </Flex>
2095
+ );
2096
+ };
2097
+ ```
2098
+
2099
+ CSS style property merging works by [CSS specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity). If two matching selectors have the same specificity, the last defined property wins. Stencils take advantage of this by making all styles have the same specificity of `0-1-0` and inserting `base` styles, then `modifiers` (in order), then `compound` modifiers (in order). This means if a property was defined in `base`, a `modifier`, and a `compound` modifier, the `compound` modifier would win because it is the last defined. This should be the expected order.
2100
+
2101
+ > Caution
2102
+ > While we support `mergeStyles` we'd advise against using this in your components so that users can get the performance benefit of static styling using utilities like `createStyles` and `createStencil` in tandem with the `cs` prop.
2103
+
2104
+ ## Canvas Kit Styling Utilities
2105
+
2106
+ A collection of helpful functions for styling with `@workday/canvas-kit-styling`. While they're fairly simple, they make styling much nicer.
2107
+
2108
+ ### Pixels to Rem
2109
+
2110
+ This function converts a `px` value (number) to `rem` (string). This keeps you from having to do any tricky mental division or write irrational numbers.
2111
+
2112
+ ```ts
2113
+ import {px2rem} from '@workday/canvas-kit-styling';
2114
+ import {system} from '@workday/canvas-tokens-web';
2115
+
2116
+ const styles = {
2117
+ // returns '0.0625rem'
2118
+ margin: px2rem(1),
2119
+ };
2120
+ ```
2121
+
2122
+ ### Calc Functions
2123
+
2124
+ Calc functions are useful for doing basic math operations with CSS `calc()` and variables. They will also wrap variables automatically in `var()`.
2125
+
2126
+ #### Add
2127
+
2128
+ This function returns a CSS `calc()` addition string.
2129
+
2130
+ ```ts
2131
+ import {calc} from '@workday/canvas-kit-styling';
2132
+ import {system} from '@workday/canvas-tokens-web';
2133
+
2134
+ const styles = {
2135
+ // returns 'calc(var(--cnvs-sys-space-x1) + 0.125rem)'
2136
+ padding: calc.add(system.space.x1, '0.125rem'),
2137
+ };
2138
+ ```
2139
+
2140
+ #### Subtract
2141
+
2142
+ This function returns a CSS `calc()` subtraction string.
2143
+
2144
+ ```ts
2145
+ import {calc} from '@workday/canvas-kit-styling';
2146
+ import {system} from '@workday/canvas-tokens-web';
2147
+
2148
+ const styles = {
2149
+ // returns 'calc(var(--cnvs-sys-space-x1) - 0.125rem)'
2150
+ padding: calc.subtract(system.space.x1, '0.125rem'),
2151
+ };
2152
+ ```
2153
+
2154
+ #### Multiply
2155
+
2156
+ This function returns a CSS `calc()` multiplication string.
2157
+
2158
+ ```ts
2159
+ import {calc} from '@workday/canvas-kit-styling';
2160
+ import {system} from '@workday/canvas-tokens-web';
2161
+
2162
+ const styles = {
2163
+ // returns 'calc(var(--cnvs-sys-space-x1) * 3)'
2164
+ padding: calc.multiply(system.space.x1, 3),
2165
+ };
2166
+ ```
2167
+
2168
+ #### Divide
2169
+
2170
+ This function returns a CSS `calc()` division string
2171
+
2172
+ ```ts
2173
+ import {calc} from '@workday/canvas-kit-styling';
2174
+ import {system} from '@workday/canvas-tokens-web';
2175
+
2176
+ const styles = {
2177
+ // returns 'calc(var(--cnvs-sys-space-x1) / 2)'
2178
+ padding: calc.divide(system.space.x1, 2),
2179
+ };
2180
+ ```
2181
+
2182
+ #### Negate
2183
+
2184
+ This function negates a CSS variable to give you the opposite value. This keeps you from having to wrap the variable in `calc()` and multiplying by `-1`.
2185
+
2186
+ ```ts
2187
+ import {calc} from '@workday/canvas-kit-styling';
2188
+ import {system} from '@workday/canvas-tokens-web';
2189
+
2190
+ const styles = {
2191
+ // returns 'calc(var(--cnvs-sys-space-x4) * -1)'
2192
+ margin: calc.negate(system.space.x4),
2193
+ };
2194
+ ```
2195
+
2196
+ ### keyframes
2197
+
2198
+ The `keyframes` function re-exports the [Emotion CSS keyframes](https://emotion.sh/docs/keyframes) function, but is compatible with a custom Emotion instance and is understood by the Static style transformer.
2199
+
2200
+ #### Example
2201
+
2202
+ ```tsx
2203
+ import {system} from '@workday/canvas-tokens-web';
2204
+ import {createComponent} from '@workday/canvas-kit-react/common';
2205
+ import {
2206
+ handleCsProp,
2207
+ keyframes,
2208
+ createStencil,
2209
+ calc,
2210
+ px2rem,
2211
+ CSProps,
2212
+ } from '@workday/canvas-kit-styling';
2213
+
2214
+ /**
2215
+ * Keyframe for the dots loading animation.
2216
+ */
2217
+ const keyframesLoading = keyframes({
2218
+ '0%, 80%, 100%': {
2219
+ transform: 'scale(0)',
2220
+ },
2221
+ '40%': {
2222
+ transform: 'scale(1)',
2223
+ },
2224
+ });
2225
+
2226
+ export const loadingStencil = createStencil({
2227
+ base: {
2228
+ display: 'inline-flex',
2229
+ gap: system.space.x2,
2230
+ width: system.space.x4,
2231
+ height: system.space.x4,
2232
+ fontSize: system.space.zero,
2233
+ borderRadius: system.shape.round,
2234
+ backgroundColor: system.color.bg.muted.softer,
2235
+ outline: `${px2rem(2)} solid transparent`,
2236
+ transform: 'scale(0)',
2237
+ animationName: keyframesLoading,
2238
+ animationDuration: calc.multiply('150ms', 35),
2239
+ animationIterationCount: 'infinite',
2240
+ animationTimingFunction: 'ease-in-out',
2241
+ animationFillMode: 'both',
2242
+ },
2243
+ });
2244
+
2245
+ /**
2246
+ * A simple component that displays three horizontal dots, to be used when some data is loading.
2247
+ */
2248
+ export const LoadingDot = createComponent('div')({
2249
+ displayName: 'LoadingDots',
2250
+ Component: ({...elemProps}: CSProps, ref, Element) => {
2251
+ return <Element ref={ref} {...handleCsProp(elemProps, loadingStencil())}></Element>;
2252
+ },
2253
+ });
2254
+ ```
2255
+
2256
+ ### injectGlobal
2257
+
2258
+ The `injectGlobal` function re-exports the [Emotion CSS injectGlobal](https://emotion.sh/docs/@emotion/css#global-styles) function, but is compatible with a custom Emotion instance and is understood by the Static style transformer. It will also wrap our CSS tokens to ensure you can inject global styles using our CSS variables.
2259
+
2260
+ ```tsx
2261
+ injectGlobal({
2262
+ ...fonts,
2263
+ 'html, body': {
2264
+ fontFamily: system.fontFamily.default,
2265
+ margin: 0,
2266
+ minHeight: '100vh',
2267
+ ...system.type.heading.large,
2268
+ },
2269
+ '#root, #root < div': {
2270
+ minHeight: '100vh',
2271
+ },
2272
+ });
2273
+ ```
2274
+
2275
+ #### Example
2276
+
2277
+ ```tsx
2278
+ import {createRoot} from 'react-dom/client';
2279
+ import {fonts} from '@workday/canvas-kit-react-fonts';
2280
+ import {system} from '@workday/canvas-tokens-web';
2281
+ import {cssVar, injectGlobal} from '@workday/canvas-kit-styling';
2282
+ import {App} from './App';
2283
+
2284
+ import '@workday/canvas-tokens-web/css/base/_variables.css';
2285
+ import '@workday/canvas-tokens-web/css/brand/_variables.css';
2286
+ import '@workday/canvas-tokens-web/css/system/_variables.css';
2287
+
2288
+ //@ts-ignore
2289
+ injectGlobal({
2290
+ ...fonts,
2291
+ 'html, body': {
2292
+ fontFamily: cssVar(system.fontFamily.default),
2293
+ margin: 0,
2294
+ minHeight: '100vh',
2295
+ },
2296
+ '#root, #root < div': {
2297
+ minHeight: '100vh',
2298
+ ...system.type.body.small,
2299
+ },
2300
+ });
2301
+
2302
+ const container = document.getElementById('root')!;
2303
+ const root = createRoot(container);
2304
+ root.render(<App />);
2305
+ ```
2306
+
2307
+ ### Custom Emotion Instance
2308
+
2309
+ Static style injection happens during the parsing stages of the files. This means when you `import` a component that uses static styling, the styles are injected immediately. This happens way before rendering, so using the Emotion [CacheProvider](https://emotion.sh/docs/cache-provider) does not work. A custom instance must be created _before_ any style utilities are called - during the bootstrapping phase of an application. We don't have a working example because it requires an isolated application, but here's an example adding a `nonce` to an application:
2310
+
2311
+ ```tsx
2312
+ // bootstrap-styles.ts
2313
+ import {createInstance} from '@workday/canvas-kit-styling';
2314
+
2315
+ // assuming this file is being called via a `script` tag and that
2316
+ // script tag has a `nonce` attribute set from the server
2317
+ createInstance({nonce: document.currentScript.nonce});
2318
+
2319
+ // index.ts
2320
+ import React from 'react';
2321
+ import ReactDOM from 'react-dom';
2322
+
2323
+ // call the bootstrap in the import list. This has the side-effect
2324
+ // of creating an instance
2325
+ import './bootstrap-styles';
2326
+
2327
+ import App from './App';
2328
+
2329
+ const root = ReactDOM.createRoot(document.querySelector('#root'));
2330
+
2331
+ root.render(<App />);
2332
+
2333
+ // App.tsx
2334
+ import React from 'react';
2335
+
2336
+ // The following will create and inject styles. We cannot adjust
2337
+ // the Emotion instance after this import
2338
+ import {PrimaryButton} from '@workday/canvas-kit-react/button';
2339
+
2340
+ // if we call `createInstance` here, we'll get a warning in
2341
+ // development mode
2342
+
2343
+ export default () => {
2344
+ return <PrimaryButton>Button</PrimaryButton>;
2345
+ };
2346
+ ```