2.5 — Estilização: CSS com Escopo vs CSS-in-JS
CSS sem conflitos, sem bibliotecas, sem runtime.
Objetivos da Aula
- Entender o escopo automático de CSS no Svelte
- Comparar com soluções CSS-in-JS do React
- Ver estilos dinâmicos, globais e variáveis CSS
O Problema: CSS Global
CSS tradicional é global por padrão:
/* styles.css */
.button {
background: blue;
}
/* Outro arquivo qualquer */
.button {
background: red; /* CONFLITO! Quem ganha? */
} Isso causa:
- Conflitos de nomes de classes
- Efeitos colaterais inesperados
- CSS que não pode ser removido com segurança
- Especificidade wars (
!importanteverywhere)
Soluções no React
CSS Modules
// Button.module.css
.button {
background: blue;
padding: 10px;
}
.primary {
background: purple;
}
// Button.jsx
import styles from './Button.module.css'
function Button({ primary, children }) {
return (
<button className={`${styles.button} ${primary ? styles.primary : ''}`}>
{children}
</button>
)
}
// Gera: <button class="Button_button_x7y2z Button_primary_a1b2c"> styled-components
import styled from 'styled-components'
const StyledButton = styled.button`
background: ${props => props.primary ? 'purple' : 'blue'};
padding: 10px;
color: white;
border: none;
&:hover {
opacity: 0.8;
}
${props => props.large && `
padding: 20px;
font-size: 1.2rem;
`}
`
function App() {
return <StyledButton primary large>Clique</StyledButton>
}
// Gera: <button class="sc-aBcDe xyz123"> Emotion
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
const buttonStyle = css`
background: blue;
padding: 10px;
`
function Button() {
return <button css={buttonStyle}>Clique</button>
}
// Ou inline
function Button() {
return (
<button
css={css`
background: blue;
padding: 10px;
`}
>
Clique
</button>
)
} Tailwind CSS
function Button({ primary }) {
return (
<button
className={`px-4 py-2 rounded ${
primary
? 'bg-purple-600 text-white'
: 'bg-blue-600 text-white'
}`}
>
Clique
</button>
)
} Svelte: Escopo Automático
No Svelte, CSS é automaticamente escopado ao componente:
<!-- Button.svelte -->
<button class="button primary">
Clique
</button>
<style>
/* Este CSS só afeta ESTE componente! */
.button {
background: blue;
padding: 10px;
}
.primary {
background: purple;
}
/* Seletores de elemento também são escopados */
button {
border: none;
cursor: pointer;
}
</style> O que o compilador gera:
<button class="button primary svelte-xyz123">Clique</button>
<style>
.button.svelte-xyz123 { background: blue; padding: 10px; }
.primary.svelte-xyz123 { background: purple; }
button.svelte-xyz123 { border: none; cursor: pointer; }
</style> Vantagens:
- ✅ Zero configuração
- ✅ Zero runtime (CSS puro no output)
- ✅ Sem conflitos de nomes
- ✅ Código morto é removível
- ✅ CSS fica junto do componente
- ✅ Warnings se classes não forem usadas
Estilos Dinâmicos
React (styled-components)
const Box = styled.div`
width: ${props => props.size}px;
background: ${props => props.color};
transform: rotate(${props => props.rotation}deg);
`
<Box size={100} color="red" rotation={45} /> Svelte
<script>
export let size = 100
export let color = 'red'
export let rotation = 0
</script>
<div
class="box"
style:width="{size}px"
style:background={color}
style:transform="rotate({rotation}deg)"
>
</div>
<style>
.box {
/* Estilos estáticos aqui */
border-radius: 8px;
}
</style> Ou com CSS Variables
<script>
export let size = 100
export let color = 'red'
export let rotation = 0
</script>
<div
class="box"
style="--size: {size}px; --color: {color}; --rotation: {rotation}deg"
>
</div>
<style>
.box {
width: var(--size);
background: var(--color);
transform: rotate(var(--rotation));
border-radius: 8px;
}
</style> Classes Condicionais
React
// Manual
<div className={`card ${isActive ? 'active' : ''} ${isHighlighted ? 'highlighted' : ''}`}>
// Com clsx/classnames
import clsx from 'clsx'
<div className={clsx('card', { active: isActive, highlighted: isHighlighted })}>
// styled-components
const Card = styled.div`
/* base */
${props => props.active && css`/* active styles */`}
${props => props.highlighted && css`/* highlighted styles */`}
` Svelte
<script>
export let isActive = false
export let isHighlighted = false
</script>
<!-- Diretiva class: -->
<div
class="card"
class:active={isActive}
class:highlighted={isHighlighted}
>
</div>
<!-- Shorthand quando nome da classe = nome da variável -->
<div class="card" class:active class:highlighted>
</div>
<style>
.card {
padding: 1rem;
border: 1px solid #ccc;
}
.active {
border-color: blue;
}
.highlighted {
background: yellow;
}
</style> Estilos Globais
Svelte: :global()
<style>
/* Escopado normalmente */
.container {
padding: 1rem;
}
/* Global: afeta elementos filhos de qualquer componente */
.container :global(h1) {
color: red;
}
/* Totalmente global */
:global(body) {
margin: 0;
font-family: system-ui;
}
/* Classes globais */
:global(.error) {
color: red;
}
</style> Arquivo Global
<!-- +layout.svelte ou App.svelte -->
<slot />
<style>
/* Estilos globais da app */
:global(*) {
box-sizing: border-box;
}
:global(body) {
margin: 0;
font-family: 'Inter', sans-serif;
}
:global(a) {
color: inherit;
text-decoration: none;
}
</style> Ou arquivo CSS separado:
// main.js
import './global.css'
import App from './App.svelte' CSS Variables como Props
Passando variáveis CSS para componentes
<!-- Box.svelte -->
<div class="box">
<slot />
</div>
<style>
.box {
background: var(--bg, white);
color: var(--color, black);
padding: var(--padding, 1rem);
}
</style>
<!-- Uso -->
<Box --bg="navy" --color="white" --padding="2rem">
Conteúdo
</Box> Isso compila para:
<div class="box svelte-xyz" style="--bg: navy; --color: white; --padding: 2rem;">
Conteúdo
</div> Tema Dinâmico
<!-- ThemeProvider.svelte -->
<script>
export let theme = {
primary: '#646cff',
secondary: '#535bf2',
background: '#242424',
text: '#ffffff'
}
</script>
<div
class="theme"
style:--primary={theme.primary}
style:--secondary={theme.secondary}
style:--bg={theme.background}
style:--text={theme.text}
>
<slot />
</div>
<style>
.theme {
background: var(--bg);
color: var(--text);
}
</style>
<!-- Qualquer componente filho -->
<style>
button {
background: var(--primary);
color: var(--text);
}
button:hover {
background: var(--secondary);
}
</style> Pré-processadores
Com vitePreprocess
// svelte.config.js
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
preprocess: vitePreprocess()
} SCSS
<style lang="scss">
$primary: #646cff;
$radius: 8px;
.card {
border-radius: $radius;
&:hover {
background: lighten($primary, 40%);
}
.title {
color: $primary;
}
}
</style> PostCSS / Tailwind
<!-- Com Tailwind configurado -->
<div class="flex items-center gap-4 p-4 bg-gray-100 rounded-lg">
<img class="w-12 h-12 rounded-full" src={avatar} alt="" />
<div>
<h3 class="font-bold">{name}</h3>
<p class="text-gray-600">{email}</p>
</div>
</div>
<!-- Também funciona em <style> -->
<style>
.custom {
@apply flex items-center gap-4 p-4;
}
</style> Comparação Final
| Aspecto | React CSS-in-JS | Svelte |
|---|---|---|
| Setup | Instalar + configurar | Zero config |
| Runtime | ~10-20KB (styled-components) | 0KB |
| Escopo | Depende da solução | Automático |
| Sintaxe | Template literals / objetos | CSS nativo |
| Dinâmico | Props no JS | CSS vars / style: |
| Classe condicional | clsx/classnames | class: directive |
| Global | ThemeProvider | :global() |
| Tooling | Plugin específico | Native |
| DevTools | Extension específica | CSS padrão |
✅ Desafio da Aula
Objetivo
Criar um componente Card com tema customizável via CSS variables.
Requisitos
Componente
Card.sveltecom:- Props:
variant(‘default’ | ‘primary’ | ‘danger’) - Slot para conteúdo
- Slot nomeado “header”
- Slot nomeado “footer”
- Props:
Cada variante tem cores diferentes via CSS vars
Deve aceitar override de cores:
<Card --bg="pink">
Spec de Verificação
- Card default tem background neutro
- Card primary tem background azul/roxo
- Card danger tem background vermelho
- Passar
--bg="pink"sobrescreve a cor
Solução
🔍 Clique para ver a solução
<!-- Card.svelte -->
<script>
export let variant = 'default'
</script>
<div class="card" class:primary={variant === 'primary'} class:danger={variant === 'danger'}>
{#if $$slots.header}
<div class="header">
<slot name="header" />
</div>
{/if}
<div class="body">
<slot />
</div>
{#if $$slots.footer}
<div class="footer">
<slot name="footer" />
</div>
{/if}
</div>
<style>
.card {
--_bg: var(--bg, #f5f5f5);
--_border: var(--border, #e0e0e0);
--_text: var(--text, #333);
background: var(--_bg);
border: 1px solid var(--_border);
color: var(--_text);
border-radius: 8px;
overflow: hidden;
}
.card.primary {
--_bg: var(--bg, #646cff);
--_border: var(--border, #535bf2);
--_text: var(--text, white);
}
.card.danger {
--_bg: var(--bg, #ef4444);
--_border: var(--border, #dc2626);
--_text: var(--text, white);
}
.header {
padding: 1rem;
border-bottom: 1px solid var(--_border);
font-weight: 600;
}
.body {
padding: 1rem;
}
.footer {
padding: 1rem;
border-top: 1px solid var(--_border);
background: rgba(0, 0, 0, 0.05);
}
</style> <!-- Uso -->
<Card>
<span slot="header">Título</span>
<p>Conteúdo do card</p>
<div slot="footer">
<button>Ação</button>
</div>
</Card>
<Card variant="primary">
Conteúdo primário
</Card>
<Card variant="danger" --bg="pink">
Card perigo com override de cor
</Card>Próxima aula: 2.6 — Performance e Tamanho do Bundle