Cores no Tailwind: a escala 50–950, opacidade e oklch
Como funciona a escala de 50 a 950, o que muda no v4 com oklch, como aplicar opacidade com a barra e como colocar a cor da sua marca dentro da paleta.
No Tailwind você não escolhe uma cor: você escolhe uma família (sky,
rose, slate) e um passo dentro dela, de 50 a 950. bg-sky-50 é um azul
quase branco; bg-sky-950 é quase preto. O número não é intensidade de cor —
é o inverso da luminosidade.
Todos os exemplos aqui são o painel de atendimentos de uma clínica veterinária, a Patinha Feliz: fichas de paciente, etiquetas de status e a cor da marca. E todo CSS que aparece abaixo dos blocos saiu do compilador de verdade, com este comando:
npx @tailwindcss/cli -i in.css -o out.css --content painel.htmlDone in 25ms
Se você ainda não instalou nada, comece por como instalar o Tailwind CSS v4 e volte para cá — o resto desta lição depende de você conseguir compilar.
Ler a escala: 50 é quase branco, 950 é quase preto
A versão 4.3.3 traz 26 famílias de cor com 11 passos cada — 286 cores
nomeadas, todas escritas em oklch(). Esta é a família sky inteira, copiada
do theme.css que vem dentro do pacote:
--color-sky-50: oklch(97.7% 0.013 236.62);
--color-sky-100: oklch(95.1% 0.026 236.824);
--color-sky-200: oklch(90.1% 0.058 230.902);
--color-sky-300: oklch(82.8% 0.111 230.318);
--color-sky-400: oklch(74.6% 0.16 232.661);
--color-sky-500: oklch(68.5% 0.169 237.323);
--color-sky-600: oklch(58.8% 0.158 241.966);
--color-sky-700: oklch(50% 0.134 242.749);
--color-sky-800: oklch(44.3% 0.11 240.79);
--color-sky-900: oklch(39.1% 0.09 240.876);
--color-sky-950: oklch(29.3% 0.066 243.157);O primeiro número de cada linha é a luminosidade. Ele cai de 97,7% até 29,3% conforme o passo sobe. O terceiro número, o matiz, quase não se move: fica girando na casa dos 230 a 243. É por isso que a família parece uma cor só, em onze intensidades.
Na prática do dia a dia, três faixas resolvem quase tudo:
| faixa | serve para | exemplo na clínica |
|---|---|---|
| 50–200 | fundo de área, fundo de etiqueta, divisória | fundo do cartão do paciente |
| 300–500 | ícone decorativo, borda visível, gráfico | barrinha de espécie na lateral |
| 600–950 | texto, botão sólido, título | nome do paciente, botão “Chamar” |
bg-, text-, border-, ring-: a cor é sempre o sufixo
O nome da classe é sempre o mesmo padrão: o que você quer pintar, depois a cor. Isso vale para todas as propriedades que aceitam cor.
<article class="bg-sky-50 border-slate-200 text-slate-900">
<span class="bg-emerald-100 text-emerald-800">Alta</span>
<span class="bg-amber-100 text-amber-800">Em atendimento</span>
<span class="bg-rose-600 text-white">Urgente</span>
<p class="text-slate-500">Rex, 4 anos</p>
</article>O compilador transforma cada classe numa regra de uma linha só, apontando para a variável da cor:
Repare no que não apareceu: nenhuma das outras 276 cores. O v4 só emite a variável da cor que ele encontrou no seu código. A paleta inteira não pesa no arquivo final — pesa só o que você usou. É a mesma lógica que vale para espaçamento e para qualquer outro utilitário.
Gradiente segue o mesmo padrão, com from-, via- e to-:
<div class="bg-linear-to-r from-sky-600 to-emerald-500"></div>O que muda quando o v4 troca rgb por oklch
Na versão 3, a paleta era escrita em RGB. Na 4, é oklch(). A diferença que
você sente não é a cor em si — é o comportamento nas bordas.
oklch() separa luminosidade, croma e matiz em três números independentes. Isso
deixa o Tailwind girar o matiz sem que a cor mude de brilho, e é o que permite
o navegador exibir tons mais vivos em telas modernas, que enxergam além do sRGB.
A gramática de oklch() segue a especificação moderna de cores no CSS; aqui o
foco permanece no modo como a paleta Tailwind usa esses valores.
O que quase ninguém conta é que a escala não é uniforme entre famílias.
Medi a luminosidade de cada passo em seis famílias, lendo os valores direto do
theme.css:
import Color from 'colorjs.io';
import { readFileSync } from 'node:fs';
const tema = readFileSync('node_modules/tailwindcss/theme.css', 'utf8');
const cor = (n) =>
new Color(tema.match(new RegExp(`--color-${n}:\\s*([^;]+);`))[1].trim());
for (const passo of [400, 500, 600, 700]) {
const linha = ['sky', 'emerald', 'amber', 'rose', 'violet', 'slate']
.map((f) => (cor(`${f}-${passo}`).oklch.l * 100).toFixed(1).padStart(7));
console.log(String(passo).padEnd(6) + linha.join(''));
}No passo 600, amber está com 66,6% de luminosidade e slate com 44,6%. São
22 pontos de diferença sob o mesmo número. Trocar text-sky-600 por
text-amber-600 porque “é o mesmo passo” muda o contraste da página inteira.
Opacidade com barra: bg-slate-900/40 no lugar de bg-opacity-40
Para deixar uma cor transparente, cole uma barra e o percentual no fim da classe. Serve para qualquer utilitário de cor.
<div class="bg-slate-900/40"></div>Aqui está a mudança de mecanismo que o v4 trouxe. Não sai um rgba() com quarto
canal: sai um color-mix(), misturando a cor com transparent no espaço
oklab. E sai duas vezes — a primeira linha, em srgb, é a versão de reserva
para navegador que não entende color-mix(in lab, …); a segunda, dentro do
@supports, é a que os navegadores atuais aplicam.
A consequência prática é boa: como o valor é um color-mix em cima de
var(--color-slate-900), trocar o valor da variável no
modo escuro muda a versão transparente
junto, sem você escrever nada.
<div class="bg-slate-900 bg-opacity-40 text-white text-opacity-70"></div>O fundo continua opaco, o compilador termina com “Done in 23ms” e ninguém avisa nada. Se você migrou um projeto da versão 3 e a transparência sumiu, o culpado é este. Outras causas de classe que não pega estão reunidas em Tailwind não está aplicando as classes.
O par texto/fundo que passa no contraste — medido
A WCAG pede razão de contraste de 4,5:1 para texto normal e 3:1 para
texto grande (a partir de 18,66px em negrito ou 24px). Medi os onze passos de
sky como texto sobre fundo branco, com a fórmula da WCAG 2.1:
const branco = new Color('#fff');
for (const passo of [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]) {
const razao = branco.contrastWCAG21(cor(`sky-${passo}`));
console.log(
`sky-${String(passo).padEnd(3)} | ${razao.toFixed(2).padStart(5)}:1 | ` +
`normal: ${razao >= 4.5 ? 'passa' : 'REPROVA'} | ` +
`grande: ${razao >= 3 ? 'passa' : 'REPROVA'}`
);
}Guarde a linha do 600. text-sky-600 sobre branco dá 4,09:1 — reprova por
pouco no texto de tamanho normal, e é exatamente a classe que todo mundo usa em
link. O primeiro passo seguro para texto corrido é o 700.
E o “reprova por pouco” muda de família para família. Medindo o passo 600 de
cada uma sobre branco: amber-600 dá 3,21:1, emerald-600 dá 3,74:1,
sky-600 dá 4,09:1, rose-600 dá 4,78:1, violet-600 dá 5,88:1 e
slate-600 dá 7,56:1. Só as três últimas passam.
Agora as combinações que a clínica usa de verdade nas etiquetas:
| status | classes | razão | veredito |
|---|---|---|---|
| Alta | bg-emerald-100 text-emerald-800 |
6,73:1 | passa |
| Alta | bg-emerald-100 text-emerald-600 |
3,29:1 | reprova |
| Em atendimento | bg-amber-100 text-amber-800 |
6,41:1 | passa |
| Em atendimento | bg-amber-100 text-amber-600 |
2,88:1 | reprova |
| Urgente | bg-rose-600 text-white |
4,78:1 | passa |
| Urgente | bg-rose-500 text-white |
3,73:1 | reprova |
| Agendado | bg-sky-100 text-sky-800 |
6,59:1 | passa |
| Agendado | bg-sky-600 text-white |
4,09:1 | reprova |
Daí sai uma regra que você consegue aplicar de cabeça: para etiqueta clara, conte seis passos de distância. Fundo 100 com texto 700 ou 800; fundo 200 com texto 800. Fundo 100 com texto 600 são cinco passos, e cinco passos não bastam.
O erro mais comum: a classe que o Tailwind nunca viu
Este é o tropeço número um com cores no Tailwind, e ele custa uma tarde. A etiqueta de status parece pedir uma classe montada na hora:
const CORES = { alta: 'emerald', atendimento: 'amber', urgente: 'rose' };
export function Etiqueta({ status, texto }) {
const cor = CORES[status];
return (
<span className={`bg-${cor}-100 text-${cor}-800 rounded-full px-3 py-1`}>
{texto}
</span>
);
}rounded-full, px-3 e py-1 foram gerados. As seis classes de cor, não. O
Tailwind não executa o seu JavaScript: ele lê o arquivo como texto e procura
por sequências que pareçam nomes de classe. bg-${cor}-100 nunca é um nome de
classe no arquivo — é um pedaço de template.
A correção é guardar a string inteira, e não o pedaço:
const ESTILO = {
alta: 'bg-emerald-100 text-emerald-800',
atendimento: 'bg-amber-100 text-amber-800',
urgente: 'bg-rose-600 text-white',
};
export function Etiqueta({ status, texto }) {
return (
<span className={`${ESTILO[status]} rounded-full px-3 py-1`}>{texto}</span>
);
}O componente ficou até mais legível: o mapa agora diz qual é a aparência de cada status, em vez de esconder metade dela na interpolação.
Cor arbitrária, cor de variável e cor vinda da API
Quando a cor não está na paleta, existem três saídas, em ordem de preferência.
A primeira é o valor arbitrário, entre colchetes. Serve para um caso isolado:
<div class="bg-[#0d9488] text-[oklch(97%_0.02_190)]"></div>Repare no sublinhado no lugar do espaço dentro do oklch(). O nome da classe
não pode ter espaço, então o Tailwind usa _ e converte de volta na saída.
A segunda é apontar para uma variável CSS, com parênteses. É o atalho do v4
para var(--nome), e resolve o caso em que a cor só existe em tempo de
execução — a espécie do paciente chega do banco de dados:
export function Ficha({ paciente }) {
return (
<article
className="border-l-4 bg-white p-4"
style={{ '--cor-especie': paciente.corDaEspecie }}
>
<span className="bg-(--cor-especie)/15 text-(--cor-especie) rounded px-2">
{paciente.especie}
</span>
</article>
);
}Duas coisas acontecem aí, e as duas são úteis. O valor da cor sai do CSS e vai
para o atributo style, onde pode ser qualquer coisa. E a barra de opacidade
continua funcionando em cima da variável, porque color-mix() aceita var()
como argumento. Se as variáveis CSS ainda são novidade
para você, é o momento de conhecer.
A terceira saída é a boa para cor que se repete: dar um nome a ela.
Colocar a cor da clínica dentro da escala
O bloco @theme registra cores novas no mesmo lugar de onde vêm as originais.
O nome precisa começar com --color-, e o que vier depois vira o sufixo da
classe:
@import "tailwindcss";
@theme {
--color-patinha-100: oklch(93.5% 0.045 190);
--color-patinha-500: oklch(70% 0.132 190);
--color-patinha-600: oklch(60% 0.118 190);
--color-patinha-800: oklch(43% 0.082 190);
}Com isso no arquivo de entrada, bg-patinha-600, text-patinha-800 e até
ring-patinha-500/30 passam a existir, com o mesmo comportamento das cores de
fábrica:
Note que declarei quatro passos e usei quatro. Você não precisa preencher os onze de cara — declare o que a interface usa hoje.
O prefixo é literal, e errar nele custa caro porque não sai erro nenhum.
Escrevendo --cor-patinha-600 em vez de --color-patinha-600, o resultado é
este:
@theme {
--cor-patinha-600: oklch(60% 0.118 190);
}Uma camada de utilitários vazia, o compilador satisfeito e a página sem cor
nenhuma. O @theme não valida nomes: variável que não começa com um prefixo
conhecido é tratada como variável comum e ignorada pelo gerador de classes.
Num design system a sério, vale ir além e apagar a paleta de fábrica, para
que ninguém no time use bg-sky-500 por engano:
@theme {
--color-*: initial;
--color-white: #fff;
--color-patinha-100: oklch(93.5% 0.045 190);
--color-patinha-600: oklch(60% 0.118 190);
--color-patinha-800: oklch(43% 0.082 190);
}O HTML de teste tinha bg-sky-500 junto das outras classes. Ele simplesmente
não existe mais. É o tipo de trava que transforma uma paleta em regra, e não em
sugestão.
O que vem depois
Você já consegue montar um painel inteiro com cor consistente e contraste verificado. O passo seguinte é dar nome também à tipografia e às escalas da marca, e isso é assunto de @theme no Tailwind v4. Se preferir revisar a base antes, o que é o Tailwind explica como uma classe vira CSS, e o guia completo de Tailwind mostra a ordem de estudo até o fim da trilha.
Antes de seguir, faça um teste no seu projeto: procure todo text- seguido de
um passo 600 ou menor aplicado a texto de leitura. Pela tabela acima, boa parte
deles está abaixo de 4,5:1.
Prefere aprender em vídeo?
Tem aula sobre este assunto no nosso canal.
DevClub no YouTubeTUDO SOBRE CORES NO CSS | Aprende de uma vez por todas sobre Cores no CSSAssistir a aula
DevClub no YouTubeTAILWIND CSS - APRENDA DO ZERO - Recriando o Facebook com Tailwind CSSAssistir a aula
Perguntas frequentes
Existe passo 1000 ou passo 25 na escala do Tailwind?
Preciso decorar quais cores existem?
Dá para usar cor em hex nas classes do Tailwind?
A opacidade com barra funciona em cima de variável CSS?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em @tailwindcss/cli 4.3.3 no Node 24.16.0, com colorjs.io 0.6.0, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- Tailwind CSS — Colors — tailwindcss.com
- MDN — color-mix() — developer.mozilla.org
- WCAG 2.2 — Contrast (Minimum), critério 1.4.3 — w3.org


