Skip to Content

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:

  1. color: The color that will be darkened (this is typically the “base” color on the given element).
  2. fallback: A fallback color if the first color is not valid or not defined.
  3. mixinColor: The color that will be mixed in with the first color (or fallback color).
  4. 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:

  1. color: The color that will be darkened (this is typically the “base” color on the given element).
  2. fallback: A fallback color if the first color is not valid or not defined.
  3. 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.... or system.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:

  1. color: The color that will be darkened (this is typically the “base” color on the given element).
  2. fallback: A fallback color if the first color is not valid or not defined.
  3. 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.... or system.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>; };