This content originally appeared on Bits and Pieces - Medium and was authored by James B
Lock Down Your Theme Colours with TypeScript
Setting up bulletproof prop validation for colours on a button, using Typescript.
You’ve spent ages building out your Design System, got your brand colours on point, and now you’re building out your component library. You want to lock down your colour props to only allow brand colours, but you only want to allow some of those colours, and only in certain combinations.
An example here is a button component. The background and text colours can be dynamic, but we don’t want to accept every colour in our Design System (we might end up with green text on an orange button 🤢) — we’ll just hand pick the ones that work best together!
We can use Typescript to make sure only the colour pairings that we’ve chosen get used, and that the colours are always within the Design System even if the Design System colour set changes in the future.
The goal
Allow the user to specify background and text colours for a button. These colours must be with a ‘theme’ colour set. Only certain colour combinations are allowed.
- Let’s go with the following colours for our theme:
— red
— blue
— yellow
— pink
— white
— black - We’d like to only allow certain combinations of colours for the background and text (because only certain combinations look good). For these combinations let’s go with:
— red background, white text
— blue background, white text
— yellow background, black text
— pink background, black text
— white background, black text
— black background, white text
Passing criteria:
- Don’t allow any colour props outside of the theme set.
- Don’t allow any pairings outside of the above list.
Here’s the finished CodeSandbox link to have a play around: https://codesandbox.io/s/lock-down-theme-colours-with-typescript-rdhrq1
Try updating the Button props in the App file. Anything outside of the above criteria should cause an error (redline).
The tools
The Extract utility type:
Extract allows us to specify certain values inside a type. For example if we have:
type Characters = "fry" | "leela" | "bender" | "flexo" | "zoidberg";
type Robots = Extract<"bender" | "flexo", Characters>;
then Robots is going to equal "bender"| "flexo".
Q: Why not just set type Robots = "bender" | "flexo;?
A: Because now if we change the Character type it will alter our Robots type. Let’s say we don’t want to include flexo in the Characters type any more, so type Characters = "fry" | "leela" | "bender" | "zoidberg"; . Now Robots is going to equal "bender".
Discriminated Union:
Discriminated Unions allow us to discriminate between types with a shared literal member. For example:
type Adverseries =
{
hero: "batman";
villain: "joker" | "scarecrow" | "twoFace"
} |
{
hero: "superman";
villain: "lexLuthor" | "darkseid" | "brainiac"
}
Now if we have a component such as:
const Component = ({adverseries}: {adverseries: Adverseries}) => ...
the following will pass typechecks:
<Component adverseries={{hero: "batman", villain: "joker"}} />
<Component adverseries={{hero: "superman", villain: "brainiac"}} />
whereas the following will fail:
<Component adverseries={{hero: "batman", villain: "lexLuthor"}} />
<Component adverseries={{hero: "superman", villain: "scarecrow"}} />
Q: Why not just set the following?
type Hero = "batman" | "superman"
type Villain = "lexLuthor" | "darkseid" | "brainiac" | "joker" | "scarecrow" | "twoFace"
const Component = ({adverseries}: {adverseries: {hero: Hero; villain: Villain}}) => ...
A: Because now the following will pass typechecks, which we don’t want!
Component adverseries={{hero: "batman", villain: "scarecrow"}} />
The set up
Inside our App container we’ve set up a couple of things.
We’ve defined a type called ThemeColour, which contains all the colours we have in our theme. We’re going to use these colour names as keys to access values on the object below, which we’ve called themeColours.
Q: Why not just have the hex values as the ThemeColour values, and lose the object completely?
A: If we set up our theme colours as an object and use the keys to access them, we can tweak the hex values of our colours without having to update props all over the place.
The build
Inside our Button component we’ve built a basic button in the return. It’s got a couple of tailwind classes to give us something to work with.
The Button component has one prop (colours), which is an object with 2 keys, text and background .
Inside the style prop we’ve set two dynamic styles — backgroundColor and color. The values are pulled from our themeColours (imported from App) using keys provided by the colours prop.
The colours prop type utilises our Discriminated Union — we have a list of background colours we’ll allow when we have white text, and likewise for black text. Wrapping these values with Extract means we’re confident that any prop passed in will be in both the list of allowed colours and the ThemeColour set.
Let’s take a look at this in 2 parts.
Part 1 — use the Discriminated Union to allow certain pairings:
type ButtonProps = {
colours:
| {
text: "white";
background: "red" | "blue" | "black";
}
| {
text: "black";
background: "pink" | "white" | "yellow";
};
};
// PASS!
<Button colours={{ text: "white", background: "blue" }} />
// FAIL! Combination not allowed.
<Button colours={{ text: "black", background: "blue" }} />
Great stuff — we don’t have any dodgy colour pairings
Part 2 — Make sure our colours are part of the Theme. We do this by wrapping the types in Extract . This will also stop people breaking things in the future.
Example A:
We set up our component as before, only with Extract wrappers.
type ButtonProps = {
colours:
| {
text: Extract<"white", ThemeColour>;
background: Extract<"red" | "blue" | "black", ThemeColour>;
}
| {
text: Extract<"black", ThemeColour>;
background: Extract<"pink" | "white" | "yellow", ThemeColour>;
};
};
// PASS!
<Button colours={{ text: "white", background: "blue" }} />
// FAIL! Combination not allowed.
<Button colours={{ text: "black", background: "blue" }} />
Now an enterprising developer comes along and renames ‘blue’ to ‘darkBlue’ in our ThemeColours:
type ButtonProps = {
colours:
| {
text: Extract<"white", ThemeColour>;
background: Extract<"red" | "blue" | "black", ThemeColour>;
}
| {
text: Extract<"black", ThemeColour>;
background: Extract<"pink" | "white" | "yellow", ThemeColour>;
};
};
// FAIL! blue no longer exists in the Extract type
<Button colours={{ text: "white", background: "blue" }} />
Bang, we get a type error. This is great - our component hasn’t changed at all, but because the higher level ThemeColours have changed we get a redline on the Button component.
Example B:
A developer wants to extend the range of button colours, so they add a new colour to the button prop types:
type ButtonProps = {
colours:
| {
text: Extract<"white", ThemeColour>;
// BROWN ADDED TO BACKGROUND COLOURS
background: Extract<"red" | "blue" | "black" | "brown", ThemeColour>;
}
| {
text: Extract<"black", ThemeColour>;
background: Extract<"pink" | "white" | "yellow", ThemeColour>;
};
};
// FAIL! Brown does not exist on the Extract
<Button colours={{ text: "white", background: "brown" }} />
Awesome — because ‘brown’ isn’t a theme colour even adding it to the type won’t allow it to be used. It’s caught because it’s not a theme colour, so Extract<"red" | "blue" | "black" | "brown", ThemeColour> still leaves "red" | "blue" | "black" , causing a redline error.
💡 Note: If your projects are bigger, consider building a proper design system that is composable and scalable, using Bit’s open-sourced ThemeProvider that lets you quickly build a full theming solution with the Context API and design tokens.
To learn more about theming components with React:
Theming in Components with React and Bit
Summary:
That wraps it up! We’ve got a button that allows different colour combos where every colour exists within a defined set.
This is super handy when working with Component Libraries or Design Systems; it makes sure components stay on-brand by sticking to the theme colours while allowing the flexibility to create defined combinations — the best of both worlds!
Thanks for reading, and happy coding ✌️
Build Apps with reusable components, just like Lego
Bit’s open-source tool help 250,000+ devs to build apps with components.
Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.
Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:
→ Micro-Frontends
→ Design System
→ Code-Sharing and reuse
→ Monorepo
Learn more:
- How We Build Micro Frontends
- How we Build a Component Design System
- How to reuse React components across your projects
- 5 Ways to Build a React Monorepo
- How to Create a Composable React App with Bit
Lock down your theme colours with Typescript was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by James B
James B | Sciencx (2023-03-06T08:47:47+00:00) Lock down your theme colours with Typescript. Retrieved from https://www.scien.cx/2023/03/06/lock-down-your-theme-colours-with-typescript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.