Estilos e troca
Treze linguagens visuais completas sobre os mesmos componentes. Carregue o CSS, envolva o app num StyleProvider e troque uma palavra para mudar tudo.
// você vai aprender
- Quais arquivos CSS e fontes cada um dos 13 estilos precisa
- O que
styleName,modeebrandfazem, e quais deles são herdados - Como aninhar estilos e trocá-los em tempo de execução sem perder estado
Experimente
Escreva na nota, curta algumas vezes e escolha outro estilo. Os componentes renderizam de novo com outro CSS; nada é montado outra vez, então o texto e a contagem continuam.
Resumo semanal
Importe o CSS
O base.css traz os tokens, as regras dos componentes e a ordem das camadas. Importe uma vez e depois um arquivo para cada estilo que você usa. O minimal é o padrão e não precisa de arquivo próprio.
import "@camaleo/styles/base.css";
import "@camaleo/styles/neubrutalism.css";
import "@camaleo/styles/editorial.css";
Se as pessoas podem escolher o estilo, importe de antemão todos os estilos que você oferece. Um estilo sem o arquivo cai nos padrões do minimal, sem nenhum erro.
Carregue as fontes
As fontes não vêm junto. Cada estilo nomeia as famílias dele nos tokens --cml-font-*, com fallbacks do sistema, então funciona mesmo sem elas, só que menos parecido consigo mesmo. Carregue só as dos estilos que você publica:
| Estilo | Fontes |
|---|---|
| minimal | Inter |
| swiss | Archivo |
| editorial | Newsreader (texto), Fraunces (títulos), Inter (controles e rótulos) |
| progressive | Figtree |
| material | Roboto Flex |
| cupertino | Inter |
| glass | Plus Jakarta Sans |
| neumorphism | Manrope |
| clay | Nunito, Fredoka (títulos) |
| neubrutalism | Space Grotesk |
| retro | IBM Plex Sans |
| retro-pixel | Pixelify Sans, Press Start 2P (títulos grandes, só latim básico) |
| y2k | Exo 2, Audiowide (títulos grandes) |
O CSS pede as famílias pelo nome, então uma tag <link> de folha de estilos ou um @font-face hospedado por você funciona do jeito que está. Com next/font, aponte o token para a variável da fonte:
/* app/layout.tsx: const grotesk = Space_Grotesk({ subsets: ["latin"], variable: "--font-grotesk" }) */
[data-style="neubrutalism"] {
--cml-font-body: var(--font-grotesk), system-ui, sans-serif;
}Envolva o app
O StyleProvider renderiza uma <div> com data-style, data-mode e os tokens da marca inline. Tudo dentro dele, inclusive diálogos e menus renderizados num portal, recebe o estilo.
import { StyleProvider } from "@camaleo/react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<StyleProvider styleName="neubrutalism" mode="system" brand="#3fa34d">
{children}
</StyleProvider>
);
}
| Prop | Valores | Herda de um provider pai? |
|---|---|---|
styleName | Um dos 13 nomes; minimal por padrão | Não: um provider aninhado sem ela é minimal |
mode | light, dark ou system | Sim, quando omitida (a raiz segue o sistema) |
brand | Qualquer cor CSS | Sim, quando omitida |
Os 13 estilos
Todo estilo renderiza o mesmo DOM: o teste de matriz compara o markup dos 13 e falha se um for diferente. Só o CSS muda, e é por isso que a troca sai barata.
| Estilo | Em uma linha |
|---|---|
| minimal | Neutro e discreto, os padrões de onde todo outro estilo parte |
| swiss | Uma grotesca, linhas de 1px em tinta, cantos retos, sem sombras |
| editorial | Papel, tinta e filetes finos, com serifas de revista |
| progressive | Cara de app instalável: fundo cinza quente, áreas de toque de 44px |
| material | Superfícies tonais, botões em pílula, campos preenchidos e contornados |
| cupertino | Fundo cinza agrupado, células brancas, cantos contínuos |
| glass | Camadas translúcidas foscas sobre um fundo suave e desfocado |
| neumorphism | Uma só superfície moldada pela luz: ações em relevo, campos afundados |
| clay | Formas fofas em tons pastel que parecem modeladas em argila |
| neubrutalism | Contornos de 2px em tinta e sombras duras, sem desfoque |
| retro | Visual clássico de desktop com chanfros em quatro cores |
| retro-pixel | 8 bits: bordas grossas em tinta, cantos em degrau, fontes pixeladas |
| y2k | Botões brilhantes de gelatina, cromado escovado e risca de giz |
O material se inspira no Material Design e o cupertino nas Human Interface Guidelines. O Camaleo não tem ligação com o Google nem com a Apple e não usa nenhuma fonte, ícone ou marca registrada deles.
Aninhamento
Um StyleProvider aninhado é uma ilha: todo token é declarado de novo no elemento dele, então ele parte de padrões limpos em vez de se misturar com o pai. O mode e o brand do pai ainda chegam até ele, a não ser que você os defina.
Seu app, na skin da barra de preview
Uma ilha retro: ela começa dos tokens limpos
Troca em tempo de execução
Guarde o estilo no estado e passe para baixo. Mudar o styleName muda um atributo no mesmo elemento, então o React mantém cada componente e o estado dele. Não coloque o estilo numa key: isso remonta a árvore e zera os formulários.
"use client";
import { useState } from "react";
import { STYLE_NAMES, type StyleName, StyleProvider } from "@camaleo/react";
export function App({ children }: { children: React.ReactNode }) {
const [skin, setSkin] = useState<StyleName>("minimal");
return (
<StyleProvider styleName={skin}>
<select aria-label="Skin" value={skin} onChange={(e) => setSkin(e.target.value as StyleName)}>
{STYLE_NAMES.map((name) => <option key={name}>{name}</option>)}
</select>
{children}
</StyleProvider>
);
}
A página inteira, a partir do servidor
Para aplicar o estilo no próprio <html> (fundo da página, barras de rolagem), espalhe styleScopeProps nele. É seguro no servidor, então um layout raiz pode chamá-lo sem um componente de cliente.
import { styleScopeProps } from "@camaleo/react/style-scope";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="pt-BR" {...styleScopeProps({ styleName: "editorial", mode: "system" })}>
<body>{children}</body>
</html>
);
}
Popups renderizam num portal fora da sua árvore. Eles mantêm o estilo pelo contexto do React, então renderize-os dentro de um StyleProvider (ou sob um <html> com styleScopeProps), nunca numa raiz React separada.
Antes de publicar
- O
base.cssé importado uma vez, antes dos arquivos dos estilos - Há um arquivo CSS para cada estilo que as pessoas podem escolher
- As fontes desses estilos estão carregadas
- O estilo fica no estado, nunca numa
key