If you're not familiar with design tokens, they are simply representations of design specifications in code.
By "representations," it is meant that they are key-value pairs that represent design specifications.
Generally speaking, there are two types of design tokens:
"Simple" tokens - Representations of valid values of the design system. These tokens usually cover the colors, typography, spacing, etc. of the design system (i.e.
color-red-500
,font-bold
, etc.)."Component" tokens - Representations of the design specs for a component/element (i.e.
component-button-background-color
).
With a design tokens pipeline, you can write out design tokens in JSON format, and then translate those "raw"/JSON design tokens into formatted tokens (JavaScript modules, CSS variables, SASS variables, etc.).
With that background in mind, imagine that a design system had "simple" design tokens defining valid colors.
Here's the JSON representation:
{
"color": {
"red-50": "#FFC3C2",
"red-100": "#FFAFAD",
// ...etc
}
}
Now, imagine that a design tokens pipeline formats the JSON into the following JavaScript modules:
export colorRed50 = "#FFC3C2";
export colorRed100 = "#FFAFAD";
// ...etc
And, let's say these tokens can be consumed in an application via an npm package:
// SomeComponent.jsx
import * as tokens from "@some/design-system/tokens";
function SomeComponent() {
const style = { color: tokens.colorRed50 };
return <div style={style}>Some Component</div>
}
Now, given such a setup, how can we programmatically create gradients when given two color tokens?
Here's one way:
// SomeComponent.jsx
import * as tokens from "@some/design-system/tokens";
function SomeComponent() {
const style = {
background: `
linear-gradient(
45deg,
${tokens.colorRed50},
${tokens.colorRed100}
)
`,
};
return <div style={style}>Some Component</div>
}
Ok, but is there a way that we can refactor this?
Well, we could create a helper function that returns the gradient when providing the from
and to
values:
// get-gradient.js
export default function getGradient(from, to) {
return `linear-gradient(45deg, ${from}, ${to})`;
}
// SomeComponent.jsx
import * as tokens from "@some/design-system/tokens";
import getGradient from './get-gradient.js';
function SomeComponent() {
const style = {
background: getGradient(
tokens.colorRed50,
tokens.colorRed100,
),
};
return <div style={style}>Some Component</div>
}
This refactor doesn't save in lines of code, but it does guarantee that gradients will be created the same so long as they are created through the getGradient
helper function.
What if we refactored one step further and allowed the gradient to be applied to any child component via a wrapper component?
// Gradient.jsx
import { Children, cloneElement } from 'react';
function getGradient(from, to) {
return `linear-gradient(45deg, ${from}, ${to})`;
}
export default function Gradient({ children, from, to }) {
return Children.map(children, (child) => {
return cloneElement(child, {
style: {
...child.props.style,
background: getGradient(from, to),
},
});
});
}
// SomeComponent.jsx
import * as tokens from "@some/design-system/tokens";
import AnotherComponent from './AnotherCompoent.jsx';
import Gradient from './Gradient.jsx';
function SomeComponent() {
return (
<Gradient from={tokens.colorRed50} to={tokens.colorRed100}>
<AnotherComponent />
</Gradient>
);
}
By using Children
and cloneElement
, the Gradient
component clones the child element and applies the gradient.
🎊 Awesome! Now, we have a pattern for applying a gradient via a wrapper component in React!
Top comments (0)