← all posts

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.