Recently I have had a chance to create UI a little in my work due to assessments that we run in our company. I haven't done this for a long time and apparently missed a lot.
I admit that I don't touch UI often in my daily work (now you might wonder what do I do in my job then π€«). Plus I don't get that excited about writing some CSS styles, it is not my cup of tea.
In such a case, that was a ride, and I thought my opinion would stay as close to neutral as it could be (seriously). After using both again, one after another, in the same application, I can definitely share my few thoughts on this topic. I will skip the part about what we have achieved in our working group and focus on styling experience and tooling for now.
Tailwind
Deciding on tailwind came a bit out of nowhere. We all were more or less familiar with it, more or less sceptical but we wanted to save our time on writing stuff.
Small things matter
The integration part, along with creating custom variables and themes, was fast enough. Extending or building a new theme is very intuitive.
@import "tailwindcss";
@theme {
--font-script: Comic-sans; // extending theme
--color-*: initial; // overwriting defaults
--color-white: #fff;
...
}
Also, I love it when tools have some base styles defined. Even simply disabling native margins and padding is huge. It just saves precious time and we have one problem less!
Feeling intuitive in our own stylesheets assures our flow and pushes us to keep progressing. Tailwind wants to be intuitive. It works sometimes... and sometimes not really. Take class namings.
p
for padding and mb
for margin-bottom - great!
For display
values I would prefer display-flex
, display-block
etc. instead just flex
and block
. It's probably connected to other flexbox and grid properties.
rounded
for border-radius
π€¨... If you can't remember it as me. We can create a small theme twig in our theme.
@theme {
--border-radius: var(--rounded);
--border-radius-none: var(--rounded-none);
--border-radius-full: var(--rounded-full);
// ...and so on
--rounded*: initial;
}
Personal feelings
Readability is not as problematic as many people think. The same applies to maintainability and scalability. However, it required some time to get used to the syntax, and VsCode IntelliSense was a little too slow for me to hint proper class π . The code itself grows pretty well. Stays readable even with a few lines of classes for small piece of code. It's pretty easy to find what I want and edit it.
Important note: Don't think about using
@apply
too fast. You can end up with Tailwind-in-CSS π
Server side
By the way, no issues during my entire experience for our testing phase for SSR. Important factor! You can just start using it and it just works. I don't even have anything more to say there π
CSS-in-JS (emotion)
Let's agree it's rather a twilight of such solutions in 2024 and 2025. Of course, this is connected with the react trend for using server components and all that.
See this link: https://nextjs.org/docs/app/building-your-application/styling/css-in-js
Underlying issue
It's probably the biggest issue nowadays, even though those were very popular tools. Most developers are looking for other solutions to avoid creating tech debt and move forward.
As CSS-in-JS relies heavily on React Context API and now your react app might have a mix of server components inside "client components" it simply loses its data and possibility to modify styles on rerender correctly. It is impossible to achieve with the same library π
Don't worry, there are compatible CSS-in-JS alternative libraries!
To understand the full context behind this problem, I recommend checking this article from Joshua Comeau. It is always worth checking his blog posts.
Looking back
Not sure if it deserved that amount of attention after I look back. After all, I regret shifting everything to JS in my apps.
We can agree on at least one thing there - they gave a decent developer experience (aka a lazy developer's experience) as we don't need to switch context that much. Everything can stay even within one file (I'm looking at you tailwind π). We loved that concept and that concept lost us.
Experience and long-term vision
It turns out that you have to do a lot of typing with that solution! And it comes together with configuration effort (btw. not only during project bootstrap). It felt way less grateful compared to Tailwind.
I still very much like the possibility of passing conditional props and all the power related to it.
const Button = styled.button<{ variant?: 'primary' | 'secondary'; }>`
background: ${props => props.variant === 'primary' ? "#ddd" : "#fff"};
`;
render(
<div>
<Button variant="primary">Primary</Button>
</div>
);
Side note it probably complicates more than helps for example in understanding the final syntax and potential refactor tho.
Speaking about refactoring it must be a real curse when you have used style overwriting too much. Plus it might be a sign for you and your designer. Example syntax below:
import { Button } from 'your-design-system-lib'
const MyFunnyButton = styled(Button)`
margin-bottom: 3px; // 3px, really?! don't do that :')
`
Something to revisit and fix at the source. Design systems should be consistent and overwrites shouldn't be needed for atom components.
If I had to bootstrap a new project now, honestly I would pass this solution.
Variables and theming
CSS variables are probably one of the greatest things available to me. Defining a palette once and reusing it across all components is almost like selecting a predefined component variant by prop. Import one file with a project theme variables and I'm ready to go (with a small cheatsheet at the beginning ofc.)
:root {
--main-bg-color: #ddd;
--secondary-bg-color: #000;
--alternative-bg-color: #fff;
}
Hussle with postprocessors and additional configs
Note: Postprocessors are plugins that run after generating your CSS file with a bundler and help optimise it. A good example could be the PostCSS tool.
Have you run your project without adding any CSS postprocessors or configs? Just a barebone CSS file? You did? You must be crazy or lazy π
Well, I wasn't able to run anything without them. It unlocks a lot of benefits. We can name a few now from PostCSS tool:
- cssnano which deletes unused code,
- nested plugin to allow using nesting similar to sass project,
- stylelint... self-explanatory one
- autoprefixer so you don't need to worry about it (not that relevant nowadays but still worth using)
- import which allows to use
@import
rule like the one known from JS files
If I got your interest, the full list is available here: https://postcss.org/docs/postcss-plugins
Of course, I'm considering it as a slowdown to the project and baggage to maintain. We are making a trade with ourselves to invest some time now and get better developer experience together with good stylesheet performance.
Imagine having all those autoprefixes, dead code elimination and modification out of the box without adding a single line.
Lightning CSS
The feeling I have mentioned is potentially the goal of another tool. Lightning CSS. Yes, it's in Rust π but more about that trend in another blog post.
Anyway, if you are looking for a well-packed alternative to PostCSS with improved build times, you should take a look at this tool.
Most necessary tools are included and configurable like vendor prefixes, nesting, source maps, modifications, browser targets and modern CSS syntax.
Summary
It's great to see changes happening in CSS tools and an even more bright future with CSS tools with RSC support (at least for some of them). My overall experience with these tools was more or less accurate. I haven't completely lost touch with them and it was very easy to come back which is positive!
If you think about it. This new approach makes a big impact. We needed to consider this even in CSS-related blog posts. This will impact our future toolset for projects as well. Is it a bad thing? There is only one way to figure this out π
Top comments (0)