```
## How To Customize Styles
There are multiple ways to customize styles for components within Canvas Kit. The approach you choose will depend on use case.
### Create Styles
#### Using `createStyles` with `cs` prop
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.
```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;
```
> **Note:** `createStyles` handles wrapping our token variables in `var(--${token})`
You can also apply styles created via `createStyles` via `className`.
```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;
```
If you need to dynamically apply styles based on some state or prop, use [Stencils](#stencils) instead.
## Stencils
Stencils can be useful when applying dynamic styles or building your own reusable component.
### Extending Stencils
[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.
Stencils that define variables, modifiers and base styles can be extended to create your own reusable component using Canvas Kit styles.
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.
**Before v11** you'd have to use `systemIconStyles` function to overwrite styles for an icon:
```tsx
// Before v11
import {systemIconStyles} from '@workday/canvas-kit-react';
import {space} from '@workday/canvas-kit-react/tokens'; // old tokens
// old way of styling with Emotion styled
const StyledNavIcon = styled('span')(({size, iconStyles}){
display: 'inline-flex',
pointerEvents: 'unset',
margin: `${space.xxxs} ${space.xxxs} 0 0`,
padding: '0',
'svg': {
...iconStyles,
width: size,
height: size,
}
});
const NavIcon = ({iconColor, iconHover, iconBackground, iconBackgroundHover, icon, size}) => {
// old way of styling with systemIconStyles function
// systemIconStyles is deprecated in v11
const iconStyles = systemIconStyles({
fill: iconColor,
fillHover: iconHover,
background: iconBackground,
backgroundHover: iconBackgroundHover,
});
// insert icon function used by platform or any other functionality here
return (
);
};
```
**From v11** you'd extend `systemIconStencil` to reuse its styles:
```tsx
// v11
import {createStencil} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
import {systemIconStencil} from '@workday/canvas-kit-react/icon';
const navIconStencil = createStencil({
// We extend `systemIconStencil` to inherit it's base styles, modifiers and variables so that we can customize it
extends: systemIconStencil,
vars: {
// These variables support our styling iconHover and iconBackgroundHover
// they can be removed later and overwritten by `cs`.
// Also note the variables have no value. This allows for cascading styles.
fillHover: '',
backgroundHover: '',
},
base: ({fillHover, backgroundHover}) => ({
display: 'inline-flex',
pointerEvents: 'unset',
// instead of using our old tokens it's better to use our new system tokens
margin: `${system.space.x1} ${system.space.x1} 0 0`,
padding: '0',
'&:hover, &.hover': {
// systemIconStencil doesn't have hover specific variables
// so we reassigned color and backgroundColor variables using pseudo-selector
[systemIconStencil.vars.color]: fillHover,
[systemIconStencil.vars.backgroundColor]: backgroundHover,
},
}),
});
// Your reusable NavIcon component using Stencils
const NavIcon = ({
iconColor,
iconHover,
iconBackground,
iconBackgroundHover,
icon,
size,
...elemProps
}) => {
// insert icon function used by platform or any other functionality here
return (
);
};
```
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.
## Merging Styles
### handleCsProp
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.
`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:
```tsx
const myStencil = createStencil({
// ...
});
const MyComponent = elemProps => {
return
;
};
// All props will be merged for you
;
```
`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`.
### mergeStyles (deprecated)
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.
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:
```
createStyles > CSS Prop > Styled Component > Style props
```
This will mean that any `css` prop or use of `styled` within the component tree _per element_ will cause style class merging. For example:
```tsx
import styled from '@emotion/styled';
import {createStyles} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';
const styles1 = createStyles({
padding: 4,
});
const styles2 = createStyles({
padding: 12,
});
const Component1 = props => {
return
;
};
const Component2 = props => {
return
;
};
const Component3 = styled(Component1)({
padding: 8,
});
const Component4 = props => {
return
;
};
export default () => (
<>
>
);
```
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}`:
```html
```
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.
The following example shows this style merging.
```tsx
import * as React from 'react';
import styled from '@emotion/styled';
import {jsx} from '@emotion/react';
import {Flex} from '@workday/canvas-kit-react/layout';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {base} from '@workday/canvas-tokens-web';
import {createStyles, cssVar} from '@workday/canvas-kit-styling';
const backgroundColors = {
cssProp: cssVar(base.orange500),
styledComponent: cssVar(base.green500),
styleProps: cssVar(base.magenta500),
createStyles: cssVar(base.purple500),
};
const StyledPrimaryButton = styled(PrimaryButton)({
backgroundColor: backgroundColors.styledComponent,
});
const styles = createStyles({
backgroundColor: backgroundColors.createStyles,
});
const CSSProp = () => (
CSS Prop
);
const StyledComponent = () => (
Styled Component
);
const CreateStyles = () => (
createStyles
);
const StyleProps = () => (
Style Props
);
// We use this object and cast to `{}` to keep TypeScript happy. Emotion extends the JSX interface
// to include the `css` prop, but the `jsx` function type doesn't accept the `css` prop. Casting to
// an empty object keeps TypeScript happy and the `css` prop is valid at runtime.
const cssProp = {css: {backgroundColor: backgroundColors.cssProp}} as {};
export const StylingOverrides = () => {
return (
Buttons
createStyles
{jsx(PrimaryButton, {...cssProp}, 'CSS Prop')}
Styled Component
Style Props
{jsx(
PrimaryButton,
{
...cssProp,
cs: styles,
},
'createStyles + CSS Prop'
)}
createStyles + Styled Component
createStyles + Style Props
createStyles + Styled Component + Style Props
{jsx(
StyledPrimaryButton,
{
...cssProp,
backgroundColor: backgroundColors.styleProps,
cs: styles,
},
'createStyles + CSS Prop + Styled Component + Style Props'
)}
{jsx(StyledPrimaryButton, {...cssProp}, 'CSS Prop + Styled Component')}
{jsx(
PrimaryButton,
{
...cssProp,
backgroundColor: backgroundColors.styleProps,
},
'CSS Prop + Style Props'
)}
Styled Component + Style Props
Style Precedence: createStyles > CSS Props >{' '}
Styled Component > Style Props
);
};
```
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.
> Caution
> 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.
## Canvas Kit Styling Utilities
A collection of helpful functions for styling with `@workday/canvas-kit-styling`. While they're fairly simple, they make styling much nicer.
### Pixels to Rem
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.
```ts
import {px2rem} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns '0.0625rem'
margin: px2rem(1),
};
```
### Calc Functions
Calc functions are useful for doing basic math operations with CSS `calc()` and variables. They will also wrap variables automatically in `var()`.
#### Add
This function returns a CSS `calc()` addition string.
```ts
import {calc} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns 'calc(var(--cnvs-sys-space-x1) + 0.125rem)'
padding: calc.add(system.space.x1, '0.125rem'),
};
```
#### Subtract
This function returns a CSS `calc()` subtraction string.
```ts
import {calc} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns 'calc(var(--cnvs-sys-space-x1) - 0.125rem)'
padding: calc.subtract(system.space.x1, '0.125rem'),
};
```
#### Multiply
This function returns a CSS `calc()` multiplication string.
```ts
import {calc} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns 'calc(var(--cnvs-sys-space-x1) * 3)'
padding: calc.multiply(system.space.x1, 3),
};
```
#### Divide
This function returns a CSS `calc()` division string
```ts
import {calc} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns 'calc(var(--cnvs-sys-space-x1) / 2)'
padding: calc.divide(system.space.x1, 2),
};
```
#### Negate
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`.
```ts
import {calc} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns 'calc(var(--cnvs-sys-space-x4) * -1)'
margin: calc.negate(system.space.x4),
};
```
### keyframes
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.
#### Example
```tsx
import {system} from '@workday/canvas-tokens-web';
import {createComponent} from '@workday/canvas-kit-react/common';
import {
handleCsProp,
keyframes,
createStencil,
calc,
px2rem,
CSProps,
} from '@workday/canvas-kit-styling';
/**
* Keyframe for the dots loading animation.
*/
const keyframesLoading = keyframes({
'0%, 80%, 100%': {
transform: 'scale(0)',
},
'40%': {
transform: 'scale(1)',
},
});
export const loadingStencil = createStencil({
base: {
display: 'inline-flex',
gap: system.space.x2,
width: system.space.x4,
height: system.space.x4,
fontSize: system.space.zero,
borderRadius: system.shape.round,
backgroundColor: system.color.bg.muted.softer,
outline: `${px2rem(2)} solid transparent`,
transform: 'scale(0)',
animationName: keyframesLoading,
animationDuration: calc.multiply('150ms', 35),
animationIterationCount: 'infinite',
animationTimingFunction: 'ease-in-out',
animationFillMode: 'both',
},
});
/**
* A simple component that displays three horizontal dots, to be used when some data is loading.
*/
export const LoadingDot = createComponent('div')({
displayName: 'LoadingDots',
Component: ({...elemProps}: CSProps, ref, Element) => {
return
;
},
});
```
### injectGlobal
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.
```tsx
injectGlobal({
...fonts,
'html, body': {
fontFamily: system.fontFamily.default,
margin: 0,
minHeight: '100vh',
...system.type.heading.lg,
},
'#root, #root < div': {
minHeight: '100vh',
},
});
```
#### Example
```tsx
import {createRoot} from 'react-dom/client';
import {fonts} from '@workday/canvas-kit-react-fonts';
import {system} from '@workday/canvas-tokens-web';
import {cssVar, injectGlobal} from '@workday/canvas-kit-styling';
import {App} from './App';
import '@workday/canvas-tokens-web/css/base/_variables.css';
import '@workday/canvas-tokens-web/css/brand/_variables.css';
import '@workday/canvas-tokens-web/css/component/_variables.css';
import '@workday/canvas-tokens-web/css/system/_variables.css';
//@ts-ignore
injectGlobal({
...fonts,
'html, body': {
fontFamily: cssVar(system.fontFamily.default),
margin: 0,
minHeight: '100vh',
},
'#root, #root < div': {
minHeight: '100vh',
...system.type.body.sm,
},
});
const container = document.getElementById('root')!;
const root = createRoot(container);
root.render(
);
```
### Custom Emotion Instance
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:
```tsx
// bootstrap-styles.ts
import {createInstance} from '@workday/canvas-kit-styling';
// assuming this file is being called via a `script` tag and that
// script tag has a `nonce` attribute set from the server
createInstance({nonce: document.currentScript.nonce});
// index.ts
import React from 'react';
import ReactDOM from 'react-dom';
// call the bootstrap in the import list. This has the side-effect
// of creating an instance
import './bootstrap-styles';
import App from './App';
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(
);
// App.tsx
import React from 'react';
// The following will create and inject styles. We cannot adjust
// the Emotion instance after this import
import {PrimaryButton} from '@workday/canvas-kit-react/button';
// if we call `createInstance` here, we'll get a warning in
// development mode
export default () => {
return
Button;
};
```