Why I still reach for vanilla CSS sometimes
·2 min read ·Frontend · CSS · Best Practices
I use Tailwind on almost every project. It's fast, consistent, and makes design decisions explicit. But there are things I still reach for plain CSS to handle, because Tailwind is a tool, not a religion.
Animations
Complex animations are where CSS shines in ways utility classes don't capture cleanly:
@keyframes slide-in {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.sidebar {
animation: slide-in 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
You can do animations in Tailwind, but anything beyond simple transitions requires custom configuration or inline styles that lose the benefit of the utility approach.
The Cascade as a Feature
Tailwind encourages you to style every element individually, which is usually good. But sometimes the cascade is exactly what you want:
.prose {
line-height: 1.75;
color: var(--color-text);
}
.prose h1, .prose h2, .prose h3 {
margin-top: 1.5em;
font-weight: 600;
}
.prose p {
margin-bottom: 1rem;
}
For article content—where you're rendering user-generated or markdown-converted HTML—you can't add utility classes to every element. You need contextual styles that flow from a parent class.
(Tailwind's @tailwindcss/typography plugin handles this specific case well, but the point stands.)
Custom Properties
:root {
--color-primary: #0ea5e9;
--spacing-section: clamp(2rem, 5vw, 6rem);
}
CSS custom properties for design tokens give you type-safe, cascade-aware variables that JavaScript can read and that adapt to themes at runtime. Tailwind configuration is build-time—custom properties are runtime.
The Point
Tailwind is excellent for what it's excellent at. Plain CSS is excellent for what it's excellent at. Use both.