Ir para o conteúdo
React

BEM, OOCSS ou Utility-First: Como Organizar Seu CSS Sem Enlouquecer

Marcos Soares
Atualizado em 
10 minutos de leitura
Ilustracao 3D de tres estruturas arquiteturais em vidro e metal representando metodologias CSS BEM OOCSS e Utility-First
Ouça este artigo
0:00BEM, OOCSS ou Utility-First: Como Organizar Seu CSS Sem Enlouquecer--:--

Conteúdo técnico toda semana

Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.

Sem spam. Cancele a qualquer momento com 1 clique.

Neste artigo

O Problema Que Todo Projeto Enfrenta

CSS é fácil de escrever. CSS é impossível de manter.

Essa frase resume a dor de qualquer equipe que já trabalhou em um projeto com mais de 50 componentes. Seletores conflitam. Especificidade vira uma guerra. !important aparece como "solução temporária" que nunca sai.

A raiz do problema não é o CSS em si. É a falta de convenção. Sem uma metodologia clara, cada dev escreve do seu jeito. O resultado? Um stylesheet de 8.000 linhas que ninguém ousa refatorar.

Três metodologias dominam o mercado: BEM, OOCSS e Utility-First. Cada uma resolve o caos de um jeito diferente. Vamos dissecar as três.

BEM: Block, Element, Modifier

BEM nasceu no Yandex, o Google russo. A ideia é simples: nomear classes CSS seguindo uma convenção rígida que elimina ambiguidade.

A estrutura é esta:

  • Block: componente independente (.card)
  • Element: parte interna do bloco (.card__title)
  • Modifier: variação do bloco ou elemento (.card--featured)

Anatomia de um Componente BEM

HTML
<article class="card card--featured">
  <img class="card__image" src="/hero.jpg" alt="Post destaque" />
  <div class="card__content">
    <h2 class="card__title">Título do Post</h2>
    <p class="card__excerpt">Resumo breve do conteúdo...</p>
    <a class="card__link card__link--primary" href="/post">
      Leia mais
    </a>
  </div>
</article>

O CSS correspondente fica assim:

CSS
/* Block */
.card {
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  overflow: hidden;
  background: #fff;
}
 
/* Modifier do Block */
.card--featured {
  border-color: #3b82f6;
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
}
 
/* Elements */
.card__image {
  width: 100%;
  height: 200px;
  object-fit: cover;
}
 
.card__content {
  padding: 1.5rem;
}
 
.card__title {
  font-size: 1.25rem;
  font-weight: 700;
  margin-bottom: 0.5rem;
  color: #1e293b;
}
 
.card__excerpt {
  font-size: 0.875rem;
  color: #64748b;
  line-height: 1.6;
}
 
.card__link {
  display: inline-block;
  margin-top: 1rem;
  font-weight: 600;
  text-decoration: none;
}
 
/* Modifier do Element */
.card__link--primary {
  color: #3b82f6;
}
 
.card__link--primary:hover {
  color: #1d4ed8;
}

Por Que BEM Funciona

Cada classe é autoexplicativa. Olhando .card__title--highlighted, você sabe exatamente:

  1. A qual componente pertence (card)
  2. Qual parte é (title)
  3. Qual variação está ativa (highlighted)

Especificidade nunca é problema. Tudo usa uma única classe. Sem aninhamento, sem IDs, sem seletores descendentes.

Onde BEM Dói

Os nomes ficam longos. Muito longos.

CSS
.navigation__dropdown-menu__item--active {}

Isso é real. Acontece em projetos grandes. O HTML fica visualmente poluído e a legibilidade cai quando componentes são profundos.

Outro problema: BEM não diz onde colocar os arquivos. Não define arquitetura de pastas. É só uma convenção de nomes.

OOCSS: Object-Oriented CSS

Nicole Sullivan criou o OOCSS em 2009. A premissa central é tratar elementos visuais como objetos reutilizáveis, separando dois conceitos:

  1. Estrutura vs Skin (aparência)
  2. Container vs Content (conteúdo)

Princípio 1: Separar Estrutura de Skin

CSS
/* Estrutura - define layout e dimensões */
.media-object {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
}
 
.media-object__body {
  flex: 1;
}
 
/* Skin - define aparência visual */
.skin-rounded {
  border-radius: 8px;
}
 
.skin-shadow {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
 
.skin-bordered {
  border: 1px solid #e2e8f0;
}

Você compõe objetos combinando classes:

HTML
<div class="media-object skin-rounded skin-shadow">
  <img class="avatar skin-rounded" src="/user.jpg" alt="Avatar" />
  <div class="media-object__body">
    <h3>Nome do Usuário</h3>
    <p>Comentário do usuário aqui.</p>
  </div>
</div>

Princípio 2: Separar Container de Content

O estilo de um elemento nunca deve depender de onde ele está. Isso é proibido:

CSS
/* ❌ Anti-pattern OOCSS */
.sidebar h3 {
  font-size: 0.875rem;
  color: #64748b;
}
 
.main-content h3 {
  font-size: 1.5rem;
  color: #1e293b;
}

O correto é criar classes explícitas:

CSS
/* ✅ Padrão OOCSS */
.heading-sm {
  font-size: 0.875rem;
  color: #64748b;
}
 
.heading-lg {
  font-size: 1.5rem;
  color: #1e293b;
}

Um Componente OOCSS Completo

CSS
/* Objeto base: botão */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.625rem 1.25rem;
  font-weight: 600;
  font-size: 0.875rem;
  border: 2px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.15s ease;
}
 
/* Skins de cor */
.btn-primary {
  background: #3b82f6;
  color: #fff;
}
 
.btn-primary:hover {
  background: #2563eb;
}
 
.btn-ghost {
  background: transparent;
  color: #3b82f6;
  border-color: #3b82f6;
}
 
.btn-ghost:hover {
  background: #eff6ff;
}
 
/* Skins de tamanho */
.btn-sm {
  padding: 0.375rem 0.75rem;
  font-size: 0.75rem;
}
 
.btn-lg {
  padding: 0.875rem 1.75rem;
  font-size: 1rem;
}
HTML
<button class="btn btn-primary btn-lg">Salvar Projeto</button>
<button class="btn btn-ghost btn-sm">Cancelar</button>

Onde OOCSS Brilha e Onde Falha

A reutilização é excelente. O CSS final tende a ser menor porque você evita duplicação. O Bootstrap inteiro foi construído sobre princípios OOCSS.

O problema? Não existe convenção de nomenclatura formal. Cada equipe inventa a sua. Sem disciplina, o projeto vira uma sopa de classes genéricas que ninguém sabe se pode alterar sem quebrar outra coisa.

Utility-First: O Paradigma do Tailwind

Utility-First leva o OOCSS ao extremo. Cada classe faz uma única coisa. Você compõe a UI inteiramente no HTML.

O Tailwind CSS é o representante dominante dessa abordagem.

Setup Real com Tailwind

Bash
npm install -D tailwindcss @tailwindcss/postcss postcss

Crie o arquivo postcss.config.js:

JavaScript
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

No seu CSS principal:

CSS
@import "tailwindcss";

O Mesmo Card, Agora em Utility-First

HTML
<article class="overflow-hidden rounded-lg border border-slate-200 bg-white">
  <img
    class="h-52 w-full object-cover"
    src="/hero.jpg"
    alt="Post destaque"
  />
  <div class="p-6">
    <h2 class="mb-2 text-xl font-bold text-slate-800">
      Título do Post
    </h2>
    <p class="text-sm leading-relaxed text-slate-500">
      Resumo breve do conteúdo...
    </p>
    <a
      href="/post"
      class="mt-4 inline-block font-semibold text-blue-500 hover:text-blue-700"
    >
      Leia mais
    </a>
  </div>
</article>

Zero CSS customizado. Tudo declarado inline via classes utilitárias.

Em React, a composição fica natural:

TSX
interface CardProps {
  title: string;
  excerpt: string;
  href: string;
  featured?: boolean;
}
 
function Card({ title, excerpt, href, featured = false }: CardProps) {
  return (
    <article
      className={`
        overflow-hidden rounded-lg border bg-white
        ${featured
          ? "border-blue-500 shadow-lg shadow-blue-500/15"
          : "border-slate-200"
        }
      `}
    >
      <div className="p-6">
        <h2 className="mb-2 text-xl font-bold text-slate-800">
          {title}
        </h2>
        <p className="text-sm leading-relaxed text-slate-500">
          {excerpt}
        </p>
        <a
          href={href}
          className="mt-4 inline-block font-semibold text-blue-500
                     hover:text-blue-700 transition-colors"
        >
          Leia mais
        </a>
      </div>
    </article>
  );
}

Extraindo Componentes com @apply

Quando a repetição incomoda, o Tailwind oferece @apply:

CSS
/* components.css */
.btn-primary {
  @apply inline-flex items-center justify-center rounded-md
         bg-blue-500 px-5 py-2.5 text-sm font-semibold text-white
         transition-colors hover:bg-blue-600
         focus:outline-none focus:ring-2 focus:ring-blue-500
         focus:ring-offset-2;
}
 
.btn-ghost {
  @apply inline-flex items-center justify-center rounded-md
         border-2 border-blue-500 bg-transparent px-5 py-2.5
         text-sm font-semibold text-blue-500
         transition-colors hover:bg-blue-50;
}

Use com moderação. Abusar do @apply é basicamente escrever CSS normal com passos extras.

Comparação Direta: O Mesmo Componente em 3 Metodologias

Vamos construir um alert banner nas três abordagens.

BEM

CSS
.alert {
  padding: 1rem 1.25rem;
  border-radius: 8px;
  border-left: 4px solid;
  font-size: 0.875rem;
  line-height: 1.5;
}
 
.alert--success {
  background: #f0fdf4;
  border-color: #22c55e;
  color: #166534;
}
 
.alert--error {
  background: #fef2f2;
  border-color: #ef4444;
  color: #991b1b;
}
 
.alert__title {
  font-weight: 700;
  margin-bottom: 0.25rem;
}
 
.alert__message {
  opacity: 0.9;
}
HTML
<div class="alert alert--success">
  <p class="alert__title">Sucesso!</p>
  <p class="alert__message">Seu arquivo foi enviado.</p>
</div>

OOCSS

CSS
.box {
  padding: 1rem 1.25rem;
  border-radius: 8px;
  font-size: 0.875rem;
  line-height: 1.5;
}
 
.box-left-accent {
  border-left: 4px solid;
}
 
.theme-success {
  background: #f0fdf4;
  border-color: #22c55e;
  color: #166534;
}
 
.theme-error {
  background: #fef2f2;
  border-color: #ef4444;
  color: #991b1b;
}
 
.text-bold {
  font-weight: 700;
}
 
.mb-xs {
  margin-bottom: 0.25rem;
}
HTML
<div class="box box-left-accent theme-success">
  <p class="text-bold mb-xs">Sucesso!</p>
  <p>Seu arquivo foi enviado.</p>
</div>

Utility-First (Tailwind)

HTML
<div class="rounded-lg border-l-4 border-green-500 bg-green-50
            px-5 py-4 text-sm leading-relaxed text-green-800">
  <p class="mb-1 font-bold">Sucesso!</p>
  <p class="opacity-90">Seu arquivo foi enviado.</p>
</div>

Tabela de Trade-Offs

CritérioBEMOOCSSUtility-First
Curva de aprendizadoBaixaMédiaMédia-Alta
Legibilidade do HTMLAltaMédiaBaixa inicialmente
Legibilidade do CSSAltaMédiaN/A (quase sem CSS)
ReutilizaçãoMédiaAltaMuito Alta
Tamanho do CSS finalGrandeMédioPequeno (com purge)
Refatoração seguraFácilArriscadaMuito Fácil
Onboarding de juniorsFácilDifícilMédio

Quando Usar Cada Uma

Escolha BEM quando:

  • A equipe é grande e heterogênea em senioridade
  • O projeto não usa framework de componentes
  • Você precisa de CSS legível em code review
  • O design system é estável e muda pouco

Escolha OOCSS quando:

  • Você está construindo um framework CSS interno
  • Precisa de máxima reutilização entre projetos
  • A equipe tem maturidade para manter disciplina

Escolha Utility-First quando:

  • O projeto usa React, Vue ou Svelte (componentes)
  • Velocidade de prototipação é prioridade
  • Você quer bundle CSS mínimo em produção
  • A equipe aceita a verbosidade no HTML

Abordagem Híbrida: O Que Funciona na Prática

Projetos reais raramente usam uma metodologia pura. A combinação mais eficaz que já usei:

TSX
// Tailwind para layout e espaçamento
// Classes semânticas para componentes complexos
function Dashboard() {
  return (
    <div className="grid grid-cols-12 gap-6 p-8">
      <aside className="col-span-3">
        <Navigation />
      </aside>
      <main className="col-span-9">
        <div className="mb-6 flex items-center justify-between">
          <h1 className="text-2xl font-bold text-slate-900">
            Dashboard
          </h1>
          <button className="btn-primary">Novo Projeto</button>
        </div>
        <StatsGrid />
      </main>
    </div>
  );
}

Layout com utilities. Componentes interativos complexos com classes extraídas. É pragmático.

Configurando o Melhor dos Dois Mundos

JavaScript
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  content: ["./src/**/*.{ts,tsx,mdx}"],
  theme: {
    extend: {
      colors: {
        brand: {
          50: "#eff6ff",
          500: "#3b82f6",
          700: "#1d4ed8",
          900: "#1e3a5f",
        },
      },
      spacing: {
        18: "4.5rem",
        88: "22rem",
      },
    },
  },
};
CSS
/* src/styles/components.css */
@import "tailwindcss";
 
/* Apenas componentes que aparecem 5+ vezes */
.btn-primary {
  @apply inline-flex items-center justify-center rounded-md
         bg-brand-500 px-5 py-2.5 text-sm font-semibold text-white
         transition-colors hover:bg-brand-700
         focus:ring-2 focus:ring-brand-500 focus:ring-offset-2;
}
 
.input-field {
  @apply w-full rounded-md border border-slate-300 px-3 py-2
         text-sm text-slate-900 placeholder:text-slate-400
         focus:border-brand-500 focus:outline-none focus:ring-1
         focus:ring-brand-500;
}

A regra de ouro: só extraia com @apply o que aparece em cinco ou mais lugares. Antes disso, copie as utilities sem culpa.

Minha Opinião Sincera

Eu usei BEM por anos. É sólido. Funciona. Mas depois que migrei para Utility-First com Tailwind em projetos React, não voltei.

O motivo é simples: componentes já são a abstração.

Com BEM, você cria uma camada de nomes no CSS que espelha a estrutura do HTML. Em React, essa camada é redundante. O componente <Card> já encapsula markup e estilo. Nomear .card__title dentro de Card.tsx é burocracia sem benefício.

OOCSS tem mérito teórico, mas na prática é frágil. Sem uma convenção de nomes rígida como BEM, a "orientação a objetos" vira anarquia. Já vi projetos com 200 classes utilitárias caseiras que ninguém documentou. O Tailwind resolve isso ao ser o dicionário completo que sua equipe nunca vai escrever.

Minha crítica ao Tailwind? O HTML fica feio. Isso é real. Linhas com 15 classes assustam. Mas é um trade-off que aceito porque:

  1. Deletar componente = deletar estilos. Sem CSS órfão.
  2. O bundle final com purge é minúsculo. Frequentemente abaixo de 10KB.
  3. Refatorar é seguro. Não existe efeito colateral global.

Se você trabalha sem framework de componentes — sites WordPress, templates server-side puros — BEM ainda é rei. A clareza dos nomes compensa a verbosidade.

Se está construindo um design system para múltiplos produtos, OOCSS como filosofia (não como implementação) é valioso. Pense em tokens e primitivas.

Mas para a maioria dos projetos modernos com React, Next.js, Vue ou Svelte? Utility-First com Tailwind é a resposta pragmática. Não é a mais elegante. É a que menos faz você enlouquecer.

Pare de debater metodologias. Escolha uma, documente e siga. O CSS que mata projetos não é o mal escrito. É o que não segue padrão nenhum.

Leia tambem

Marcos Soares

Escrito por

Marcos Soares

Fullstack Developer · CEO da Agência Poti

Fullstack Developer e CEO da Agência Poti. Mais de 20 anos construindo arquiteturas cloud-native com React, Next.js e sistemas distribuídos. Parceiro comercial do estúdio iellou design. Fundador do Vivo de Código.

Comentários

Participe da discussão

Seja o primeiro a comentar!

Continue Aprofundando

Conteúdo técnico toda semana

Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.

Sem spam. Cancele a qualquer momento com 1 clique.