Pitch: Slynx: por que joguei o CSS fora e fiz meu próprio sistema de estilos
Esse é o segundo post sobre a Slynx — uma linguagem que estou construindo pra criar aplicações desktop que compilam pra múltiplos targets.
Hoje eu estou mais interessado em falar sobre o sistema de estilos dela. Os motivos pelos quais eu dei um pé na bunda do CSS e escrevi um sistema de estilos eu mesmo.
Por que não CSS?
Quando mexi com webgpu, skia, e amigos, a ideia de estilos estáticos como o CSS simplesmente não existe, visto que um conteúdo definido durante runtime precisa ser passado pra GPU. Simplesmente não existe
.estilo {
padding: 12px;
border-radius: 12px;
border-color: #ff0000;
background-color: #ffff00;
}
O conteúdo precisa estar na GPU de alguma forma e reescrever os shaders só porque a cor mudou é inviável.
Então a melhor ideia que tive desde o dia que nasci: 'E se for uma função pura?' e essa é a ideia por trás.
Uma das coisas que levou a essa escolha é que a linguagem NÃO é idealizada pra ser um projeto único da e pra web, mas compilável pra qualquer canto, com a web sendo o primeiro target. Então esse modelo fica até mais fácil pra se utilizar caso eu queira compilar pra swift-ui por exemplo.
Outros problemas do CSS que me deixaram com pelo menos 4 pés atrás são:
- Sem reuso de código, classes e IDs são péssimos pra escalar
- 0 Type safety.
- Estilos mais complexos são todos cheios de gambiarra
- Não compila pra outros targets. Flutter, iOS, desktop — cada um tem seu próprio modelo.
O que é um stylesheet
Um stylesheet é uma função que gera estilos. Ela é parametrizada, tipada, e passa pelo pipeline completo do compilador — parser, HIR, type checker, IR, codegen.
stylesheet Bg(color: int) uses Fg(color | 0x00ff00) {
let c = (color & 0xff) << 8; //pega somente o azul da cor e utiliza ele como verde de 'c'
styles {
backgroundColor: color,
foregroundColor: c,
}
}
stylesheet Fg(color: int) {
styles {
foregroundColor: color
}
}
E você usa assim num componente:
component Pedrinho {
Div {
style: Bg(12),
Text { text: "Pedrinho" }
}
}
Simples. Sem classes, sem seletores, sem cascade acidental.
uses — herança de estilos
Um stylesheet pode herdar de outros via uses. A precedência é da esquerda pra direita, com o próprio stylesheet tendo prioridade máxima:
stylesheet C() uses A(), B() {
styles {
foregroundColor: 0x00ff00
}
}
// precedência: A < B < C
// se A e B definem foregroundColor, C sobrescreve
Isso facilita criar bibliotecas de estilos. Você define SomeStyle(arg1, arg2) uma vez, e quem usa a sua lib faz:
stylesheet MyStyle() uses SomeStyle(red, 4) {}
A sobrescrita é intencional e explícita — sem surpresa de especificidade como no CSS.
Como funciona por baixo
Cada stylesheet gera 3 constructs na IR:
- Uma struct com um campo por propriedade resolvida
- Uma função construtora que avalia as expressões e monta a struct
- Uma função apply que recebe
(componente, struct)e emite@sapplypor propriedade
O @sapply é uma UI Operation da IR — ela carrega um código numérico (da STYLES_TABLE) que diz pro backend o que aplicar. O backend decide como fazer isso. A IR só declara a intenção.
@sapply BACKGROUND_COLOR, #t0, 0xFF0000FF;
@sapply FOREGROUND_COLOR, #t0, 0x00FF00FF;
No componente, um @initcall garante que o estilo é aplicado antes do componente aparecer na UI:
component %C() {
#t0 = specialized Div;
@initcall ApplyBgStyle, #t0, %BgStyle{12}
}
Estado atual — honestidade acima de tudo
O sistema tá funcional, mas cru. Por enquanto só existem 2 propriedades implementadas:
backgroundColorforegroundColor
Ambas representadas como i32 por ora — cor é um placeholder até a stdlib de UI existir.
O que já existe no parser mas ainda não lowers:
- Estados como
hover(0.3s) { ... }eclicked { ... } - Transições com duração e curva de easing
Também só é possível aplicar estilos em Div e Text por enquanto — componentes customizados ainda não suportam style:.
O roadmap inclui mais propriedades (padding, margin, opacity, border, shadow), estados funcionais, animações, e estilos em componentes customizados. Mas isso vem com o tempo.
Por que não Tailwind
Acredito que não tenha nem como, uma ideia que penso é em utilizar o operador '|' para composição de estilos, então algo como
Style1() | Style2() | Style3(), mas isso há de ser averiguado.
O repositório tá em github.com/Slynx-Language/slynx. Feedbacks são bem-vindos — especialmente sobre o design do sistema de estilos, que ainda tem muita coisa em aberto.