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';
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.
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.
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.
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
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.
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
function, but is compatible with a custom Emotion instance and is understood by the Static style
transformer.
Example
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 <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.large,
},
'#root, #root < div': {
minHeight: '100vh',
},
});Example
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/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.small,
},
});
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>;
};Can't Find What You Need?
Check out our FAQ section which may help you find the information you're looking for. For further information, contact the #ask-canvas-design or #ask-canvas-kitchannels on Slack.