Tema, densidade e tokens
Os tokens de todos os temas embutidos são publicados estaticamente dentro de styles.css (blocos :root[data-theme="..."] na layer cm.tokens) — nenhum setup é necessário para renderizar com o tema padrão cm-neutral. Trocar de tema é só mudar o atributo data-theme do <html>: o CmThemeProvider faz isso em runtime (e persiste em localStorage); em Next, o CmThemeScript no <head> aplica o tema salvo antes do primeiro paint, sem flash no SSR. Desde a 3.20, um tema também pode descrever vidro (surfaces), tracking (typography.tracking) e o raio dos botões (radii.button) — três escalas opcionais que não mudam a aparência de nenhum tema anterior.
Toggle e menu de tema
Criar um tema customizado
Estenda um tema publicado, sobrescreva os tokens desejados e registre via customThemes.
Tema customizado
primary, accent e os raios vêm do tema novo; o resto herda do cm-neutral.
Superfície de vidro (.cm-glass)
Os três tokens de surfaces trabalhando juntos sobre um fundo, ao lado de um painel opaco para comparação.
Painel de vidro
.cm-glassO papel de parede atravessa a superfície, borrado.
Painel opaco
CmCardA mesma caixa sem vidro: o fundo para na borda.
Sem suporte a backdrop-filter, .cm-glass cai para superfície opaca (--color-card): perde-se o efeito, nunca a leitura.
Pré-visualizar um tema em um escopo
Os tokens dos temas embutidos moram em :root[data-theme], então pendurar data-theme numa <div> não troca o tema de um trecho. themeToCSSVars resolve: as variáveis são herdáveis e valem para a subárvore.
Frevo OS
data-theme="frevo"Instalação em disco inteiro
Vidro do tema: rgba(20, 20, 22, 0.82) com desfoque de 24px.
Fora do escopo
O tema da página segue intacto — mesmos botões, com o raio e as cores do tema atual, sem pílula.
Tokens de tracking
As três medidas de letter-spacing aplicadas a um título, a um parágrafo e a um rótulo em caixa alta.
Título grande
--tracking-tightInstalação do sistema
Texto corrido
--tracking-normalO particionamento apaga todo o conteúdo do disco selecionado. Revise o destino antes de continuar: a operação não pode ser desfeita depois que a gravação começa.
Rótulo em caixa alta
--tracking-wideDestino da instalação
Densidade via hook
Densidade atual: default
Snippets
Trechos prontos para colar no projeto.
Layout raiz no Next
import "cosmemilton-ui/styles.css";
import { CmThemeProvider, CmThemeScript } from "cosmemilton-ui/theme";
export default function RootLayout({ children }) {
return (
<html lang="pt-BR" suppressHydrationWarning>
<head>
<CmThemeScript defaultThemeName="cm-neutral" />
</head>
<body>
<CmThemeProvider defaultThemeName="cm-neutral">
{children}
</CmThemeProvider>
</body>
</html>
);
}Tema fixo sem JavaScript
Como os tokens já estão no CSS, qualquer tema embutido pode ser fixado direto no atributo — útil para páginas estáticas ou apps sem troca de tema.
<html data-theme="cm-dark">Sobrescrevendo tokens de um tema via CSS
O provider não escreve mais estilos inline no <html>, então CSS comum fora de @layer sempre vence os blocos da biblioteca.
:root[data-theme="cm-neutral"] {
--color-primary: #7c3aed;
}Temas publicados e helpers de CSS
import {
themes,
extendThemes,
themeToCSSVars,
themeToCSSBlock,
themeSelector,
} from "cosmemilton-ui/theme";
const available = Object.keys(themes);
const registry = extendThemes([customTheme]);
themeSelector(themes["cm-blue"]);
// ':root[data-theme="cm-blue"]'
themeToCSSVars(themes["cm-blue"]);
// { "--color-primary": "...", ... }
themeToCSSBlock(themes["cm-blue"]);
// ':root[data-theme="cm-blue"] { --color-primary: ...; }'Vidro: os três tokens e a classe .cm-glass
Vidro nunca é uma coisa só — é preenchimento translúcido MAIS borda de um fio MAIS desfoque do fundo. A classe compõe os três; sem backdrop-filter no navegador, ela cai para superfície opaca (perde-se o efeito, nunca a leitura).
// A classe já vem no CSS da lib — nada a importar além do styles.css.
<div className="cm-glass" style={{ borderRadius: "var(--radius-xl)", padding: "var(--space-xl)" }}>
Painel sobre o desktop
</div>
/* Equivalente escrito à mão — é exatamente isto que a classe evita repetir:
.meu-painel {
background: var(--surface-glass);
border: 1px solid var(--surface-glass-border);
backdrop-filter: blur(var(--surface-glass-blur));
}
Uma borda só, nunca duas: empilhar um box-shadow de anel por cima devolve a
borda dupla que a escala existe para evitar. */Um tema que declara vidro, tracking e pílula
As três escalas novas da 3.20 são opcionais e parciais, como space e motion: declare só o que você quer opinar e o resto vem do default da lib.
import { themes, type ThemeConfig } from "cosmemilton-ui/theme";
const base = themes["cm-neutral"];
const casaTheme: ThemeConfig = {
...base,
name: "casa",
typography: {
...base.typography,
// Tracking é opt-in por design: nenhum componente aplica sozinho, então
// emitir o token não muda nada até algum CSS seu pedir por ele.
tracking: { tight: "-0.03em", wide: "0.1em" },
},
radii: {
...base.radii,
// Torna a pílula o padrão da casa sem repetir shape="pill" em cada CmButton.
button: "9999px",
},
// Sem este bloco, o vidro é derivado de card/foreground do próprio tema.
surfaces: {
glass: "rgba(18, 20, 24, 0.78)",
glassBorder: "rgba(255, 255, 255, 0.1)",
glassBlur: "20px",
},
};Tema frevo (Frevo OS)
Grafite, vidro e papel: raio 12–16, pílula nos botões, Archivo e o vermelho #c62828 como acento único. Atenção ao nome — ele é registrado como "frevo", sem o prefixo cm- dos demais.
import { CmThemeProvider, CmThemeScript, frevoTheme, themes } from "cosmemilton-ui/theme";
themes["frevo"] === frevoTheme; // true — já vem no registro publicado
// Como qualquer tema embutido, basta o atributo:
// <html data-theme="frevo">
<CmThemeScript defaultThemeName="frevo" />
<CmThemeProvider defaultThemeName="frevo">{children}</CmThemeProvider>Catálogo de design tokens
Variáveis publicadas estaticamente em styles.css (layer cm.tokens): o bloco :root traz o padrão cm-neutral e cada tema embutido tem um bloco :root[data-theme="..."] trocando esses valores.
:root {
/* Cores — cada cor de conteúdo tem um par "-foreground" para contraste */
--color-background: #f8fafc;
--color-foreground: #20242a;
--color-muted: #eef1f4;
--color-muted-foreground: #69717d;
--color-card: #ffffff;
--color-card-foreground: #20242a;
--color-popover: #ffffff;
--color-popover-foreground: #20242a;
--color-primary: #334155;
--color-primary-foreground: #ffffff;
--color-secondary: #d9e0e8;
--color-secondary-foreground: #20242a;
--color-accent: #0f766e;
--color-accent-foreground: #ffffff;
--color-success: #2f855a;
--color-success-foreground: #ffffff;
--color-warning: #b7791f;
--color-warning-foreground: #1f2933;
--color-danger: #c2413a;
--color-danger-foreground: #ffffff;
--color-info: #2563a8;
--color-info-foreground: #ffffff;
--color-border: #d8dee6;
--color-input: #cfd6df;
--color-ring: #475569;
--color-selection: #dbeafe;
--color-selection-foreground: #1e293b;
--color-overlay: rgba(32, 36, 42, 0.48);
/* Tipografia */
--font-family: var(--font-geist-sans, 'Inter', sans-serif);
--font-mono: var(--font-geist-mono, 'JetBrains Mono', monospace);
--font-base: 16px;
--font-scale: 1.18;
/* Tracking (letter-spacing) — nenhum componente aplica sozinho: os tokens
existem para você opinar em títulos e rótulos em caixa alta */
--tracking-tight: -0.02em;
--tracking-normal: 0;
--tracking-wide: 0.08em;
/* Raios de borda */
--radius-xs: 0.125rem;
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--radius-xl: 0.625rem;
--radius-full: 9999px;
/* Raio dos botões na forma padrão. Emitido sempre; vale --radius-md quando o
tema não declara radii.button (exatamente o que os botões já usavam) */
--radius-button: 0.375rem;
/* Sombras (elevação) */
--shadow-xs: 0 1px 2px 0 rgba(15, 23, 42, 0.05);
--shadow-sm: 0 1px 3px 0 rgba(15, 23, 42, 0.08);
--shadow-md: 0 8px 24px -18px rgba(15, 23, 42, 0.28);
--shadow-lg: 0 18px 44px -28px rgba(15, 23, 42, 0.32);
--shadow-xl: 0 28px 70px -38px rgba(15, 23, 42, 0.38);
/* Espaçamento (gaps, paddings, margins) */
--space-none: 0;
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 0.75rem;
--space-lg: 1rem;
--space-xl: 1.5rem;
--space-2xl: 2rem;
--space-3xl: 3rem;
/* Camadas de empilhamento (z-index dos overlays) */
--z-base: 0;
--z-sticky: 20;
--z-docked: 30;
--z-overlay: 300;
--z-modal: 301;
--z-dropdown: 500;
--z-toast: 9999;
--z-tooltip: 10000;
/* Movimento (durações e curvas) */
--motion-duration-fast: .15s;
--motion-duration-base: .2s;
--motion-duration-slow: .3s;
--motion-ease-standard: ease;
--motion-ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
/* Breakpoints */
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;
/* Densidade do modo "default" (também existem --density-compact-* e --density-comfortable-*) */
--density-default-control-height: 2.5rem;
--density-default-control-padding-x: 0.875rem;
--density-default-control-padding-y: 0.5rem;
--density-default-gap: 0.5rem;
--density-default-icon-size: 1.125rem;
/* Superfícies semânticas (derivadas das cores) */
--layer-base: var(--color-background);
--layer-surface: var(--color-card);
--layer-elevated: color-mix(in srgb, var(--color-card) 96%, var(--color-background));
--layer-floating: var(--color-popover);
--layer-overlay: var(--color-overlay);
/* Vidro — a superfície translúcida (painel/menu sobre desktop, foto ou vídeo).
Tema que não declara "surfaces" recebe um vidro derivado das PRÓPRIAS cores,
em vez de um cinza que não pertence a paleta nenhuma */
--surface-glass: color-mix(in srgb, var(--color-card) 82%, transparent);
--surface-glass-border: color-mix(in srgb, var(--color-foreground) 8%, transparent);
--surface-glass-blur: 24px;
}Consumindo os tokens no seu CSS
Use os tokens em vez de valores fixos para herdar tema, dark mode e densidade automaticamente.
.minha-secao {
background: var(--color-card);
color: var(--color-card-foreground);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--space-lg);
box-shadow: var(--shadow-md);
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
}
.minha-secao:focus-visible {
outline: 2px solid var(--color-ring);
outline-offset: 2px;
}Sobrescrevendo tokens em um escopo
Tokens são variáveis CSS herdáveis: redefina qualquer um em um escopo — usando os próprios primitivos da lib — para personalizar sem sair do sistema.
import { CmButton } from "cosmemilton-ui/client";
import { CmStack } from "cosmemilton-ui/server";
// Tudo dentro desta seção herda o primary roxo e o raio maior.
// CmStack as="section" vira um <section> semântico, sem HTML cru.
<CmStack as="section" style={{ "--color-primary": "#7c3aed", "--radius-md": "1rem" } as React.CSSProperties}>
<CmButton tone="primary">Herda os tokens deste escopo</CmButton>
</CmStack>Notas
Temas publicados: cm-neutral, cm-school, cm-dark, cm-orange, cm-red, cm-blue, cm-green, cm-violet, cm-midnight, cm-rose, cm-aurora e frevo.
O tema frevo (3.20) é o único registrado sem o prefixo cm-: o nome dele é frevo, então o atributo é data-theme="frevo". Ele é escuro por padrão e declara as três escalas novas — vidro literal, tracking apertado nos títulos e radii.button em pílula.
Densidades públicas: default, comfortable e compact.
Como os tokens dos temas embutidos já vêm em styles.css, o CmThemeScript injeta apenas alguns bytes por página: ele lê o tema salvo no localStorage e seta data-theme antes do primeiro paint (não serializa mais os tokens de todos os temas).
CmThemeScript aceita a prop nonce, repassada ao <script>/<style> inline — necessário em apps cuja Content-Security-Policy não permite 'unsafe-inline'.
Temas custom (que não existem no CSS estático) são emitidos uma vez como um <style id="cm-theme-custom"> pelo CmThemeScript e/ou pelo CmThemeProvider, com seletor de especificidade maior que os blocos embutidos — um tema custom pode inclusive sobrescrever o nome de um tema publicado.
themeToCSSVars(theme) gera as variáveis a partir do objeto do tema; nomes camelCase viram kebab-case (mutedForeground → --color-muted-foreground). themeToCSSBlock(theme) rende o bloco CSS completo (tokens + color-scheme) e themeSelector(theme) devolve o seletor :root[data-theme="..."].
Famílias de tokens: --color-*, --font-*, --tracking-*, --radius-*, --shadow-*, --space-*, --z-*, --motion-duration-*/--motion-ease-*, --breakpoint-*, --density-<modo>-*, --layer-* e --surface-*.
3.20 — vidro (surfaces): --surface-glass, --surface-glass-border e --surface-glass-blur, mais a classe utilitária .cm-glass, que compõe os três. É uma escala e não uma cor porque nenhuma cor sozinha carrega o desfoque. Tema que não declara surfaces recebe um vidro derivado das próprias cores via color-mix, nas próprias matizes.
3.20 — tracking (typography.tracking): --tracking-tight, --tracking-normal e --tracking-wide. Nenhum componente da lib aplica esses tokens sozinho, de propósito: re-espaçar todo título de todo consumidor seria mudança visual quebrando. Eles ficam disponíveis para o seu CSS opinar.
Cuidado para não confundir: a prop tracking do CmText (tight/normal/wide) é anterior a esses tokens e continua com valores próprios e fixos — ela não lê --tracking-*. Para o espaçamento vir do tema, aplique o token você mesmo (letterSpacing: "var(--tracking-tight)").
3.20 — raio de botão (radii.button): o CmButton em shape default e square passou a ler border-radius: var(--radius-button, var(--radius-md)). O token é emitido SEMPRE, com valor igual a --radius-md quando o tema não declara. Isso é exatamente o que os botões liam antes do token existir, então nenhum tema anterior mudou de aparência — e um tema novo pode tornar a pílula o padrão da casa sem repetir shape="pill" em cada CmButton.
As três escalas novas são opcionais e parciais (Partial<...>), como space, zIndex e motion: declare só o que você quer opinar. Nenhum campo obrigatório foi adicionado ao ThemeConfig e nenhuma assinatura mudou.
Cada cor de conteúdo acompanha um par -foreground para garantir contraste sobre a superfície.
Por serem variáveis CSS herdáveis, os tokens podem ser redefinidos em qualquer escopo (inclusive via style inline) sem recompilar nada.
Para criar um tema próprio, monte um ThemeConfig — o mais simples é dar spread em um tema publicado (themes["cm-neutral"]) e sobrescrever só os tokens desejados, já que colors, typography, radii e shadows são obrigatórios e completos.
Registre o(s) tema(s) com a prop customThemes do CmThemeProvider (aceita ThemeConfig[] ou um ThemeRegistry); extendThemes faz o merge com os publicados e os temas custom aparecem no CmThemeMenu junto dos demais.