Pular para o conteúdo
// visualizar em
salvo em todas as páginas

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.

sunset.css
@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.

sunset.css
@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:

GrupoTokens 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.

DICA

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.

sunset.css
@layer cml.style {
  @scope ([data-style="sunset"]) to ([data-style]) {
    [data-slot="card-header"] {
      border-bottom: var(--cml-border-width) solid var(--cml-border);
    }
  }
}
CUIDADO

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:

app/providers.tsx
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):

sunset.test.tsx
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.css
/* 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
Esta página foi útil? · Editar esta página no GitHub