---
title: "Why we still write CSS in 2025."
date: 2025-03-25
url: https://remiam.co.uk/notes/why-we-still-write-css
tags: [CSS, Tailwind, Architecture]
read_time_minutes: 6
description: "In 2025 Tailwind dominates, CSS-in-JS has receded, and we still write hand-rolled CSS in specific places. Why, where, and the rules we use."
---

# Why we still write CSS in 2025.

*Published 2025-03-25 · 6 min read · by Liam (Remiam)*

Tailwind is on every project. Styled-components died quietly. CSS-in-JS is mostly gone. And yet our team still writes hand-rolled CSS in places — here's why.

Tailwind is on every project we ship. Styled-components is gone. Emotion is gone. CSS-in-JS lives in a few legacy codebases and almost no new ones. And yet, in 2025, our team still writes plenty of plain CSS — sometimes more than the Tailwind. We have opinions about why.

## What Tailwind is great for

- Application chrome — the small, repeated, utility-flavoured patterns that make up 80% of a UI.
- Working in teams. Class names are scoped to the element, conflicts are impossible, code review is easier.
- Anywhere the design system is real and the tokens map cleanly onto utilities.

## Where we still write CSS

- Anything bespoke. Hero animations, kinetic typography, custom motion patterns — these are CSS, not utility soup.
- Print stylesheets. Tailwind isn't designed for them.
- Complex grid layouts that read better with named lines than utility classes.
- Components with 30+ properties where the utility version becomes longer than the CSS version.

## When CSS reads better than Tailwind

```css hero.css
/* Bespoke kinetic typography — much clearer as CSS than utilities */
.hero-headline {
  font-family: var(--font-display);
  font-size: clamp(56px, 8vw, 120px);
  line-height: 0.92;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  background: linear-gradient(180deg, var(--color-on-primary) 0%, var(--color-on-primary-mute) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: headlineSlideUp 800ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes headlineSlideUp {
  from { transform: translateY(40px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
```

## Rules we use

- Token-first, always. Whether the rule lives in Tailwind or in a CSS file, it points at the same CSS variables.
- Scoped to the component. Either via <style scoped> or via a clear naming convention. No globals.
- If a Tailwind class string passes 12 utilities, consider whether a CSS file would read better.
- Don't @apply your way out of the problem. @apply is sugar; it can hide what's actually happening.

> The argument isn't 'Tailwind vs CSS'. The argument is 'use each one where it earns its keep'. The team that does both fluently is faster than the team that's all-in on either.

The argument isn't 'Tailwind vs CSS'. The argument is 'use each one where it earns its keep'. The team that does both fluently is faster than the team that's all-in on either.

## References

1. [Tailwind CSS — official documentation](https://tailwindcss.com)
2. [MDN — CSS guide](https://developer.mozilla.org/en-US/docs/Web/CSS)
