Style props are a convenient mechanism for modifying styles directly on components with CSS-in-JS. Unfortunately, they also come with a significant runtime cost which negatively impacts our user experience. Because of those concerns, we've introduced new styling utilities that allow you to get the convenience of CSS-in-JS, but has much better runtime performance.
To start, we'll work through the process of migrating a Box
component.
In the example below, we'll walk through the steps needed to migrate a simple Box
component.
import { Box, BoxProps } from "@workday/canvas-kit-react/layout";
export const Container = ({ children, ...elemProps }: BoxProps) => {
<Box
as="section"
padding="l"
backgroundColor="frenchVanilla100"
border="solid 1px"
borderColor="soap500"
borderRadius="m"
color="blackPepper300"
depth={1}
{...elemProps}
>
{children}
</Box>;
};
- Migrate style props to
createStyles
- Update token values to use our new tokens
- Update
Box
component to a semantic HTML element
First, we'll need to extract our style props to object styles and convert them to object styles using our createStyles
function.
Before with Box
import { Box, BoxProps } from "@workday/canvas-kit-react/layout";
export const Container = ({ children, ...elemProps }: BoxProps) => {
<Box
as="section"
padding="l"
backgroundColor="frenchVanilla100"
border="solid 1px"
borderColor="soap500"
borderRadius="m"
color="blackPepper300"
depth={1}
{...elemProps}
>
{children}
</Box>;
};
After with createStyles
import { Box, BoxProps } from "@workday/canvas-kit-react/layout";
import { createStyles } from "@workday/canvas-kit-styling";
const containerStyles = createStyles({
padding: "l",
backgroundColor: "frenchVanilla100",
border: "solid 1px",
borderColor: "soap500",
borderRadius: "m",
color: "blackPepper300",
depth: 1,
});
export const Container = ({ children, ...elemProps }: BoxProps) => {
return (
// createStyles generates a string value, which we can pass to the className prop.
<Box as="section" className={containerStyles} {...elemProps}>
{children}
</Box>
);
};
Notice in the updated example our styles are defined outside the component. That's exactly what we want! If our styles were defined within the component they would be built at render time which is expensive for performance. Keeping styles outside the component allows them to be built at compile time instead.
Also notice we're passing containerStyles
to the className
prop. createStyles
uses @emotion/css
under the hood to generate a unique hashed CSS class name. We're off to a good start! Now it's time to move to step 2.
Now that we've moved our styles into createStyles
we need to move to step two: update the token values. Style props allowed you to use shorthand names for tokens, but those won't apply styles correctly when using createStyles
. Also, those shorthand names reference our old tokens from @workday/canvas-kit-react/tokens
. To do this, we'll need to import the new tokens package, @workday/canvas-tokens-web
, and translate the old tokens to the new tokens based on their values.
We'll also make an important update by moving from base
tokens to system
tokens. System tokens are themeable, dynamic values with semantic names. This makes it easier to understand the token's purpose and adjust the value over time – less work for you down the road as styles evolve.
To update these values, we'll reference the tables in the Token Reference Tables doc. With those tables in mind, we can update our token values. Here's how we'll go about it:
- Old space tokens will map to the new space tokens
- Old border radius tokens will map to the new shape tokens
- Old depth tokens will map to new depth tokens
- Color tokens will need to be mapped contextually
- find a new color token that matches the RGBA or hex value
- if you can't find a system token that works, use a new base token instead.
- Use
px2rem
to convert pixel values to rem values
Before with the old tokens:
import { createStyles } from "@workday/canvas-kit-styling";
const containerStyles = createStyles({
padding: "l",
backgroundColor: "frenchVanilla100",
borderColor: "soap500",
borderRadius: "m",
color: "blackPepper300",
depth: 1,
});
After with the new tokens:
import { createStyles, px2rem } from "@workday/canvas-kit-styling";
import { system } from "@workday/canvas-tokens-web";
const containerStyles = createStyles({
padding: system.space.x8,
// Because we are updating a background color, we're going to use our system `bg` color tokens.
backgroundColor: system.color.bg.default,
// We're using the `px2rem` function to convert our 1px value to a rem value.
border: `solid ${px2rem(1)}`,
// Because we are updating a border color, we're going to use our system `border` color tokens.
borderColor: system.color.border.container,
borderRadius: system.shape.x1,
color: "blackPepper300",
// Notice we updated `depth` to `boxShadow` below, as `depth` is not a valid CSS property.
boxShadow: system.depth[1],
});
If you can't find a system token that's a good match, use a new base token instead. Here's an example:
Before with style props
import { Box } from "@workday/canvas-kit-react/layout";
const CustomComponent = (props) => {
return <Box backgroundColor="berrySmoothie100" color="berrySmoothie600" {...props} />
}
After with createStyles
import { createStyles } from "@workday/canvas-kit-styling";
import { base } from "@workday/canvas-tokens-web";
const customStyles = createStyles({
backgroundColor: base.berrySmoothie100,
color: base.berrySmoothie600,
})
const CustomComponent = (props) => {
return <div className={customStyles} {...props} />
}
Now that we have our styles sorted, we're ready to remove Box
entirely and replace it with a semantic HTML element. By default, Box
renders a <div>
element, but in this example, our as
prop overrides that value and sets it to section
.
Before with Box
import { Box, BoxProps } from "@workday/canvas-kit-react/layout";
import { createStyles } from "@workday/canvas-kit-styling";
import { system } from "@workday/canvas-tokens-web";
const containerStyles = createStyles({
padding: system.space.x8,
backgroundColor: system.color.bg.default,
border: `solid ${px2rem(1)}`,
borderColor: system.color.border.container,
borderRadius: system.shape.x1,
color: "blackPepper300",
boxShadow: system.depth[1],
});
export const Container = ({ children, ...elemProps }: BoxProps) => {
return (
<Box as="section" className={containerStyles} {...elemProps}>
{children}
</Box>
);
};
After with section
import { createStyles } from "@workday/canvas-kit-styling";
import { system } from "@workday/canvas-tokens-web";
const containerStyles = createStyles({
padding: system.space.x8,
backgroundColor: system.color.bg.default,
border: `solid ${px2rem(1)}`,
borderColor: system.color.border.container,
borderRadius: system.shape.x1,
color: "blackPepper300",
boxShadow: system.depth[1],
});
export const Container = ({ children, ...elemProps }) => {
return (
<section className={containerStyles} {...elemProps}>
{children}
</section>
);
};
Flex
is very similar to Box
, but it has special Flexbox props that make layout simple. We can refactor it with createStyles
the samw way we refactored Box
with minor alterations.
Before with Flex
import { Flex, FlexProps } from "@workday/canvas-kit-react/layout";
const ToastCard = ({ children, ...props }) => {
return (
<Flex
position="absolute"
flexDirection="column"
top="30%"
left="10%"
padding="m"
>
<Flex.Item as="h2" flex={1}>
Heading
</Flex.Item>
<Flex.Item as="p" flex={2}>
{children}
</Flex.Item>
</Flex>
);
};
After with createStyles
import { createStyles } from "@workday/canvas-kit-styling";
import { system } from "@workday/canvas-tokens-web";
const toastStyles = createStyles({
// We always need to add `display: "flex"` or `display: "inline-flex" added to flex component styles to ensure they work correctly. By default, it uses `display: 'flex'.
display: "flex",
position: "absolute",
flexDirection: "column",
top: "30%",
left: "10%",
padding: system.space.x6,
});
// `Flex.Item` styles don't need `display: flex` added.
const headingStyles = createStyles({
flex: 1,
});
const bodyStyles = createStyles({
flex: 2,
});
const ToastCard = ({ children, ...props }) => {
return (
<div className={toastStyles} {...props}>
<h2 className={headingStyles}>Heading</h2>
<p className={bodyStyles}>{children}</p>
</div>
);
};
You can also migrate styles from the style
prop to createStyles
. Here's a good example:
Before with the style
prop:
import {space, color} from '@workday/canvas-kit-react/common';
const CustomHeading = (props) => {
return (
<h2 style={{ color: color.blackPepper400, lineHeight: space.s, margin: 0 }} {...props} />
)
}
After with createStyles
:
import { createStyles } from "@workday/canvas-kit-styling";
import { system } from "@workday/canvas-tokens-web";
const headingStyles = createStyles({
color: system.color.fg.strong,
lineHeight: system.space.x4,
margin: 0,
})
const CustomHeading = (props) => {
return (
<h2 className={headingStyles} {...props} />
)
}
Sometimes you'll have common styles in an object that need to be shared across multiple elements or components. This can feel a bit unintuitive at first with createStyles
, but here's a good example of how to solve it.
Before with Flex
import {Flex} from '@workday/canvas-kit-react/layout';
const commonStyles = {
color: 'blackPepper500',
minHeight: 'l',
minWidth: '12px',
padding: 'm',
}
const Section = () => {
return (
<Flex gap="s" flexDirection="column">
<Flex.Item flex={1} backgroundColor="blueberry100" {...commonStyles}>Part One</Flex.Item>
<Flex.Item flex={2} backgroundColor="sourLemon100"{...commonStyles}>Part Two</Flex.Item>
<Flex.Item flex={1} backgroundColor="cinnamon100" {...commonStyles}>Part Three</Flex.Item>
<Flex.Item flex={2} backgroundColor="greenApple100" {...commonStyles}>Part Four</Flex.Item>
</Flex>
)
}
After with createStyles
:
import { createStyles } from "@workday/canvas-kit-styling";
import { base, system } from "@workday/canvas-tokens-web";
const sectionStyles = createStyles({
display: 'flex',
flexDirection: 'column',
gap: system.space.x4,
});
const commonStyles = {
color: system.color.fg.stronger,
minHeight: system.space.x8,
minWidth: system.space.x3,
padding: system.space.x6,
}
const partOneStyles = createStyles({
...commonStyles,
backgroundColor: base.blueberry100,
flex: 1,
});
const partTwoStyles = createStyles({
...commonStyles,
backgroundColor: base.sourLemon100,
flex: 2,
});
const partThreeStyles = createStyles({
...commonStyles,
backgroundColor: base.cinnamon100,
flex: 1,
});
const partFourStyles = createStyles({
...commonStyles,
backgroundColor: base.greenApple100,
flex: 2,
});
const Section = () => {
return (
<div className={sectionStyles}>
<div className={partOneStyles}>Part One</div>
<div className={partTwoStyles}>Part Two</div>
<div className={partThreeStyles}>Part Three</div>
<div className={partFourStyles}>Part Four</div>
</div>
)
}
Canvas Kit also provides a styled
function in @workday/canvas-kit-react/common
. It was effectively a wrapper around Emotion's styled
function that also used rtl-css-js
to support RTL (right-to-left) styles. But we no longer need this because modern browsers natively support CSS Logical Properties. Our styled
function should be updated to use our new createStyles
utility. Thankfully because both functions use object styles, the translation is fairly straightforward. The major difference is styled
returns a styled React component, and createStyles
returns a hashed CSS class name (a string) that can be applied to a component. Let's look at an example of how to update:
Note: Migrating to CSS Logical Properties
There are many CSS logical properties, but the most common ones you'll run into are listed below.
Property Name Logical Property Name marginTop
marginBlockStart
marginBottom
marginBlockEnd
marginLeft
marginInlineStart
marginRight
marginInlineEnd
paddingTop
paddingBlockStart
paddingBottom
paddingBlockEnd
paddingLeft
paddingInlineStart
paddingRight
paddingInlineEnd
top
insetBlockStart
bottom
insetBlockEnd
left
marginInlineStart
right
marginInlineEnd
Before with Canvas Kit's styled
function:
import {color, space, styled} from '@workday/canvas-kit-react/common';
const HeadingContainer = styled('div')({
backgroundColor: color.frenchVanilla100,
marginLeft: space.s,
padding: space.m,
border: `solid 1px ${color.soap500}`,
});
export const ProfileSection = ({ heading, children, ...props }) => {
return (
<section {...props}>
<HeadingContainer>
<h2>Profile Heading</h2>
</HeadingContainer>
{children}
</section>
)
}
After with Canvas Kit's createStyles
function:
import { createStyles } from "@workday/canvas-kit-styling";
import { system } from "@workday/canvas-tokens-web";
const headingContainerStyles = createStyles({
backgroundColor: system.color.bg.default,
// Rememeber you'll need to convert `marginLeft` to `marginInlineBlock` for RTL support
marginInlineBlock: system.space.x4,
padding: system.space.x6,
border: `solid 1px ${system.color.border.container}`,
});
export const ProfileSection = ({ heading, children, ...props }) => {
return (
<section {...props}>
<div className={headingContainerStyles}>
<h2>{heading}</h2>
</div>
{children}
</section>
)
}
If you're styling a Canvas Kit component, you can use the cs
prop instead of passing styles to className
. Here's an example:
Before with Canvas Kit components using style props:
import { Text } from '@workday/canvas-kit-react/text';
import { Card } from '@workday/canvas-kit-react/card';
const CustomCard = (props) => {
return (
<Card backgroundColor="blackPepper400">
<Text
typeLevel="title.medium"
color="frenchVanilla100"
lineHeight="2rem"
marginBottom="s"
>
Card Text
</Text>
</Card>
)
}
After with the cs
prop on Canvas Kit components.
import { Text } from '@workday/canvas-kit-react/text';
import { Card } from '@workday/canvas-kit-react/card';
import { createStyles } from "@workday/canvas-kit-styling";
import { system } from "@workday/canvas-tokens-web";
const cardContainerStyles = createStyles({
backgroundColor: system.color.bg.contrast.default,
})
const cardTextStyles = createStyles({
...system.type.title.medium,
color: system.color.fg.inverse,
lineHeight: '2rem',
marginBottom: system.space.x4,
})
const CustomCard = (props) => {
return (
<Card cs={cardContainerStyles}>
<Text cs={cardTextStyles}>
Card Text
</Text>
</Card>
)
}
- What if I need dynamic styles?
- What if I want my components styles to be extensible or composable?