marceloacevedo
vamos conversar
Lançamento

Canon CSS: um jeito certo de fazer cada coisa

Por que LLMs escrevem UI inconsistente, o que aconteceu quando dei a um deles um framework sem alternativas, e o que aconteceu quando deixei agentes refatorarem este site inteiro com ele.

MAMarcelo Acevedo · agosto de 2026

Peça a um modelo para centralizar uma div com um framework de utilities e você recebe uma resposta diferente a cada vez. Não uma resposta errada: uma diferente. Há centenas de combinações válidas, e um modelo de linguagem escolhe entre elas por distribuição estatística, não por raciocínio. Peça cinco vezes, publique cinco interfaces levemente diferentes.

O problema não é técnico. É epistemológico: nossos frameworks têm graus de liberdade demais. Então construí um que quase não tem nenhum, e este blog está escrito com ele.

Restrição semântica

Canon é um framework de CSS puro com um vocabulário fechado. Seis valores de espaçamento. Sete tamanhos de texto. Sete layouts, doze componentes, uma forma canônica por padrão. A API são atributos data que declaram o que algo é, nunca como fica:

<div data-layout="grid" data-cols="3" data-gap="lg">
  <article data-component="card">
    <div data-slot="header">Title</div>
    <div data-slot="body">Content</div>
  </article>
</div>

Sem build step, zero JavaScript, um único arquivo de 4kb com gzip. Se um valor não tem token, ele não existe. E a documentação é distribuída como um system prompt de ~750 tokens que você injeta em qualquer modelo, porque documentação que um modelo lê é documentação que é seguida.

Funciona mesmo?

A afirmação é testável, então eu a testei. Ao longo de três iterações de treinamento, LLMs com contexto limpo, recebendo apenas o prompt, geraram 20 páginas: dashboards, landings, um blog, um catálogo de e-commerce e um produto completo de 7 páginas escrito por 7 agentes que nunca viram o output uns dos outros.

  • Zero violações de regras em todas as gerações, verificadas mecanicamente pelo canon-lint.
  • A iteração 1 expôs 5 lacunas de vocabulário. A iteração 2, duas cosméticas. A iteração 3, nenhuma.
  • Os 7 agentes às cegas produziram um produto coerente: a mesma identidade em cada página, dark mode exatamente onde deveria estar.

As falhas interessantes nunca foram quebra de regras. Foram ambiguidade: números de métricas que saíam minúsculos em uma execução e enormes em outra porque não havia uma forma canônica de escrever "um número grande". A correção nunca foi um prompt melhor. Foi fechar a lacuna no vocabulário, para que o output feio ficasse difícil de produzir.

O teste de verdade: este site

Benchmarks são uma coisa. Apostar a própria marca é outra, então foi o que eu fiz: seis agentes LLM, trabalhando em paralelo, portaram cada página e cada componente deste site de Tailwind para Canon. Os três idiomas, os formulários, o SEO, com as ilustrações intactas.

A marca sobreviveu dentro de um theme file de cerca de 60 linhas: overrides de tokens para a tinta (#0a0a0a), o acento teal, a fonte display GeneralSans, o corpo de 17px, uma escala display com clamp() e uma medida de leitura mais larga, mais um bloco @layer canon.theme de três regras para botões pill e o tracking do display. Resultado líquido: 770 linhas a menos e zero Tailwind no código.

O que quebrou foi a parte interessante, porque tudo o que quebrou virou vocabulário. O port expôs lacunas, e cada uma delas hoje faz parte do Canon:

  • Animações não tinham expressão, então agora existe data-motion={rise|float|pulse|lift}, que respeita prefers-reduced-motion automaticamente.
  • O menu burger mobile virou um padrão <details> com zero JS na topbar.
  • Os rótulos de marca em monospace viraram data-mono.
  • A visibilidade responsiva virou data-hide.
  • As larguras de leitura hardcoded viraram tokens --width que um theme pode sobrescrever.

O ciclo foi o mesmo toda vez: o port bate em um muro, o muro vira uma proposta de vocabulário, o framework, o prompt, a skill e o linter se atualizam juntos, e a página é regenerada. Essa é a doutrina inteira em um único movimento:

Não basta que o markup válido seja possível. O markup feio tem que ser difícil.

O que o Canon não é

Não é um substituto do Tailwind para humanos que querem controle no nível do pixel: quando cada pixel de um design com marca importa, utilities continuam sendo a ferramenta certa. Mas o contra-argumento mais forte a "um vocabulário fechado tem que parecer genérico" é o site que você está lendo. Isto era um portfólio em Tailwind feito à mão, e agentes LLM refatoraram tudo para Canon mantendo a marca. Canon é para o caso em que um modelo escreve a maior parte da UI e você quer que cada página produzida pareça um único produto: ferramentas internas, dashboards, MVPs, apps geradas por agentes. E, pelo visto, um site pessoal com identidade forte.

Experimente

<link rel="stylesheet"
  href="https://cdn.jsdelivr.net/gh/marcelodevelop/canonframework@main/dist/canon.css">

Pegue o system prompt do repo, cole no seu modelo, peça uma página e valide o resultado com npx canon-lint. O ciclo inteiro leva um minuto: github.com/marcelodevelop/canonframework.