React Development with Styled Components
Let’s talk about CSS and React!
It can make your React and HTML & CSS development experience quite a bit smoother if you define your CSS and styles directly in the React component (.tsx) files. Some people say it’s heretical to bake CSS styles directly inside React components. But in reality, it improves your productivity quite a bit when you don’t suffer from the headache of constantly switching between CSS files and their React components. I believe that the firmly rooted ideology of keeping them separate is based on the assumption that other people, such as UX designers, would be responsible for updating the styles whereas the developers would take care of the code part of the components.
I haven’t seen a single project where this would be the case. It was always the developer who would do the both. It turns out, the styles typically are quite deeply entangled with the DOM structure, which is defined by the component. How you organize your React components deeply affects the structure of the accompanied CSS files. Therefore it generally does not make sense for UX designers to go into such detail that they directly edit CSS files.
As writing the final CSS for the React components goes tightly hand-in-hand with creating the actual components, it feels quite obvious to place every component’s style in the same file with the component.
There are quite some nice React libaries that make your styles in React code look really neat, such as Styled Components.
Some Practices With Styled Components
Styled Compoents is a great way to smoothly embed CSS into your code and also improve readability by giving some semantic meaning to your elements.
Let’s have a look at a sample implementation of a styled component:
const Div = styled.Div({
backgroundColor: 'black',
display: 'flex',
flexDirection: 'row'
});
const SomeLabel = styled.span({
verticalAlign: 'left'
});
const MyCustomInput = styled.input({
color: 'white',
border: 'none'
});
export const MyInfoField = (props: {
label: string;
value: string;
}): ReactNode => (
<Div>
<SomeLabel>{props.label}</SomeLabel>
<MyCustomInput type={'text'} value={props.value} />
</Div>
);
(See also the complete example on my GitHub repo!)
The greatness of this approach is that you can easily “label” underlying basic elements with your own names and thereby make the code more self-documenting. That helps other coders understand the purpose of each element just by reading the code. You can also easily export these component snippets and reuse them elsewhere in your application!
I’ve been writing styled components this way since about half a year now on a very professional customer project with extremely high quality requirements. I can tell you, writing React code with styled components this way makes you feel like having a grip like it never was in the past when the styles were placed in separate files. I have used separate plain CSS files and pre-compiled CSS files in the past, such as Sass, PostCSS etc. And I have been doing it a lot - since decades! The guideline by the Wise of the Internet always was that embedding styles directly in the JavaScript code is a bad practice. But once you take your first steps to the ‘dark side’ and start embedding styles in the code in the way described above, there’s no turning back!
About Bootstrap and Other ‘CSS Frameworks’
There are many CSS frameworks, such as Bootstrap, Materialize etc. I think such CSS frameworks can be a good starting point for unexperienced style tweakers to get something look proper without a lot of hassle. However, the problem is that while Bootstrap provides a proper looking default layout, the reality is that the customer usually comes up with a design that deviates from the standard Bootstrap style. Then you start entering problems because you will need to code your own styles on top of the default Bootstrap styles. You end up partially rewriting and covering up underlying Bootstrap styles and that isn’t a beautiful thing by definition. It’s sort of a hack that you have to start ‘fixing’ the default sauce by mixing in your own flavors. There will be maintainability problems in the long term. If the customer has their own unique design for the UI, you would be better off writing your vanilla styles from scratch and get rid of the underlying ‘framework’ mess that does not quite fit into what you are really doing.
How About Tailwind CSS?
I don’t like Tailwind. Tailwind CSS comes with the idea that you make your CSS ‘atomic’ by having predefined set of ‘atomic’ CSS classes. I think it’s a bad idea. You don’t need these ‘atomic’ classes because the CSS natively also provides these things called ‘properties’ that are already atomic by their nature. You just need to understand them. Learning CSS takes time. But it definitely pays off. If the properties themselves feel too atomic, that’s why you can write CSS classes. Their purpose is to group CSS properties into entities that you can use separately or combine in your code as you wish. You will end up in a quite neat and maintainable structure if you do it with care. With Tailwind, on the other hand, you will end up writing insanely long class list strings, for example:
<div
class="flex items-center justify-between p-4 bg-blue-500 hover:bg-blue-600 text-white font-bold border border-blue-700 rounded-lg shadow-lg transition-all duration-300 transform hover:scale-105 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 focus:bg-blue-700 active:bg-blue-800 active:shadow-none active:transform-none sm:w-full md:w-3/4 lg:w-1/2 xl:w-1/3 2xl:w-1/4 h-auto text-sm sm:text-base md:text-lg lg:text-xl xl:text-2xl uppercase tracking-wide leading-relaxed"
>
Horrific Tailwind Example
</div>
There’s a serious problem with defining your HTML styles as a long list of atomic classes. It’s far from readable if the properties are described horizontally in one line. Compare the example with a proper CSS file that has all the CSS properties defined vertically - it’s way more readable!
At the end of the day, you will want to simplify those cryptic class hieroglyphes somehow - perhaps defining a class to describe a group of classes. Which begs the question, what’s the point of all that hassle? Why not rather keep it all plain and simple? Why not rather be patient enough to really take the effort to understand the basic CSS and use the default mechanisms that it offers to create great layouts. It’s important to have the patience to learn CSS properly because the lack of patience is a bad excuse for screwing up your code with Tailwind.
Getting Rid of CSS Classes Altogether
If we want good code then we may not want to add more CSS classes to our code and we definitely do not want to mess up our code with Tailwind insanity. What we actually might want to do is the opposite of adding more classes - let’s get rid of them altogether! Yes, a class-free world is possible! Namely, if you start embedding your styles into your React code, a whole new world of opportunities opens up to you. You can actually use the TypeScript (or JavaScript) programming language’s native features to organize your CSS properties - which is way more powerful than any CSS pre-processor (such as Sass etc.) ever was. Writing React components becomes more intuiitive because you use the same code logic that you use for components also to manage your CSS properties. Let’s peek in to understand what that really means!
Applying Skins to Components
With styled components you can have an approach to CSS that integrates it smoothly to your TypeScript & React code. Without this approach, CSS will always appear like a cumbersome separate attachment to your code. When your CSS definitions fit your code just as if they were only some more code, this can be a quite neat experience. To get a grip of this, let’s create a nicely layered button component. The component is organized layer-wise. As the first layer we have the base lase layer that defines the fundamental style features that your compnent is going to have:
const BaseButton = styled.button({
all: "unset",
borderRadius: "5px",
color: "white",
});
The baseButton style defines the properties that our button will have. No matter what, we want this button to have all default settings unset and it definitely must have slightly rounded corners (border radius 5px) and white text color.
Then, we can extend the base button and define how the button must look when it is enabled or disabled:
export const Enabledbutton = styled(Basebutton)({
backgroundcolor: "blue",
cursor: "pointer",
});
export const DisabledButton = styled(BaseButton)({
backgroundColor: "gray",
cursor: "default",
});
I’ll be back to these exciting topics very soon. Cheers!