1

CSS-in-JS vs CSS Modules vs Vanilla CSS: Qual Estratégia de Estilização Escolher em 2025

O custo que ninguém mostra no tutorial de estilização

A escolha de estratégia de estilização parece uma decisão de gosto pessoal. Não é. Ela afeta bundle size, tempo de hidratação, cache de assets e a capacidade do time de refatorar componentes sem medo. E o cenário mudou: o React Server Components tornou CSS-in-JS runtime um problema real de performance, enquanto o CSS nativo ganhou features como @layer, nesting e container queries que eliminam boa parte da motivação original de soluções como styled-components.

Este post compara três estratégias com critérios explícitos, código funcional e uma matriz de decisão para você fechar a escolha sem ficar preso em thread do Twitter.

As três abordagens em código real

Antes de comparar, vale nivelar o que cada abordagem produz de fato. O componente é o mesmo: um card de produto com título, preço e badge de desconto.

CSS-in-JS com styled-components

// ProductCard.tsx
import styled from 'styled-components';

// Transient prop ($hasDiscount) evita que a prop vaze para o DOM
const Badge = styled.span<{ $hasDiscount: boolean }>`
  display: ${({ $hasDiscount }) => ($hasDiscount ? 'inline-block' : 'none')};
  background: #e53e3e;
  color: #fff;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 0.75rem;
`;

const Card = styled.article`
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 16px;
  transition: box-shadow 0.2s ease;

  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }
`;

const Price = styled.p`
  font-size: 1.25rem;
  font-weight: 700;
  color: #1a202c;
`;

interface ProductCardProps {
  title: string;
  price: number;
  discountPercent?: number;
}

export function ProductCard({ title, price, discountPercent }: ProductCardProps) {
  const hasDiscount = (discountPercent ?? 0) > 0;

  return (
    <Card>
      <h3>{title}</h3>
      <Price>R$ {price.toFixed(2)}</Price>
      <Badge $hasDiscount={hasDiscount}>{discountPercent}% OFF</Badge>
    </Card>
  );
}

CSS Modules

/* ProductCard.module.css */
.card {
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 16px;
  transition: box-shadow 0.2s ease;
}

.card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.price {
  font-size: 1.25rem;
  font-weight: 700;
  color: #1a202c;
}

.badge {
  display: none;
  background: #e53e3e;
  color: #fff;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 0.75rem;
}

/* Classe condicional separada: mais fácil de rastrear no DevTools */
.badgeVisible {
  display: inline-block;
}
// ProductCard.tsx
import styles from './ProductCard.module.css';

interface ProductCardProps {
  title: string;
  price: number;
  discountPercent?: number;
}

export function ProductCard({ title, price, discountPercent }: ProductCardProps) {
  const hasDiscount = (discountPercent ?? 0) > 0;

  return (
    <article className={styles.card}>
      <h3>{title}</h3>
      <p className={styles.price}>R$ {price.toFixed(2)}</p>
      <span className={`${styles.badge} ${hasDiscount ? styles.badgeVisible : ''}`}>
        {discountPercent}% OFF
      </span>
    </article>
  );
}

Vanilla CSS com data attributes

/* product-card.css */
/* @layer garante que estilos de componente não conflitem com reset global */
@layer components {
  .product-card {
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 16px;
    transition: box-shadow 0.2s ease;

    &:hover {
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }
  }

  .product-card__price {
    font-size: 1.25rem;
    font-weight: 700;
    color: #1a202c;
  }

  .product-card__badge {
    display: none;
    background: #e53e3e;
    color: #fff;
    padding: 2px 8px;
    border-radius: 4px;
    font-size: 0.75rem;

    /* data attribute como API de estado: semântico e pesquisável */
    &[data-visible='true'] {
      display: inline-block;
    }
  }
}
// ProductCard.tsx
import './product-card.css';

interface ProductCardProps {
  title: string

---

Leia o artigo completo em [https://www.vivodecodigo.com.br/react/css-in-js-vs-css-modules-vs-vanilla-css-estrategia-estilizacao-2025](https://www.vivodecodigo.com.br/react/css-in-js-vs-css-modules-vs-vanilla-css-estrategia-estilizacao-2025)
Carregando publicação patrocinada...