I tend to do a lot of frontend work with vanilla css, and sometimes css modules which have been my tools of choice since moving away from Stitches. I really like to have a design system (or a system of any kind) when working on a project because going back to cross-reference values is exhausting and Tailwind is a great utility to diminish this headache greatly but I like to treat Tailwind as what it is ā a utility. CSS is wide, broad and rather intricate. I sincerely do not think that a Swiss Army knife replaces a full tool kit for building anything worth its weightāsure, itās handy if Iām stranded in the woods and need to open a can of beans, but for real work? Iād rather not hack my way through with a spork and a mini-screwdriver. Give me the right tool for the job every time.
As such, I tend to use both vanilla CSS and tailwind in most cases because px-auto
and way faster than having to type out padding-inline: auto;
or padding-left: auto; padding-right: auto;
for the uninitiated. But here lies the case where I like to be very specific with my CSS and CSS Variables fill that role for me perfectly, especially in the scenario where I have to toggle themes and values. The drawback here is not being able to get a reference to the value when I want to use it. Enter CSVars.
This is what it looks like in use
index.css
is defined as so:
:root {
--primary-color: #3498db; /* Main brand color used for CTAs, links, and highlights */
}
Why CSS variables and not extend Tailwind styles?
Well Iām glad you asked. The most obvious reason here would be that I would be constricted to Tailwind when I need theme-specific styling. When the codebase grows larger, packages would be added and they would need to be styled. Say I was using a Radix primitive, I would like to have the option to have themes for my primitive and defining this in CSS is way more readable to me (opinions may vary) than writing out Tailwind shortcuts on one single line where my editor canāt fully breathe.
button {
display: flex;
align-items: center;
justify-content: center;
gap: 8px; /* For when there's a prefix or suffix icon */
width: max-content;
height: 40px;
padding-inline: 12px;
border-radius: 8px;
font-size: 12px;
color: var(--color-text-strong);
background-color: var(--color-primary);
pointer: cursor;
}
button[data-variant:"secondary"] {
color: var(--color-text-surface);
background-color: var(--color-secondary)
}
The Tailwind equivalent of this would look something like this:
<!-- Primary Button -->
<button class="flex items-center justify-center gap-2 w-max h-10 px-3 rounded-lg text-xs text-[var(--color-text-strong)] bg-[var(--color-primary)] cursor-pointer">
Primary Button
</button>
<!-- Secondary Button -->
<button class="flex items-center justify-center gap-2 w-max h-10 px-3 rounded-lg text-xs text-[var(--color-text-surface)] bg-[var(--color-secondary)] cursor-pointer" data-variant="secondary">
Secondary Button
</button>
Or if we wanted to go the @apply
way would do this:
<!-- HTML Structure -->
<button class="btn-primary">
Primary Button
</button>
<button class="btn-secondary">
Secondary Button
</button>
@layer components {
.btn-primary {
@apply flex items-center justify-center gap-2 w-max h-10 px-3 rounded-lg text-xs cursor-pointer;
color: var(--color-text-strong);
background-color: var(--color-primary);
}
.btn-secondary {
@apply flex items-center justify-center gap-2 w-max h-10 px-3 rounded-lg text-xs cursor-pointer;
color: var(--color-text-surface);
background-color: var(--color-secondary);
}
}
Thereās probably a more hacky way to get this done but Iām scared to look it up.
There is also the issue of separation of concern. I like the idea of having different things sit in different places according to their use. The way one would create a folder for hooks should be the same way one should have an button.css
or an button.module.css
. I tend to skim over code according to keywords and I do not read lines fully so the absence of line breaks and the consolidation that Tailwind aims to provide does not really help me understanding what is happening with my styles.
Third reason would be portability. I want to know that the styles that I write are being written in the language that the engine would use and not something that is interpreted and going to be converted into css at the end of the day. Working with Barima Effah (CTO) at NewComma opened up my eyes to this because when he would work on his design system, he would go into detail with PostCSS and css variables were abundant because they just simply worked! No need remembering what the hex code for the dropdown is when it can be referenced anywhere with a css variable. That is peak state management to me.
I love Tailwind and it really shines when you want to get up and go without having worrying about defining styling, especially colors, from scratch but I canāt help to fall back to good olā css when I need to be more specific.
How does CSVars work?
Itās really simple. It watches for file changes (triggered by file saves) and gathers all the saved variables for you to reference. The completion is triggered when the user types in var(
. A completion dropdown shows up next to your cursor with a list of available variables and filters based on the current text you typed in so far. The cherry on top is you have a preview of your values right there, just like Tailwindšš½. This way you can glance through and pick exactly what youāre looking for. In the details section of the highlights completion, you will find the full definition of the variable, The comment associated with the variable and where the variable can be found (filename : line number)
Caveats
- There is the issue of using variables in the same file theyāre defined. VSCode by default provides completion for variables defined in the same file and because I did not want to go against this User Experience flow, I decided it was best to turn off completions for CSVars when youāre in the same file as the variables were defined because if not, duplication happens. You see both VSCodeās completions and CSVarsā completions, all in one list!
- At the time of writing this, the only files that are being watch are
.css
,scss
and.less
. I havenāt done testing on watching more types of files like.astro
and.jsx
/.tsx
. For Astro specifically, detecting styles can be issue when theyāre defined in the.astro
file itself using the style tags with theis:global
property attached even. Looking into the DX of that and how it would affect performance and if itās a good improvement, the addition will be made
Please try out CSVars and let me know what you think, bad or good. Thank you for reading through my ramblingsš