Stylemapper - A Better Way To Style React Applications
Ivo Meiรner
Posted on August 23, 2022
There are plenty of ways to style React applications. From CSS-in-JS libraries like Emotion, Styled Components or Stitches to writing CSS (or SCSS / SASS) and then leverage build tooling to bring everything together. They all can be a great fit, but I was never 100% satisfied with the approaches. Some lead to repetitive code, while others make it more difficult to use native CSS features. Sometimes the styles are hard to reuse, and other times they are hard to customize.
But I have found a new way that I want to share with the web development community:
Combining a utility-based CSS framework like Tailwind CSS with a small library I wrote called Slicknode Stylemapper.
Why Stylemapper?
I have long been a Tailwind CSS skeptic. I have read a lot about it and saw raving reviews from developers I respect. But because I didn't have to pick a CSS solution for a larger project in a while, I only kept an eye on the framework without actually using it.
Recently, I started working on a large codebase using Tailwind CSS. Several teams are working on the project, and it does help in a big way with scaling the design system and keeping a consistent layout. The utility-based CSS framework approach can feel weird at first, but once I got over the initial hesitation of adding styles right into the JSX code, it turned out to be very productive.
However, there were still a few things I didn't like about using Tailwind CSS in a React project:
Tailwind CSS in React is ugly
Maybe this is just me, but I found using Tailwind CSS in React pretty ugly. CSS classes are littered across the React component code, making them harder to read. A lot of style definitions are so long that they won't fit into a single line on my big screen. I either have to scroll horizontally or enable automatic line breaks in my IDE. Here is an example from the TailwindCSS website (that doesn't even include responsiveness and dark-mode):
export function ProjectListButton() {
return (
<a href="/new" class="hover:border-blue-500 hover:border-solid hover:bg-white hover:text-blue-500 group w-full flex flex-col items-center justify-center rounded-md border-2 border-dashed border-slate-300 text-sm leading-6 text-slate-900 font-medium py-3">
New project
</a>
);
}
You can hide this mess by creating custom components, which is the recommended approach to date according to the documentation, but that brings me to the next point.
Manual Repetitive Work
It is tedious to create all those React wrapper components only to add some styles. If you want to write your code in an extensible way, you have to manually merge the component base styles with the class names of the React props. And in case you are using Typescript, you have to create the prop type definitions by hand for every component. With support for refs, event handlers, extensibility, and a simple variant "selected", this is what one of the examples would look like:
import * as React from 'react';
export interface NavProps extends React.ComponentPropsWithoutRef<'div'> {
selected?: boolean;
}
export const Nav = React.forwardRef<HTMLDivElement, NavProps>((props, ref) => {
const { className, children, selected, ...rest } = props;
return (
<div
{...rest}
ref={ref}
className={`py-4 px-6 text-sm ${
selected ? 'font-bold' : 'font-medium'
} ${className}`}
>
{' '}
<ul className="flex space-x-3"> {children} </ul>{' '}
</div>
);
});
Imagine a <Button/>
component with multiple variants like intent
(danger, primary, neutral), outline
, disabled
, size
and this quickly gets out of hand.
Enter Slicknode Stylemapper
I wrote a small utility library that solves all of the above problems and dramatically simplifies the component code. Inspired by Stitches, I wanted to bring a similar API to utility-based CSS frameworks. You can install it via npm and start building your custom components:
Create Styled Components
import {styled} from '@slicknode/stylemapper';
// Create styled components with CSS classes
const Menu = styled('ul', 'space-x-2 flex');
const MenuItem = styled('li', 'w-9 h-9 flex items-center justify-center');
// Then use the components in your app
const App = () => {
return (
<Menu>
<MenuItem>Home</MenuItem>
<MenuItem>Product</MenuItem>
<MenuItem>Signup Now</MenuItem>
</Menu>
);
};
Components with Variants
You can easily create components with multiple variants that change their style based on props. Stylemapper automatically infers the prop types and creates a strictly typed component.
This eliminates the style management logic from your component code and makes your application code easier to reason about:
const Button = styled('button', {
variants: {
intent: {
neutral: 'bg-slate-300 border border-slate-500',
danger: 'bg-red-300 border border-red-500',
success: 'bg-green-300 border border-green-500',
},
size: {
small: 'p-2',
medium: 'p-4',
large: 'p-8',
},
// Add any number of variants...
},
// Optionally set default variant values
defaultVariants: {
intent: 'neutral',
size: 'medium',
},
});
const App = () => {
return (
<Button intent={'danger'} size={'large'}>
Delete Account
</Button>
);
};
Custom Components
Stylemapper works with any component that has a className
prop. That makes it the perfect fit for headless UI libraries like Headless UI, Radix UI, and Reach UI. Just pass in the component as a first argument:
import {FancyComponent} from './fancy-component`;
const StyledFancyComponent = styled(FancyComponent, 'py-2 px3', {
variants: {
intent: {
danger: 'bg-red-300 border border-red-500',
success: 'bg-green-300 border border-green-500',
},
},
});
Get Started
Slicknode Stylemapper is now available on npm under the MIT license. To see the full API and get started, check out the Github repository. I would love to hear your feedback! You can follow me on Twitter or join the Slicknode community on Slack.
Posted on August 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 25, 2024
November 19, 2024