# Temas e personalização

Nenhum componente da biblioteca conhece a cor da marca. Ele pede um papel, "a
cor de acento", "a superfície levantada", e o tema responde. É isso que permite
a mesma peça servir a RivoCode num projeto e a um cliente no seguinte, sem tocar
em componente.

## As três camadas

```
Camada 1  paleta      --rc-p-lima-500: oklch(...)       a cor crua, sem opinião
Camada 2  contrato    --color-accent: var(--rc-accent)  vira bg-accent no Tailwind
Camada 3  tema        --rc-accent: var(--rc-p-lima-500) quem faz o papel, aqui
```

A **paleta** é um dicionário de cores. A **camada de contrato** liga cada papel
ao vocabulário do Tailwind: todo nome ali vira utilitário, então `--color-surface`
produz `bg-surface`, `text-surface` e `border-surface`, dentro da biblioteca e
no layout que você escreve. O **tema** é a única camada que decide qual cor faz
qual papel.

Trocar de cliente é reescrever a camada 3. Nada mais.

## Os dois temas prontos

```tsx
<RivoProvider theme="rivocode-dark">   {/* padrão */}
<RivoProvider theme="rivocode-light">
<RivoProvider theme="system">          {/* segue o sistema operacional */}
```

Com `system`, o Provider lê `prefers-color-scheme` e acompanha a troca enquanto
a página está aberta, não é só a leitura inicial.

O `data-rc-theme` que o Provider escreve também **pinta o fundo e a cor de
texto** do elemento que o carrega. Com `scope="global"` isso é a página; com
`scope="local"`, só aquela árvore.

## Preenchimento e texto são tokens diferentes

Esta é a distinção que mais economiza tempo depois:

```tsx
<div className="bg-danger text-danger-fg">Botão vermelho, texto por cima</div>
<p className="text-danger-text">Mensagem de erro sobre o fundo da página</p>
```

`bg-danger` é o vermelho que **preenche** e recebe `text-danger-fg` em cima.
`text-danger-text` é o vermelho que **se lê** sobre o fundo. Nenhuma cor serve
bem para as duas coisas: a que tem contraste como texto não aguenta texto branco
por cima, e a que aguenta é clara demais para ler. Vale igual para o acento,
para `success`, `warning` e `info`.

## Todos os papéis

Um tema completo declara todos os que vêm abaixo; faltando um, o componente que o
usa cai no valor do tema anterior, e o sintoma costuma ser uma cor da RivoCode
isolada no meio do azul do cliente. Depois deles vêm três papéis de acabamento
(gradiente, brilho e vidro) que são os únicos opcionais.

### Superfície

| Token | Classe | O que veste |
|---|---|---|
| `--rc-bg` | `bg-bg` | O fundo da página |
| `--rc-surface` | `bg-surface` | Cartão, painel, campo |
| `--rc-surface-raised` | `bg-surface-raised` | O que salta do resto: menu, dica, cabeçalho de tabela |
| `--rc-overlay` | `bg-overlay` | A tarja escura atrás de diálogo e folha |

### Texto

| Token | Classe | O que veste |
|---|---|---|
| `--rc-fg` | `text-fg` | Texto principal |
| `--rc-fg-muted` | `text-fg-muted` | Texto de apoio, parágrafo secundário |
| `--rc-fg-subtle` | `text-fg-subtle` | Rótulo, legenda, cabeçalho de coluna |
| `--rc-fg-disabled` | `text-fg-disabled` | Controle desativado |

### Acento

| Token | Classe | O que veste |
|---|---|---|
| `--rc-accent` | `bg-accent` | Preenchimento da marca: botão primário, marca de escolha |
| `--rc-accent-hover` | - | O mesmo com o ponteiro em cima |
| `--rc-accent-active` | - | O mesmo no instante do clique |
| `--rc-accent-fg` | `text-accent-fg` | O que se lê **sobre** o acento |
| `--rc-accent-text` | `text-accent-text`, `bg-accent-text` | O acento que se lê **sobre o fundo**: link, item ativo, trilho da chave ligada |
| `--rc-accent-subtle` | `bg-accent-subtle` | Fundo tênue de item marcado, item de menu sob o ponteiro |

### Linha, foco e estado de linha

| Token | Classe | O que veste |
|---|---|---|
| `--rc-border` | `border-border` | A linha comum |
| `--rc-border-strong` | `border-border-strong` | A borda de um controle, que precisa se ver |
| `--rc-border-disabled` | `border-border-disabled` | A borda de um controle travado, que precisa se ver **menos** |
| `--rc-line-hover` | - | A borda com o ponteiro em cima |
| `--rc-ring` | `ring-ring` | O anel de foco do teclado |
| `--rc-selected` | `bg-selected` | Linha escolhida numa tabela. Área grande pede alfa baixo |
| `--rc-skeleton` | `bg-skeleton` | Marca de lugar do carregamento |

### Estados

Quatro famílias com quatro papéis cada, sempre no mesmo formato:

| Padrão | Classe | O que veste |
|---|---|---|
| `--rc-<estado>` | `bg-<estado>` | Preenche |
| `--rc-<estado>-fg` | `text-<estado>-fg` | O que se lê sobre o preenchimento |
| `--rc-<estado>-text` | `text-<estado>-text` | A cor que se lê sobre o fundo da página |
| `--rc-<estado>-subtle` | `bg-<estado>-subtle` | Fundo tênue de aviso |

Onde `<estado>` é `success`, `warning`, `danger` ou `info`. São dezesseis
tokens, e nenhum deles é opcional: um `Alert tone="warning"` sem
`--rc-warning-subtle` sai sem fundo.

O perigo é o único estado que também vira botão sólido, então
`--rc-danger` e `--rc-danger-fg` precisam de contraste de botão, não só de
etiqueta.

### Gráfico

| Token | O que veste |
|---|---|
| `--rc-chart-1` a `--rc-chart-8` | As oito séries, **na ordem em que devem ser usadas** |
| `--rc-chart-grid` | A grade de fundo |

Elas têm guarda própria de 3:1 contra a superfície. **Tema de cliente que não as
declara desenha gráfico sem cor de série.**

No CSS use sempre `var(--rc-chart-1)`, e nunca a classe `bg-chart-1`: essa
classe não existe na folha compilada, porque o Tailwind só gera o que encontra
ao varrer, e o resultado seria uma cor que nunca resolve, em silêncio.

### Sombra e tipografia de marca

| Token | O que veste |
|---|---|
| `--rc-shadow-1` a `--rc-shadow-3` | `shadow-1`, `shadow-2`, `shadow-3`: linha, painel, sobreposição. Cada uma já carrega o hairline de 1px que separa o flutuante da página |
| `--rc-glow-accent` | `shadow-glow`: a lanterna do acento, opt-in (hero de landing e CTA que merece cerimônia); nenhum componente liga sozinho. Para o **tema** acender sem que cada tela peça, veja `--rc-accent-shadow` adiante |
| `--rc-text-display` | Tamanho de título de marketing, em `clamp()` |
| `--rc-text-hero` | Tamanho de herói, em `clamp()` |
| `--rc-font-sans` | `font-sans`: a família do corpo, e o padrão de toda a árvore |
| `--rc-font-display` | `font-display`: a família de título, a que carrega a marca |
| `--rc-font-mono` | `font-mono`: a família de código, tabela numérica e `Kbd` |

Os passos de marketing vivem no tema e não no núcleo de propósito: um sistema
de operação nunca os usa, e um site de marca quer os seus. O glow segue a mesma
lógica: no escuro a lima ilumina, no claro quem sombreia é o tom escurecido
dela, e um tema de cliente decide o próprio brilho.

**A fonte é papel de tema, e não escala.** Os dois temas da casa declaram as
três famílias, e um tema de cliente que não as declara fica **sem família
nenhuma**: a árvore cai na fonte do navegador, exatamente como acontece com um
tema que esquece `--rc-bg`. Não há valor de `:root` por baixo para segurar a
queda, e isso é deliberado: uma fonte de sistema silenciosa por baixo faria a
falta parecer escolha, e o cliente descobriria meses depois que metade da tela
nunca vestiu a marca dele.

As famílias da RivoCode (Manrope, Poppins e JetBrains Mono) **não vêm mais
junto com o `styles.css`**. Quem quer a marca importa o arquivo de faces
separado; quem veste outra fonte instala a dela e nunca baixa as nossas:

```css
@import "@rivocode/ui/styles.css";
@import "@rivocode/ui/fonts.css";   /* só quem quer as faces da RivoCode */
```

Para vestir a fonte do cliente, instale a família e aponte os três tokens no
mesmo seletor de tema em que você já declarou as cores:

```css
@import "@rivocode/ui/styles.css";
@import "@fontsource-variable/inter";

[data-rc-theme="cliente-acme"] {
  --rc-font-sans: "Inter Variable", system-ui, sans-serif;
  --rc-font-display: "Inter Variable", system-ui, sans-serif;
  --rc-font-mono: ui-monospace, SFMono-Regular, monospace;

  /* …e os cinquenta papéis de cor. */
}
```

Repare que o `@rivocode/ui/fonts.css` **não** aparece aí: é assim que dois
clientes convivem na mesma aplicação, cada um com a sua família, e nenhum dos
dois carregando os 220 KB de `.woff2` da RivoCode.

### Acabamento: gradiente, vidro e brilho

Três papéis que não pintam cor, e sim o que vem por cima dela. São os **únicos
opcionais** do guia: ausentes, o gesto simplesmente não acontece, e é assim que
os dois temas da casa nascem: os três declarados como `none`.

| Token | Onde chega | O gesto |
|---|---|---|
| `--rc-accent-image` | `background-image` de quem veste `bg-accent` | O acento em gradiente |
| `--rc-accent-shadow` | `box-shadow` do mesmo `bg-accent` | "Neste tema o primário brilha", sem `shadow-glow` em tela nenhuma |
| `--rc-overlay-filter` | `backdrop-filter` da tarja, `bg-overlay` | Vidro fosco atrás de diálogo, folha e paleta de comando |

O acabamento viaja junto com o papel, e não com a peça: quem já vestia
`bg-accent` recebe o gradiente e o brilho, quem já vestia `bg-overlay` recebe o
vidro. É o que torna a tarja alcançável: ela é um nó interno do portal, e
`classNames={{ backdrop }}` resolve **uma tela**, enquanto o token resolve o
tema inteiro, nas quatro peças que têm tarja, de uma vez.

Quatro coisas para saber antes de usar:

- **A classe de quem escreve a tela continua vencendo.** As regras são
  `:where()`, de especificidade zero: um `bg-none` ou um `shadow-none` no
  `className` desfaz o acabamento naquela peça, e um `bg-linear-to-r` seu
  substitui o gradiente do tema.
- **O gradiente cobre a cor.** `background-image` pinta por cima de
  `background-color`, então com um gradiente opaco o `hover:bg-accent-hover` do
  Button acontece embaixo e ninguém vê. Dê alfa ao gradiente e o hover volta a
  aparecer através dele.
- **Botão desabilitado fica de fora.** O Button neutraliza o primário morto
  trocando a cor de fundo, e o gradiente sobreviveria a essa troca; a regra o
  exclui. Carregando não é desabilitado para esse fim: ali a cor ainda diz qual
  ação está em andamento.
- **O alcance é o `bg-accent` escrito direto**: botão primário, barra de
  progresso. O acento que só chega sob estado, como o `data-[checked]:bg-accent`
  da caixa de marcar, compila com outro nome de classe e não recebe o
  acabamento.

No React Native os três não atravessam: gradiente e `backdrop-filter` não são
propriedades de `View`, e o gerador de tema nativo os ignora em silêncio, como
já faz com `box-shadow` e `clamp()`. São papéis de web.

#### Um tema futurista, os três de uma vez

```css
/* tema-neon.css */
[data-rc-theme="neon"] {
  color-scheme: dark;

  --rc-bg: oklch(16% 0.02 285);
  --rc-surface: oklch(21% 0.03 285);
  --rc-surface-raised: oklch(26% 0.03 285);

  /* Com vidro, a tarja preta de sempre vira lama: ela clareia e desfoca. */
  --rc-overlay: oklch(14% 0.04 285 / 0.55);
  --rc-overlay-filter: blur(10px) saturate(130%);

  --rc-accent: oklch(64% 0.21 300);
  --rc-accent-hover: oklch(70% 0.21 300);
  --rc-accent-active: oklch(58% 0.21 300);
  --rc-accent-fg: oklch(99% 0 0);

  /* Alfa de propósito: o gradiente cobre a cor, e sem ele o hover do Button
     acontece embaixo, invisível. */
  --rc-accent-image: linear-gradient(
    135deg,
    oklch(64% 0.21 300 / 0.92),
    oklch(72% 0.16 200 / 0.92)
  );

  /* O brilho deixa de ser enfeite que cada tela liga e vira estado do acento. */
  --rc-accent-shadow: 0 0 28px oklch(64% 0.21 300 / 0.45);

  /* …e os cinquenta papéis obrigatórios. */
}
```

O que muda na tela, sem uma linha de componente ou de página: o botão primário
sai em degradê violeta→ciano e acende sozinho, e volta ao roxo chapado quando
desabilita; a tarja do `Dialog`, do `AlertDialog`, do `Sheet` e do `Command`
vira vidro fosco.

### Forma e movimento

Cor não é a única coisa que um tema decide. Canto reto e movimento seco dizem
"futurista" antes de qualquer cor, e esses nove tokens vivem em
`src/tokens/forma.css`, fora da escala, justamente para o tema poder redefinir:

| Token | O que decide |
|---|---|
| `--rc-radius-sm` a `--rc-radius-xl` | O canto de campo, cartão, painel e diálogo |
| `--rc-radius-pill` | A pílula: chave, badge, avatar, barra |
| `--rc-duration-fast`, `--rc-duration-base`, `--rc-duration-slow` | O tempo de cada transição |
| `--rc-duration-sheet`, `--rc-ease-sheet` | O tempo e a curva da folha lateral, que segue o dedo |
| `--rc-ease` | A curva de todo o resto: seca e mecânica, ou macia |
| `--rc-tracking-display`, `--rc-tracking-tight` | O espaçamento de letra do título |

Redefina no mesmo seletor do tema, junto com os papéis de cor:

```css
[data-rc-theme="acme"] {
  --rc-radius-md: 0px;                          /* canto reto */
  --rc-duration-base: 140ms;                    /* movimento seco */
  --rc-ease: cubic-bezier(0.16, 1, 0.3, 1);
}
```

A ordem já está resolvida pelo preset: `forma.css` entra antes dos temas, e
`:root` e `[data-rc-theme="x"]` têm a mesma especificidade, então o tema vence.

## O que o tema precisa garantir

Os papéis não são independentes. Estas relações precisam valer, e as cinco
primeiras são medidas por `bun run check`. Um tema que as quebra falha no CI,
e não na tela do cliente:

| Invariante | Por quê |
|---|---|
| `--rc-border-strong` a 3:1 da superfície | É a fronteira que identifica o controle (WCAG 1.4.11). Abaixo disso o campo não se distingue da página |
| `--rc-border-disabled` acima de 1,6:1 da superfície, e a 1,4× **abaixo** de `--rc-border-strong` | É o único papel com teto além de piso. Muito fraca, o controle travado some; igual à viva, ele fica idêntico ao controle que ainda responde, e a WCAG 1.4.11 dispensa componente inativo dos 3:1 justamente para abrir essa faixa |
| `--rc-<estado>-text` a 4,5:1 sobre `--rc-<estado>-subtle` | É o par que a pessoa lê no `Alert`, e não o texto contra `--rc-bg`. O alfa é composto antes de medir |
| `--rc-ring` a 3:1 contra `--rc-bg` e contra `--rc-surface` | O foco precisa aparecer nos dois fundos, e não só num |
| `--rc-accent-text` a 3:1 sobre `--rc-skeleton` **composto** no fundo | É o preenchimento e a borda do pino do `Slider` contra o trilho vazio (WCAG 1.4.11). Empatados, o trilho cheio pesa o mesmo que o vazio e ninguém lê quanto já foi. O trilho carrega alfa, então o cinza medido é o que sobra dele sobre a página e sobre o cartão |
| `--rc-skeleton` diferente da superfície | Ele é a marca de lugar do que está carregando, e o corpo do `Avatar`. Igual à superfície, os dois somem |

`--rc-surface` e `--rc-surface-raised` **podem** ser a mesma cor: no tema claro
da casa as duas são branco puro, e cartão branco sobre página cinza é o padrão
de nove entre dez painéis. Componente nenhum pode depender dessa diferença para
existir visualmente; quem precisa de corpo próprio veste `--rc-skeleton`, e quem
precisa dizer "travado" veste `--rc-border-disabled`. Os dois tokens existem pelo
mesmo motivo: são a saída de quem tropeçou em branco sobre branco e tentou
resolver subindo a superfície.

## O que **não** entra no tema

Altura de controle e respiro vivem em `src/tokens/scales.css` e valem para
todos os temas. Um tema que redefine `--rc-control-md` está resolvendo
densidade no lugar errado, para isso existe `density="compact"`, e ele muda a
escala inteira de uma vez. Escala de texto e empilhamento seguem a mesma
regra: são estrutura, e mudar deixaria de ser tema.

## Um tema de cliente, do começo ao fim

Digamos que o cliente é azul.

**1. Declare os papéis num seletor de tema.** Só os papéis; a paleta pode ser
sua ou a nossa:

```css
/* tema-acme.css */
[data-rc-theme="acme"] {
  color-scheme: dark;

  --rc-bg: oklch(21% 0.02 250);
  --rc-surface: oklch(26% 0.02 250);
  --rc-surface-raised: oklch(31% 0.02 250);
  --rc-overlay: oklch(0% 0 0 / 0.62);

  --rc-fg: oklch(97% 0.01 250);
  --rc-fg-muted: oklch(82% 0.01 250);
  --rc-fg-subtle: oklch(64% 0.01 250);
  --rc-fg-disabled: oklch(50% 0.01 250);

  --rc-accent: oklch(62% 0.19 250);
  --rc-accent-hover: oklch(66% 0.19 250);
  --rc-accent-active: oklch(58% 0.19 250);
  --rc-accent-fg: oklch(99% 0 0);
  --rc-accent-text: oklch(74% 0.16 250);
  --rc-accent-subtle: oklch(62% 0.19 250 / 0.14);

  --rc-border: oklch(100% 0 0 / 0.1);
  --rc-border-strong: oklch(100% 0 0 / 0.14);
  --rc-line-hover: oklch(100% 0 0 / 0.26);
  --rc-ring: oklch(62% 0.19 250);
  --rc-selected: oklch(62% 0.19 250 / 0.08);
  --rc-skeleton: oklch(100% 0 0 / 0.08);

  /* …e as quatro famílias de estado, as oito séries de gráfico,
     as três sombras e os dois tamanhos de marca. */
}
```

**2. Importe depois do preset**, para a sua camada 3 vencer:

```css
@import "tailwindcss";
@import "@rivocode/ui/preset";
@import "./tema-acme.css";

@source '../node_modules/@rivocode/ui/dist';
```

**3. Vista a árvore:**

```tsx
<RivoProvider theme="acme">
```

A prop aceita o nome do seu tema, e não só os dois de casa. Para guardar a
escolha num seletor, o tipo é `RivoThemeSetting` (os de casa, `system` e o
nome do cliente), com o autocomplete dos conhecidos preservado:

```tsx
const [tema, setTema] = useState<RivoThemeSetting>("acme")
```

O `color-scheme` na primeira linha não é enfeite: sem ele o navegador desenha
barra de rolagem, campo de data e menu nativo no esquema errado, e nenhum token
alcança essas peças.

**4. Confira que não falta papel:**

```bash
npx rivocode-ui check-theme src/tema-acme.css
```

O comando vem no pacote e roda no **seu** projeto, que é onde o tema mora. Ele
lê os arquivos que você passar, junta as declarações por seletor de tema, e
cobra os cinquenta e cinco papéis obrigatórios. Se faltar algum ele sai com
código 1, então uma linha no CI segura a quebra antes do deploy:

```yaml
- run: npx rivocode-ui check-theme src/temas/*.css
```

Passe **todos** os arquivos que formam o tema de uma vez. Se você separou cor e
tipografia em dois arquivos, o comando só junta os dois se os dois estiverem na
mesma chamada; o que ele não leu conta como faltando. Com `--json` a saída vira
um objeto com `ok` e a lista de papéis, para o seu pipeline ler sem regex.

**5. E deixe ele medir o contraste.** Depois da completude, o mesmo comando mede
os 76 pares por tema: o texto sobre os três fundos, a fronteira de controle de
3:1 da WCAG 1.4.11, o anel de foco, as oito cores de série, o trilho do `Switch`
ligado e os pares em que o fundo é alfa e precisa ser composto antes de medir. É
a mesma conta e a mesma tabela que o design system cobra de si mesmo — ela mora
num módulo do pacote, e não numa pasta de scripts que o npm não leva.

A ordem das duas perguntas é decisão: papel faltando primeiro, porque medir o
contraste de um papel que não existe cai no valor herdado e devolve um número
bonito por acidente. Se falta papel, o comando para antes de medir.

**A conta lê os espaços de cor modernos, e converte tudo para sRGB antes de
medir.** Hexadecimal de 3, 4, 6 e 8 dígitos, `rgb()`, `rgba()`, `hsl()`,
`hsla()`, `hwb()`, `lab()`, `lch()`, `oklab()`, `oklch()` e `color()` nos
espaços predefinidos do CSS. A paleta do Tailwind 4 é escrita em `oklch()`,
então cor copiada de lá entra direto, sem passar por conversor — que era o
caminho mais comum de vestir um cliente e o único que saía **sem medida**.

Ficam de fora duas coisas, e as duas por não terem medida possível:
`color-mix()`, que não é uma cor e sim uma conta cujo resultado depende do
espaço de interpolação e do método de matiz; e nome de cor da CSS, como
`rebeccapurple`, porque o pacote não carrega a tabela de nomes. Essas saem sem
medida e o comando reprova, em vez de ficar verde sem ter olhado — o que não se
mede não se promete.

**Cor que o sRGB não alcança é medida no valor que a tela mostra.** Quase um
terço da paleta do Tailwind 4 — 82 das 286 cores nomeadas — descreve tom fora do
gamut do sRGB: `red-500`, `blue-500` e companhia estão nessa faixa. O navegador
corta o excedente canal por canal na hora de pintar, e é esse pixel cortado que
a pessoa vê e que a conta mede. O comando diz quais papéis caíram ali, e para
qual valor:

```
nota   2 papéis descrevem tom fora do sRGB. A tela corta o excedente canal por
       canal, e é o valor cortado que foi medido — o mesmo pixel que o navegador
       pinta: accent (oklch(63.7% 0.237 25.331) → #fb2c36), ring (…)
```

Recusar seria mais fácil e diria menos: fecharia a porta justamente para as
cores mais copiadas que existem, e o número que interessa — o contraste do que
está na tela — é o do valor cortado.

**O mapa do React Native entra pelo mesmo comando.** A extensão separa as duas
formas: `.css` é a camada 3 do web, e `.ts`, `.mjs` ou `.js` é o objeto com
`light` e `dark` que o `RivoProvider` do `@rivocode/ui-native` recebe — o arquivo
que `bun run gen:native --tema` escreve.

```bash
npx rivocode-ui check-theme acme.theme.ts
```

No projeto nativo a mesma tabela de pares está em
`@rivocode/ui-native/contrast`, para medir por código sem instalar o pacote web.

### O que ele diz, e por que não é só o nome do token

"Falta `--rc-font-sans`" não faz ninguém consertar nada. O que faz é a linha de
baixo:

```
[data-rc-theme="neon"]   (src/tema-neon.css)
  52 dos 55 papéis. Faltam 3.

  QUEBRA CALADA, e é por isso que ninguém reporta:

    --rc-font-sans
      A página inteira cai na fonte do navegador. Não há valor de `:root` por
      baixo para segurar a queda, e isso é de propósito: o `tsc` compila, o
      Vite constrói, e a única coisa errada é a tela.
      Papel novo na 0.7.0: as três famílias saíram de `src/tokens/scales.css`,
      que é camada global, e passaram para dentro do seletor de tema. Um tema
      escrito para a 0.6.x compila, constrói e renderiza sem família nenhuma.
```

Três coisas que ele decide, e o motivo de cada uma:

- **Ele cobra os papéis de cor também, e não só os que quebram calados.** Um
  tema sem `--rc-accent` sai obviamente errado; um sem `--rc-font-sans`, não. Os
  dois falham, e a diferença fica na apresentação: as faltas vêm separadas em
  quebra calada e quebra visível. "Visível" quer dizer visível **na tela que usa
  o papel**, e não na que você abriu para conferir. Um `--rc-warning-subtle`
  ausente só aparece na tela que tem `Alert tone="warning"`, e ela pode não ser
  nenhuma das três que você olhou. Cobrar só a fonte ensinaria que o resto é
  opcional, e o resto não é.
- **Ele avisa quando o papel nasceu numa versão nova.** É o caso da fonte na
  0.7.0 e o do `--rc-border-disabled` na mesma versão: quem escreveu o tema para
  a versão anterior não tem como saber que passou a faltar alguma coisa, porque
  nada no upgrade avisa. Rodar o comando logo depois de subir a versão é o
  momento barato de descobrir.
- **A saída serve às duas leituras.** Texto para quem vai consertar, `--json`
  para quem vai automatizar, e o código de saída para os dois: 0 quando todo
  tema está completo, 1 quando falta papel, e também 1 quando nenhum bloco de
  tema foi encontrado nos arquivos passados, para o comando não passar verde por
  não ter olhado nada.

Os três papéis de acabamento são os únicos que ele não cobra, porque são os
únicos opcionais. Os tokens de forma também ficam de fora: eles têm valor de
`:root` por baixo, e faltar um deles não deixa nada sem valor.

## O mesmo tema no React Native

O arquivo que você acabou de escrever veste as duas plataformas, e **a fonte é
uma só de propósito**: um segundo lugar para manter a cor de um cliente é como a
promessa se quebra na prática, não por decisão, por divergência silenciosa seis
meses depois.

O que muda é **quando** a cor é decidida. No web a camada 3 é lida em runtime, e
`<RivoProvider theme="acme">` troca a página inteira com ela aberta. No React
Native o compilador do `react-native-css` resolve o token **em build** e crava o
valor dentro da regra: `.bg-accent` vira `{"backgroundColor":"#d4f34a"}`, literal,
e nos 56 KB de CSS compilado não sobra **uma ocorrência de `--`**. Não existe
variável viva para redefinir depois.

Escrito de uma vez, para quem está decidindo agora: **tema de cliente no nativo
é geração de CSS, e não troca em runtime.** O que troca em runtime lá são os dois
temas de casa, porque eles nasceram dentro do `light-dark()` que o compilador
entende.

### O caminho que funciona: sobrescrever os papéis antes de compilar

Um `@theme` seu no `global.css` do app, depois do `theme.css` do pacote, com os
papéis que a marca troca. É a mesma camada 3 de sempre, escrita no vocabulário
`--color-*` que o compilador nativo lê:

```css
@import "tailwindcss/theme.css" layer(theme);
@import "@rivocode/ui-native/theme.css";
@import "tailwindcss/utilities.css";

@theme {
  --color-accent: #2563eb;
  --color-accent-hover: #3b82f6;
  --color-accent-fg: #ffffff;
  --color-bg: light-dark(#f7f8fa, #0d1220);
  --color-surface: light-dark(#ffffff, #141b2d);
  /* …e os outros papéis que a marca troca. */
}

@source "./App.tsx";
@source "./node_modules/@rivocode/ui-native/src";
```

Depois `npx rivocode-ui-native-css`, e o app importa o `generated.css` como
sempre. Daí em diante a tela é do cliente onde a classe pinta: fundo, cartão,
botão, selo, borda, série de gráfico.

**E este caminho tem um teto: dois temas por build.** Cada papel sai como
`light-dark(claro, escuro)`, e `light-dark()` tem duas vagas, uma clara e uma
escura. Um app de um cliente cabe folgado, e é o caso normal. Uma vitrine de
cinco temas, como a que este site tem no web, **não cabe**: são cinco bundles.
É teto de arquitetura, e não pendência.

### O `@theme` do app agora veste a tela INTEIRA, inclusive o gráfico

Até 27/08/2026 este caminho vestia só metade: a cor pintada por classe seguia a
marca, e a cor que a peça lê por JS - a fatia do `ChartDonut`, o trilho do
`Switch`, o giro do `Button` - continuava saindo do mapa de tokens da RivoCode.
O sintoma **não era a marca ausente: era a tela misturada**, e era isso que
custava um dia de depuração.

Não custa mais. O `RivoProvider` resolve os 45 papéis **lendo o CSS compilado**,
uma classe `bg-` por papel, e publica o resultado no contexto que as peças já
liam. Então o que você sobrescreve no `@theme` chega aos dois lados de uma vez:
classe e contexto dizem sempre a mesma cor.

### A prop `theme` não recebe mais um mapa

```tsx
<RivoProvider theme="rivocode-dark">
```

**O mapa saiu.** Ele nunca alcançou a cor pintada por classe, e manter uma
metade que discorda da outra era pior do que não ter nenhuma. Passou por
`@deprecated` e por um aviso em `__DEV__`, e agora não existe mais: a prop
`theme` aceita só `rivocode-dark`, `rivocode-light` e `system`. A prop `scheme`
saiu junto, porque era ela que escolhia o esquema do mapa.

O gerador continua emitindo o arquivo de mapa -
`bun run gen:native --tema tema-acme.css` -, agora só como conferência de papel
faltando e como entrada da medição de contraste. Para vestir a tela, use o
`@theme` de cima.

### A regra que as peças seguem

Peça que pinta por fora da classe (o trilho do `Switch`, o giro do `Button`, a
cor da `Sparkline`, a fatia do `ChartDonut`) lê os papéis do contexto
(`useRivo().colors`), e nunca de `tokens.themes`. O contexto agora É o CSS
compilado, então ler dali é ler a mesma cor que a classe pinta. Lendo
`tokens.themes` direto a peça voltaria a discordar da tela do cliente, e há
teste que falha se alguém voltar a ler direto.

No `react-native-web` - a bancada onde se inspeciona a árvore e se tira retrato
sem simulador - a leitura sai do `getComputedStyle` do documento, e não do
`useCssElement`: lá a classe vira `className` no DOM, e é o navegador quem
resolve `var()` e `light-dark()`. Mesma cor, mesma fonte, outro leitor.

## Como pedir isto a um agente

O endereço cru deste guia é
[`/temas.md`](https://ds.rivocode.com.br/temas.md). Um prompt que costuma
funcionar:

```
Leia https://ds.rivocode.com.br/temas.md e escreva o tema "acme" completo,
com todos os cinquenta papéis. A marca é azul (#2563eb), fundo escuro.
Depois confira o contraste de texto contra fundo em cada par.
```

Pedir "todos os cinquenta papéis" importa: sem isso o agente escreve os dez
óbvios e deixa gráfico e estados sem cor, que é exatamente a falha silenciosa
que a lista acima existe para evitar.

## As guardas

O repositório da biblioteca tem travas que rodam em `bun run check`, e existem
porque todas essas falhas são silenciosas:

**Cor literal.** Nenhum componente pode escrever `#d4f34a`, `bg-lime-400` ou
`rgb(...)` direto. Se pudesse, o tema do cliente não alcançaria aquela peça, e o
erro só apareceria na tela dele.

**Contraste.** Os pares de texto, os pares compostos de estado sobre o próprio
fundo, e a fronteira não-textual de 1.4.11 (nos dois temas, com o alfa
composto antes de medir). Um tema novo deve passar pela mesma medida, é a
diferença entre "parece bom no meu monitor" e "dá para ler".

**Forma documentada.** Todo token que um tema pode declarar precisa estar
citado neste guia: os papéis de cor e os de forma. Sem isso o guia passa a
mentir em silêncio, e a mentira aparece meses depois, na tela de um cliente.

## Ajuste fino com className

Toda peça (no web e no React Native) aceita `className` na raiz, e **a classe
de quem usa vence a da peça**: o merge é por grupo do Tailwind, então um
`h-14` derruba o `h-10` do Button e um `rounded-pill` derruba o `rounded-md`,
em vez de conviver com ele.

```tsx
<Button className="h-14 rounded-pill">Assinar agora</Button>
```

É isto que torna o wrapper de cliente um arquivo pequeno no projeto dele, em
vez de um fork:

```tsx
// o botão da Acme, no repositório da Acme
import { Button, type ButtonProps } from '@rivocode/ui'
import { cn } from './cn'

export function AcmeButton({ className, ...props }: ButtonProps) {
  return <Button className={cn('rounded-pill uppercase tracking-widest', className)} {...props} />
}
```

Duas regras mantêm o gesto saudável:

- **Token, nunca cor literal.** O `className` do wrapper obedece às mesmas
  regras da peça: `bg-accent` responde ao tema do cliente, `bg-[#2563eb]` não
  responde a ninguém.
- **A raiz, não as partes.** O `className` veste o elemento externo da peça.
  Nas peças com camadas (Sheet, Dialog, Select), a documentação da prop diz o
  que ela veste (o painel, o gatilho), e o que é da plataforma continua da
  plataforma.
