Criar um estilo
Um estilo é um arquivo CSS. Defina valores de token com o nome do seu estilo na camada cml.style, acrescente algumas regras com escopo se precisar e confira o contraste nos dois modos.
// você vai aprender
- Quais tokens fazem uma aparência, e quais você pode deixar no padrão
- Como escrever o arquivo:
@layer cml.style,light-dark(),@scope … to ([data-style]) - Como testar o contraste do jeito que a biblioteca testa os próprios 13 estilos
Veja um sendo construído
Este é o "sunset", o estilo escrito nesta página. Cada passo acrescenta algo ao arquivo; a demo roda o resultado com a marca e o modo da barra "visualizar em".
Convide sua equipe
A pessoa recebe um e-mail com um link que vale por 7 dias.
@layer cml.style {
[data-style="sunset"] {
}
}Padrões — um nome ainda sem tokens fica igualzinho ao minimal.
Dê um nome e abra a camada
Escolha um nome em minúsculas e escreva tudo dentro de @layer cml.style, a camada que os 13 estilos usam. Isso põe o seu estilo acima dos padrões e das regras dos componentes, e deixa o CSS sem camada do seu app livre para sobrescrevê-lo.
@layer cml.style {
[data-style="sunset"] {
/* os tokens vão aqui */
}
}Defina os tokens que fazem a aparência
Nenhum token é obrigatório: o que você deixar de fora mantém o padrão do minimal. Na prática, uma aparência vem destes grupos, mais ou menos nesta ordem de impacto:
| Grupo | Tokens para começar |
|---|---|
| Tipografia | --cml-font-body, e --cml-font-display / --cml-font-control se forem diferentes |
| Superfícies e texto | --cml-surface, --cml-surface-raised, --cml-fg, --cml-fg-secondary, --cml-fg-muted |
| Linhas | --cml-border, --cml-border-control, --cml-border-width |
| Acento | --cml-accent, --cml-accent-fg, --cml-accent-hover: quais passos da marca você usa |
| Forma | --cml-radius-control, --cml-radius-field, --cml-radius-surface |
| Profundidade | --cml-shadow-control, --cml-shadow-surface, --cml-shadow-popover, --cml-card-shadow |
| Densidade | --cml-control-height, --cml-field-height, --cml-surface-padding |
Escreva toda cor como light-dark(light, dark), para que uma declaração cubra os dois modos. No acento, escolha passos da marca, nunca uma cor fixa, e sempre junte um passo ao -fg e ao -hover dele: é isso que mantém o texto legível com qualquer marca. A página Tokens lista todos, com os padrões.
Precisa de um preenchimento que não é superfície nem acento? Declare um token seu ao lado dos outros (o neubrutalism tem --cml-ink e --cml-highlight) e use-o nas suas regras com escopo.
Acrescente regras com escopo, se precisar
Algumas aparências pedem uma estrutura que um token não expressa: um divisor, um chanfro, outro hover. Ponha essas regras num @scope com o seu estilo como raiz e to ([data-style]) como limite, para que nunca cheguem a uma ilha aninhada em outro estilo.
@layer cml.style {
@scope ([data-style="sunset"]) to ([data-style]) {
[data-slot="card-header"] {
border-bottom: var(--cml-border-width) solid var(--cml-border);
}
}
}Cada regra que você acrescenta é uma regra que você precisa conferir nos dois modos, com movimento reduzido e com cores forçadas. Prefira um token de componente (eles estão listados em Styling hooks em cada página de componente) a uma regra.
Use o estilo
Importe o arquivo depois do base.css e passe o nome. O styleName aceita qualquer nome que o seu CSS use, então o TypeScript aceita sem cast:
import "@camaleo/styles/base.css";
import "./sunset.css";
import { StyleProvider } from "@camaleo/react";
<StyleProvider styleName="sunset" mode="system">
<App />
</StyleProvider>
Teste o contraste
A biblioteca passa cada estilo × claro e escuro × cinco cores de marca difíceis (azul, amarelo, ciano, coral, cinza) pelo axe e por um conjunto de checagens de contraste. Faça o mesmo com o seu. As checagens leem os tokens computados, então precisam de um navegador de verdade (modo browser do Vitest ou Playwright):
import "@camaleo/styles/base.css";
import "./sunset.css";
import { StyleProvider } from "@camaleo/react";
import { wcagContrast } from "culori";
import { expect, it } from "vitest";
import { render } from "vitest-browser-react";
import { Screens } from "./screens";
// Resolve um token para uma cor deixando o navegador computá-la.
function token(scope: Element, name: string) {
const probe = document.createElement("span");
probe.style.color = `var(${name})`;
scope.append(probe);
const color = getComputedStyle(probe).color;
probe.remove();
return color;
}
for (const mode of ["light", "dark"] as const)
for (const brand of ["#2563eb", "#facc15", "#22d3ee", "#ff7f50", "#808080"])
it(`sunset / ${mode} / ${brand}`, async () => {
// As suas telas, no estilo, modo e marca testados.
const screen = await render(
<StyleProvider styleName="sunset" mode={mode} brand={brand}>
<Screens />
</StyleProvider>,
);
const root = screen.container.firstElementChild as Element;
const surface = token(root, "--cml-surface");
expect(wcagContrast(token(root, "--cml-fg-muted"), surface)).toBeGreaterThanOrEqual(4.5);
expect(wcagContrast(token(root, "--cml-border-control"), surface)).toBeGreaterThanOrEqual(3);
expect(wcagContrast(token(root, "--cml-focus-ring"), surface)).toBeGreaterThanOrEqual(3);
});
Confira também --cml-danger, --cml-success, --cml-warning e --cml-info contra a sua superfície (4,5:1): os padrões deles foram ajustados para branco e quase preto, e uma superfície tingida pode jogá-los para baixo do limite. As superfícies quentes do sunset mantêm todos acima de 4,7:1.
O arquivo completo
O estilo inteiro desta página, pronto para copiar:
/* Sunset: papel quente, contornos de tinta, controles redondos e uma sombra deslocada e dura. */
@layer cml.style {
[data-style="sunset"] {
--cml-font-body: "Fraunces", Georgia, serif;
--cml-surface: light-dark(#fff7ef, #1c1410);
--cml-surface-raised: light-dark(#fffdf9, #261b15);
--cml-fg: light-dark(#2b1a14, #f6e9df);
--cml-fg-secondary: light-dark(#5a4034, #d9c4b6);
--cml-fg-muted: light-dark(#7a5a4a, #b39a8a);
--cml-border: var(--cml-fg);
--cml-border-control: light-dark(#8a6a58, #9a7f6e);
--cml-accent: light-dark(var(--cml-brand-700), var(--cml-brand-300));
--cml-accent-fg: light-dark(var(--cml-brand-700-fg), var(--cml-brand-300-fg));
--cml-accent-hover: light-dark(var(--cml-brand-700-hover), var(--cml-brand-300-hover));
--cml-border-width: 2px;
--cml-radius-control: 20px;
--cml-radius-field: 12px;
--cml-radius-surface: 24px;
--cml-card-shadow: 6px 6px 0 var(--cml-border);
--cml-shadow-popover: 4px 4px 0 var(--cml-border);
}
@scope ([data-style="sunset"]) to ([data-style]) {
[data-slot="card-header"] {
border-bottom: var(--cml-border-width) solid var(--cml-border);
}
}
}Antes de publicar
- Texto com pelo menos 4,5:1, e bordas de controle e anel de foco com pelo menos 3:1, no modo claro e no escuro
- As cinco cores de marca difíceis testadas nos dois modos
- O anel de foco visível em toda superfície que você acrescentou
- Regras com escopo conferidas com movimento reduzido e cores forçadas
- As fontes carregadas em todo lugar onde o estilo é usado