Dark mode em skill de design: como escrever as regras de tema sem duplicar tudo

Dark mode em skill de design não se resolve listando a paleta duas vezes. A skill descreve o contrato semântico (pares de superfície e foreground, tipo bg-background e text-foreground) e o CSS resolve o valor por tema, com light-dark() mais color-scheme ou com os mesmos tokens redefinidos dentro de .dark. No Tailwind CSS v4 a variante dark usa prefers-color-scheme por padrão, e dá pra trocar por atributo com @custom-variant. A skill ainda guarda as proibições (hex solto, dark: por componente) e a régua de contraste da WCAG: 4.5:1 pra texto normal e 3:1 pra não textual
Fala aí, beleza? Você escreveu uma skill de design bonitinha, listou a paleta inteira, e aí listou de novo a versão escura de cada cor logo abaixo
Aí pediu um card novo e o Claude entregou um componente que fica lindo no claro e simplesmente some no escuro
O problema quase nunca é o modelo, é o formato da regra. Quando a skill descreve COR, ela descreve um valor que só vale num tema. Quando a skill descreve TOKEN semântico, ela descreve um contrato que vale nos dois, e quem resolve o valor final é o CSS
A ideia deste post é essa: um único <code>SKILL.md</code>, um vocabulário de tokens declarado uma vez só, e a troca de tema morando onde ela sempre deveria ter morado 🙂
O que você precisa antes de começar
Nada de PC da Nasa aqui, é bem simples:
- Claude Code instalado e um projeto onde você já mexe no CSS
- um projeto com CSS próprio, Tailwind CSS v4 ou shadcn/ui (o passo a passo cobre os três caminhos)
- decidir onde a skill vai morar: <code>~/.claude/skills/<nome-da-skill>/</code> pra skill pessoal, <code>.claude/skills/<nome-da-skill>/</code> pra skill de projeto
- saber que a pasta precisa de um <code>SKILL.md</code>, que é o arquivo de entrada obrigatório, com frontmatter YAML no topo
As subpastas <code>scripts/</code>, <code>references/</code> e <code>assets/</code> são opcionais, então não precisa criar nada disso agora
E uma boa notícia pra quem odeia ficar reiniciando terminal: o Claude Code observa os diretórios de skills e reconhece skill nova ou modificada sem reiniciar. Tu salva o arquivo e segue o baile
Se você ainda está em dúvida se essa regra deveria ser uma skill ou não, tem um post aqui do blog sobre onde as regras de design devem morar que resolve essa escolha antes
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 114 aulas
- 4 projetos
- 9h 18min
Passo a passo: escrevendo as regras de tema na skill
- Crie a pasta e o <code>SKILL.md</code> com frontmatter
O nome da PASTA é o que vira o comando. O campo <code>name</code> do frontmatter é só o rótulo que aparece na listagem, então não adianta caprichar nele achando que muda a invocação
<pre><code class="language-bash">mkdir -p .claude/skills/design-system</code></pre>
E o arquivo:
<pre><code class="language-markdown">— name: Design System description: Define os tokens semânticos de cor e as regras de tema claro e escuro do projeto. Use quando o pedido envolver criar ou alterar componente de UI, cor, borda, fundo ou estado visual. —
# Regras de tema</code></pre>
Repara na <code>description</code>: ela tem capacidade (o que a skill faz) e gatilho (quando usar). Isso não é firula, é o texto que o Claude lê pra decidir se aciona a skill
O erro comum deste passo: escrever <code>description: regras de design</code>. Genérico assim, a skill fica lá parada e o componente sai do jeito que o modelo achou bonito
- Declare o vocabulário de tokens uma vez só, em pares
Aqui mora a economia toda. O shadcn/ui organiza cor em pares semânticos: o token base define a superfície, e o token com sufixo <code>-foreground</code> define o texto e os ícones que ficam em cima dela
| Token | O que ele controla | Como aparece na classe |
|---|---|---|
| <code>–background</code> | a superfície, o fundo | <code>bg-background</code> |
| <code>–foreground</code> | texto e ícones sobre a superfície | <code>text-foreground</code> |
| <code>–border</code> | a linha de contorno | <code>border-border</code> |
A regra que você escreve na skill é o par, não a cor:
<pre><code class="language-markdown">## Vocabulário de cor
Toda cor sai de token semântico. Superfície e conteúdo andam em par: se existe --x, existe --x-foreground, e o conteúdo em cima de bg-x usa text-x-foreground
Nunca declare um token só pro tema escuro. O mesmo nome vale nos dois temas</code></pre>
O erro comum deste passo: criar <code>–card-dark</code> ou <code>–texto-escuro</code>. Token com tema no nome é a duplicação voltando pela porta dos fundos
- Escreva a troca de tema no CSS, não na skill
A skill diz QUAL token usar. O CSS diz QUANTO vale aquele token em cada tema. São dois trabalhos diferentes e misturar os dois é o que faz sua skill inchar
Caminho 1, com a função light-dark(): ela recebe dois valores de cor e retorna um deles conforme o esquema de cor ativo, sem precisar da media feature <code>prefers-color-scheme</code>
<pre><code class="language-css">:root { color-scheme: light dark;
–background: light-dark(#ffffff, #0b0b0c); –foreground: light-dark(#111111, #f2f2f2); –border: light-dark(#d8d8d8, #2c2c2e); }</code></pre>
Que propriedade é essa <code>color-scheme</code>? Ela é a chave que habilita o <code>light-dark()</code>, e de quebra permite sobrescrever o esquema de cor do usuário em trechos do documento. Sem ela declarada, a função não tem como decidir qual dos dois valores devolver
Sobre suporte: <code>light-dark()</code> está disponível nos três principais motores de navegador e virou Baseline Newly available em 13 de maio de 2024
Caminho 2, o do shadcn/ui: o tema escuro redefine os MESMOS tokens dentro do seletor <code>.dark</code>, em vez de criar tokens novos
<pre><code class="language-css">:root { –background: #ffffff; –foreground: #111111; }
.dark { –background: #0b0b0c; –foreground: #f2f2f2; }</code></pre>
Os dois caminhos entregam a mesma coisa pro componente: <code>bg-background</code> e <code>text-foreground</code> funcionando nos dois temas sem uma linha condicional no JSX
O erro comum deste passo: escolher os dois ao mesmo tempo. Se o valor já é <code>light-dark()</code>, redefinir de novo em <code>.dark</code> só cria duas fontes de verdade brigando
- Acerte a estratégia de variante do Tailwind e ligue as variáveis no shadcn/ui
No Tailwind CSS v4, a variante <code>dark</code> usa por padrão a media query <code>prefers-color-scheme</code>. Ou seja: ela segue o sistema operacional do usuário
Se o seu projeto tem um botãozinho de trocar tema, isso não basta, e dá pra trocar a estratégia por atributo com a diretiva <code>@custom-variant</code>:
<pre><code class="language-css">@import "tailwindcss";
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));</code></pre>
O v4 também traz utilitários de <code>color-scheme</code> pra controlar como os elementos renderizam no tema escuro, o que ajuda naqueles componentes nativos que teimam em ignorar seu CSS
Se o projeto é shadcn/ui, o tema por variável CSS depende de <code>tailwind.cssVariables</code> estar como <code>true</code> no <code>components.json</code>:
<pre><code class="language-json">{ "tailwind": { "cssVariables": true } }</code></pre>
O erro comum deste passo: o projeto trocar tema por atributo e a skill não contar isso pro Claude. Ele gera <code>dark:</code> do jeito padrão, você clica no botão e nada acontece
Então escreva a estratégia na skill, em uma linha:
<pre><code class="language-markdown">A troca de tema neste projeto é por atributo (data-theme=dark), configurada via @custom-variant. Não assuma prefers-color-scheme</code></pre>
- Escreva as proibições, não só as recomendações
Regra positiva sozinha é fácil de contornar. A parte que segura o componente quebrado é a lista do que NÃO pode:
<pre><code class="language-markdown">## Proibido
- hex, rgb ou hsl solto dentro de componente: cor só via token
- token com nome de tema (
--algo-dark) dark:por componente quando já existe token semântico pro caso- texto sem par de superfície: se você usou
bg-x, usetext-x-foreground
Antes de entregar o componente
Confira contraste nos dois temas: 4.5:1 para texto normal, 3:1 para texto de grande escala e 3:1 para borda, ícone e indicador de estado</code></pre>
- Mande o excedente pra <code>references/</code>
A recomendação oficial de autoria de skills é manter o corpo do <code>SKILL.md</code> abaixo de 500 linhas e dividir o que passar disso em arquivos separados
E tem um critério melhor que "tá grande": quando os contextos são mutuamente exclusivos ou raramente usados juntos, a orientação é manter os caminhos em arquivos distintos, o que reduz uso de tokens
<pre><code>.claude/skills/design-system/ ├── SKILL.md └── references/ ├── tokens.md ├── contraste.md └── tailwind-v4.md</code></pre>
O erro comum deste passo: quebrar em arquivo e esquecer de referenciar ele no corpo. Arquivo que ninguém aponta é arquivo que nunca carrega
Erros comuns: quando o Claude gera componente que só existe em um tema
O componente some no tema escuro
Sintoma: card branco com texto quase branco, ou um bloco que virou um retângulo invisível quando você troca o tema
Causa: cor cravada em hex dentro do componente. Hex é valor fixo, não tem como um valor fixo mudar de tema
Solução: troque por token semântico, <code>bg-background</code> com <code>text-foreground</code>, e deixe o valor no arquivo de tokens
Como prevenir: a proibição do passo 5 escrita na skill. Corrigir na mão depois é enxugar gelo, porque o próximo componente nasce igual
Texto legível no claro e ilegível no escuro
Sintoma: o cinza médio que ficava elegante no fundo branco vira uma mancha no fundo escuro
Causa: o par de tokens foi pensado só pro tema claro. O <code>-foreground</code> do escuro herdou um valor que nunca foi medido contra a nova superfície
Solução: medir contraste nos dois temas. O critério 1.4.3 da WCAG 2.2 exige 4.5:1 para texto e 3:1 para texto de grande escala
Como prevenir: deixa claro na skill que o limiar é limiar mesmo. As razões de contraste da WCAG não devem ser arredondadas pra atingir o critério: 4.499:1 NÃO atende o limite de 4.5:1. Tome cuidado com ferramenta que mostra o valor arredondado pra cima
Borda, ícone e foco invisíveis no escuro
Sintoma: o input existe mas você não vê onde ele começa, e o anel de foco sumiu
Causa: a régua de contraste foi aplicada só em texto. Borda e ícone entraram como "detalhe visual" e ninguém mediu
Solução: o critério 1.4.11 da WCAG exige 3:1 para elementos não textuais, incluindo a informação visual necessária pra identificar componentes de interface e seus estados. Estado de foco entra nessa conta
Como prevenir: ter <code>–border</code> como token de primeira classe, usado via <code>border-border</code>, e não como uma corzinha resolvida no olho dentro de cada componente
A variante <code>dark</code> não dispara
Sintoma: o código tem <code>dark:</code> por todo lado, o botão de trocar tema muda o atributo no HTML, e a tela continua clara
Causa: o projeto troca tema por atributo, mas a variante <code>dark</code> do Tailwind v4 continua no padrão <code>prefers-color-scheme</code>, então ela obedece o sistema operacional e ignora seu botão
Solução: declarar a variante por atributo com <code>@custom-variant</code>, como no passo 4
Como prevenir: escrever a estratégia do projeto dentro da skill. O Claude não adivinha convenção de projeto, ele lê o que você deixou escrito, e quanto mais explícito o contexto que você entrega, melhor sai a resposta, que é a mesma lógica de prompts que aproveitam o Opus
Quando vale separar as regras de tema em arquivos de apoio
Skills seguem divulgação progressiva, em três níveis: o frontmatter fica sempre carregado no contexto, o corpo do <code>SKILL.md</code> só carrega quando o Claude julga a skill relevante, e arquivos referenciados carregam sob demanda
Isso joga a favor do tema. Se você conhece aquele padrão de importar módulo só quando precisa, é bem parecido: o índice fica sempre à mão, o conteúdo pesado só entra quando alguém pede
Casos concretos que valem virar arquivo separado:
- tabela de tokens longa: design system com muitas superfícies vira uma tabela enorme que só interessa quando o pedido é de cor
- guia de contraste: os critérios, os limiares e o lembrete de não arredondar cabem melhor em <code>references/contraste.md</code>
- receitas por framework: CSS puro com <code>light-dark()</code>, Tailwind v4 e shadcn/ui são caminhos mutuamente exclusivos. O projeto usa um. Carregar os três é token jogado fora
Duas notas pra quem vai distribuir isso:
Skill entregue por plugin usa o namespace <code>plugin-name:skill-name</code> e não conflita com skill pessoal ou de projeto. Um <code>my-plugin/skills/deploy/SKILL.md</code> vira <code>/my-plugin:deploy</code>. E o comando <code>claude plugin init</code> cria a pasta em <code>~/.claude/skills/</code> com o manifesto <code>.claude-plugin/plugin.json</code> e um <code>SKILL.md</code> inicial
A outra nota é chata e pega muita gente: fora do Claude Code, a especificação de Agent Skills aceita apenas as propriedades <code>allowed-tools</code>, <code>compatibility</code>, <code>description</code>, <code>license</code>, <code>metadata</code> e <code>name</code>. Seis, e só. Um campo fora da lista faz o empacotamento ou o upload falhar, então se você planeja levar a skill pro claude.ai, não invente campo no frontmatter
Conclusão
O princípio cabe em uma frase: a skill descreve o contrato semântico, o CSS resolve o valor por tema
Quando você separa assim, dark mode em skill de design deixa de ser uma segunda paleta pra manter e vira uma consequência do vocabulário que você já declarou. Um par de tokens, uma regra de troca, zero listagem duplicada
Próximo passo é prático: cria a pasta com o <code>SKILL.md</code>, escreve o vocabulário em pares e as proibições, salva (lembra que ele recarrega sozinho) e pede um componente novo
Aí abre nos dois temas e passa a régua de contraste antes de aceitar o código: 4.5:1 no texto, 3:1 no não textual
Se o componente sobreviver aos dois, sua skill está escrita direito 😀
até o próximo post!
Perguntas frequentes
Posso combinar light-dark() com o seletor .dark do shadcn/ui na mesma variável?
Não é recomendado. Se o valor do token já usa light-dark(), redefinir esse mesmo token dentro de .dark cria duas fontes de verdade brigando pelo mesmo nome. Escolha um caminho só: ou light-dark() no :root, ou a redefinição dentro de .dark, nunca os dois juntos no mesmo token.
Preciso declarar color-scheme mesmo usando só utilitários do Tailwind?
Se você usa a função light-dark() no CSS, sim: color-scheme é a propriedade que habilita essa função e permite sobrescrever o esquema de cor do usuário em trechos do documento. O Tailwind CSS v4 também traz utilitários próprios de color-scheme, que ajudam a controlar como elementos nativos renderizam no tema escuro.
Qual contraste mínimo o dark mode da skill precisa garantir pra passar na WCAG?
Pelo critério 1.4.3, texto normal precisa de contraste mínimo de 4.5:1 e texto de grande escala de 3:1. Pelo critério 1.4.11, elementos não textuais como bordas, ícones e indicadores de estado precisam de pelo menos 3:1. Vale lembrar que esses valores são limiares: 4.499:1 não atende o limite de 4.5:1, então não dá pra arredondar pra cima.
A skill de tema precisa ficar em .claude/skills ou dá pra distribuir como plugin?
Os dois caminhos existem. Skill pessoal fica em ~/.claude/skills/<nome-da-skill>/ e skill de projeto em .claude/skills/<nome-da-skill>/. Se a skill vier de um plugin, ela usa o namespace plugin-name:skill-name e não conflita com as skills pessoais ou de projeto que você já tem.
Como o Claude decide sozinho quando acionar a skill de design system?
Ele lê o campo description do frontmatter, que fica sempre carregado no contexto mesmo antes de a skill ser acionada. Por isso essa description precisa ter a capacidade (o que a skill faz) e o gatilho (quando usar), como no exemplo do passo 1 deste post. Descrição genérica demais faz a skill ficar parada sem ser chamada.
Dá pra criar um token só pro tema escuro, tipo –card-dark?
Não, e esse é o erro comum descrito no passo 2. Nunca se declara um token exclusivo pra um tema: o mesmo nome (–background, –border, e por aí vai) precisa valer nos dois, mudando só o valor resolvido pelo CSS. Token com o tema no nome é a duplicação que este post inteiro tenta evitar voltando pela porta dos fundos.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares
Skill de design serve para vários projetos ou tem que refazer em cada repositório?
Skills Claude Design funcionam em vários projetos ou é preciso recriar em cada repositório? Veja a diferença entre escopo pessoal e escopo de projeto.
Como importar seu design system no Claude Design: GitHub, arquivos ou codebase local
Importe seu design system no Claude Design via GitHub, arquivos de design, uploads ou codebase local com /design-sync e publique para a organização usar.
Como o administrador aprova e trava um sistema de design no Claude Design?
Só quem tem a permissão Claude Design Admin pode publicar, definir padrão e excluir sistemas de design. Veja como o Owner libera isso no Enterprise.
