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.
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 respeitaprefers-reduced-motionautomaticamente. - 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
--widthque 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.