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.
import {px2rem} from '@workday/canvas-kit-styling';
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.
import {calc} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns 'calc(var(--cnvs-sys-padding-xxs) + 0.125rem)'
padding: calc.add(system.padding.xxs, '0.125rem'),
};Subtract
This function returns a CSS calc() subtraction string.
import {calc} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns 'calc(var(--cnvs-sys-padding-xxs) - 0.125rem)'
padding: calc.subtract(system.padding.xxs, '0.125rem'),
};Multiply
This function returns a CSS calc() multiplication string.
import {calc} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns 'calc(var(--cnvs-sys-padding-xxs) * 3)'
padding: calc.multiply(system.padding.xxs, 3),
};Divide
This function returns a CSS calc() division string
import {calc} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns 'calc(var(--cnvs-sys-padding-xxs) / 2)'
padding: calc.divide(system.padding.xxs, 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.
import {calc} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const styles = {
// returns 'calc(var(--cnvs-sys-gap-md) * -1)'
margin: calc.negate(system.gap.md),
};colorSpace
Darken
A utility that should be used for interactive states on buttons and links.
It will return color-mix() and the result is mixing the first color and the mixin color together in the srgb colorspace by a given amount.
It takes a single options object with four properties:
color: The color that will be darkened (this is typically the “base” color on the given element).fallback: A fallback color if the first color is not valid or not defined.mixinColor: The color that will be mixed in with the first color (or fallback color).mixinValue: The percentage of the mixin color that will be added to the first color (or fallback color).
import {colorSpace, createStyles} from '@workday/canvas-kit-styling';
import {brand, system} from '@workday/canvas-tokens-web';
const styles = createStyles({
backgroundColor: system.color.brand.accent.primary,
'&:hover': {
backgroundColor: colorSpace.darken({
color: system.color.brand.accent.primary,
fallback: brand.primary.darkest,
mixinColor: system.color.accent.overlay.mixin,
mixinValue: system.opacity.accent.hover,
}),
},
});Hover
A utility that should be used for hover interactive states on buttons and links.
It will return color-mix() and the result is mixing the first color and the mixin color together in the srgb colorspace by a given amount.
It takes a single options object with three properties:
color: The color that will be darkened (this is typically the “base” color on the given element).fallback: A fallback color if the first color is not valid or not defined.colorType: A string that will determine where the mixin color and the mixin percentage comes from in tokens (i.e.system.color.accent....,system.color.surface....,system.opacity.accent....orsystem.opacity.surface....).
import {colorSpace, createStyles} from '@workday/canvas-kit-styling';
import {brand, system} from '@workday/canvas-tokens-web';
const styles = createStyles({
backgroundColor: system.color.brand.accent.primary,
'&:hover': {
backgroundColor: colorSpace.hover({
color: system.color.brand.accent.primary,
fallback: brand.primary800,
colorType: 'accent',
}),
},
});Pressed
A utility that should be used for active or pressed interactive states on buttons and links.
It will return color-mix() and the result is mixing the first color and the mixin color together in the srgb colorspace by a given amount.
It takes a single options object with three properties:
color: The color that will be darkened (this is typically the “base” color on the given element).fallback: A fallback color if the first color is not valid or not defined.colorType: A string that will determine where the mixin color and the mixin percentage comes from in tokens (i.e.system.color.accent....,system.color.surface....,system.opacity.accent....orsystem.opacity.surface....).
import {colorSpace, createStyles} from '@workday/canvas-kit-styling';
import {brand, system} from '@workday/canvas-tokens-web';
const styles = createStyles({
backgroundColor: system.color.brand.accent.primary,
'&:active': {
backgroundColor: colorSpace.pressed({
color: system.color.brand.accent.primary,
fallback: brand.primary800,
colorType: 'accent',
}),
},
});keyframes
The keyframes function re-exports the Emotion CSS keyframes
function, but is compatible with a custom Emotion instance and is understood by the Static style
transformer.
Example
import {createComponent} from '@workday/canvas-kit-react/common';
import {
CSProps,
calc,
createStencil,
handleCsProp,
keyframes,
px2rem,
} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
/**
* 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.gap.sm,
width: system.size.xxxs,
height: system.size.xxxs,
fontSize: '0',
borderRadius: system.shape.full,
backgroundColor: system.color.accent.muted.soft,
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 <Element ref={ref} {...handleCsProp(elemProps, loadingStencil())}></Element>;
},
});injectGlobal
The injectGlobal function re-exports the
Emotion CSS injectGlobal 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.
injectGlobal({
...fonts,
'html, body': {
fontFamily: system.fontFamily.default,
margin: 0,
minHeight: '100vh',
...system.type.heading.lg,
},
'#root, #root < div': {
minHeight: '100vh',
},
});Example
import {createRoot} from 'react-dom/client';
import {fonts} from '@workday/canvas-kit-react-fonts';
import {cssVar, injectGlobal} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
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';
import {App} from './App';
//@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(<App />);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 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:
// 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 />);
// 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 <PrimaryButton>Button</PrimaryButton>;
};