Recurso
Base

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

Tema atual: neutral

Criar um tema customizado

Estenda um tema publicado, sobrescreva os tokens desejados e registre via customThemes.

sunset

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-glass

O papel de parede atravessa a superfície, borrado.

Painel opaco

CmCard

A 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-tight

Instalação do sistema

Texto corrido

--tracking-normal

O 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-wide

Destino da instalação

Densidade via hook

Densidade atual: default

Snippets

Trechos prontos para colar no projeto.

Layout raiz no Next

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

css
:root[data-theme="cm-neutral"] {
  --color-primary: #7c3aed;
}

Temas publicados e helpers de CSS

tsx
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).

tsx
// 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.

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

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

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

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

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