CSS, caixa e layout
Seletores, cascata, herança, medidas, fluxo, posicionamento, flexbox, grid e desenho responsivo.
No catálogo, .livro > h2 { color: navy; } dá cor aos títulos que são filhos diretos dos cartões. O seletor escolhe os elementos e a declaração muda uma propriedade. Quando duas regras atingem o mesmo título com cores diferentes, a cascata decide qual se aplica. Esta página mostra como prever essa decisão, medir caixas e construir o layout.
Seletores
A folha externa é ligada pelo link no head do HTML. Também podes escrever regras num elemento style ou declarações no atributo style; a folha externa permite reutilizar regras entre páginas. Uma regra contém um seletor e declarações propriedade: valor, separadas por ponto e vírgula.
Uma regra como .livro > h2 { color: navy; } escolhe os h2 filhos diretos de um elemento com classe livro. O espaço escolhe descendentes a qualquer profundidade. A vírgula separa seletores, não acrescenta uma condição.
| Seletor | Elementos selecionados |
|---|---|
article.livro | article com a classe livro |
[name="q"] | Elementos com esse atributo e valor |
.livro p | Parágrafos descendentes de .livro |
.livro > p | Parágrafos filhos diretos |
h2 + p | O p imediatamente a seguir a um h2, com o mesmo pai |
h2 ~ p | Todos os irmãos p posteriores a um h2 |
li:first-child | li que é o primeiro elemento filho |
li:first-of-type | O primeiro li entre os irmãos |
li:nth-child(2n) | li numa posição par entre todos os elementos filhos |
input:checked | Controlos marcados |
a:hover, a:focus-visible | Ligações sob o apontador ou com foco visível |
p::first-letter, p::before | Uma parte ou um pseudo-elemento do p |
Desenha a árvore antes de responder. Uma section contém h2, p e p como filhos diretos. O primeiro p é o segundo filho e o primeiro do seu tipo. Por isso corresponde a p:first-of-type, mas não a p:first-child, que exigiria ser o primeiro filho. O índice de :nth-child começa em 1 e conta elementos, não nós de texto.
[href^="https://"] testa o início de um atributo; [href$=".pdf"] testa o fim; [href*="livro"] procura uma parte. [class~="livro"] procura uma palavra numa lista separada por espaços, enquanto [lang|="pt"] aceita pt ou um valor que comece por pt-. p.ativo seleciona o próprio parágrafo com a classe; p .ativo seleciona um descendente. Uma pseudo-classe, como :checked, testa um elemento; um pseudo-elemento, como ::before, representa uma parte gerada, normalmente com content.
:not(.esgotado) exclui uma condição; :is(.livro, .revista) reúne alternativas; .livro:has(button) escolhe o elemento pela existência de um descendente que corresponde. Em seletores aninhados, & representa o seletor exterior; numa pergunta de seleção, desenha pais e irmãos antes de contar especificidade.
Cascata e herança
Aqui vamos comparar declarações normais do autor, sem camadas:
#catalogo .preco {
color: red;
} /* (1, 1, 0) */
ul#catalogo li span.preco {
color: green;
} /* (1, 1, 3) */
Ambas atingem o mesmo span.preco. Resolve por etapas. Primeiro confirma que as duas declarações estão no mesmo nível de origem, importância e camada. Depois compara o trio (IDs, classes/atributos/pseudo-classes, elementos/pseudo-elementos) da esquerda para a direita: (1, 1, 0) contra (1, 1, 3).
O empate nas duas primeiras posições resolve-se na terceira, por isso a segunda ganha. Daqui sai uma regra prática: dez classes não ultrapassam um identificador, porque a comparação decide na primeira coluna onde houver diferença. * e combinadores não acrescentam especificidade.
:where(...) tem especificidade zero. :is, :not e :has usam a maior especificidade entre os argumentos. Um estilo em linha tem precedência sobre as regras normais da folha do autor; !important altera a importância, não a especificidade. Declarações importantes do utilizador podem ultrapassar as do autor. Não resumas a cascata a “o seletor mais comprido ganha”.
@layer base, componentes;
@layer base {
#catalogo p {
color: red;
}
}
@layer componentes {
p {
color: blue;
}
}
Para declarações normais, a camada posterior ganha antes de contar especificidade, por isso o parágrafo fica azul. Regras normais sem camada ficam acima das normais com camada. A prioridade das camadas inverte-se para declarações importantes.
Herança é outro mecanismo. color e propriedades de texto costumam herdar; margem, borda e padding não. Uma declaração diretamente no filho vence um valor herdado, mesmo que o seletor no pai tenha muitos IDs. inherit pede o valor calculado do pai; initial pede o valor inicial da propriedade; unset herda se a propriedade é herdada e usa o inicial nos restantes casos.
Medidas e modelo de caixa
Uma caixa tem conteúdo, padding, border e margin. Com box-sizing: content-box, a largura declarada mede o conteúdo. Se temos width: 240px, padding de 12px em cada lado e border de 2px:
Com margem de 10px em cada lado, a ocupação horizontal é 288px. Com border-box, os mesmos 240px já incluem padding e borda, e o conteúdo tem 212px. A margem continua fora dos 240px.
No diagrama, confirma onde ficam os extremos de width: 240px: em content-box ficam nas extremidades do conteúdo; em border-box, nas extremidades exteriores da borda. As margens ficam fora de width nos dois casos.
As abreviaturas seguem os lados da caixa. padding: 4px aplica 4px aos quatro lados. padding: 4px 8px dá 4px em cima e em baixo e 8px nos lados; padding: 4px 8px 12px dá 4px em cima, 8px nos lados e 12px em baixo. Com quatro valores, a ordem é cima, direita, baixo, esquerda. max-width limita uma largura flexível; overflow: auto permite deslocar conteúdo que não cabe, sem o apagar.
Margens verticais de blocos em fluxo normal podem colapsar. Duas margens positivas de 16px e 24px dão 24px entre os blocos, não 40px. Essa regra não se aplica da mesma forma aos itens flex e grid. Padding não colapsa.
rem usa o tamanho de letra da raiz. em em font-size usa o tamanho do pai; nas outras propriedades, usa o tamanho de letra do próprio elemento. Segue o exemplo com raiz a 16px e pai a 20px. Um filho com font-size: 2em tem 40px, porque dobra os 20px do pai.
Nesse filho, padding: 1em mede 40px, pois usa a letra do próprio elemento. Já padding: 1rem mede 16px, pois volta à raiz. Percentagens dependem da propriedade e da caixa de referência. vw e vh dependem do viewport.
Fluxo e posicionamento
Um bloco participa normalmente numa sequência vertical. Um elemento inline participa nas linhas de texto, com regras de largura e altura diferentes. inline-block participa na linha e aceita dimensões de caixa. display: none retira a caixa do layout; visibility: hidden conserva o espaço.
position | Efeito |
|---|---|
static | Fluxo normal; top e left não deslocam a caixa |
relative | Desloca visualmente e conserva o lugar original |
absolute | Sai do fluxo; posiciona-se pela caixa de referência, frequentemente o antepassado posicionado mais próximo |
fixed | Sai do fluxo e, no caso habitual, usa o viewport |
sticky | Permanece no fluxo e fica preso ao limite definido durante o scroll |
Se o botão Reservar tiver position: relative; top: 8px, aparece 8px abaixo do seu lugar, mas os elementos seguintes conservam as posições. O espaço original continua reservado.
Se o mesmo botão tiver position: absolute; bottom: 0 num cartão com position: relative, fica no fundo desse cartão e deixa de reservar espaço no fluxo. O texto pode então ficar por baixo do botão. Um antepassado transformado também pode estabelecer essa referência, incluindo para fixed. z-index compara níveis dentro de contextos de empilhamento; um filho não salta arbitrariamente acima de um contexto exterior.
Para texto à volta de uma imagem, usa float: left na imagem e clear: both no bloco que deve recomeçar abaixo; para grelhas e alinhamento, usa flexbox ou grid.
Flexbox e grid
Flexbox dispõe itens principalmente num eixo. flex-direction define o eixo principal; justify-content alinha nesse eixo e align-items no transversal. flex-wrap permite novas linhas. flex: 1 1 200px dá crescimento 1, redução 1 e base de 200px, não uma largura fixa garantida.
Grid define linhas e colunas. 1fr reparte espaço disponível, depois de considerar outras medidas e limites. minmax(0, 1fr) permite uma coluna reduzir sem o mínimo automático do conteúdo a forçar o layout. gap cria intervalos entre itens. grid-column: 1 / 3 vai da linha 1 à linha 3 e ocupa duas colunas. grid-template-areas dá nomes a zonas retangulares.
CSS e JavaScript
A preparar a pré-visualização…
Consola
Sem mensagens.
O catálogo começa com uma coluna. A partir de 600px, a media query cria três. Dentro de cada cartão, flexbox organiza título, texto e botão em coluna; margin-top: auto absorve o espaço livre antes do botão, por isso os botões alinham quando os cartões têm a mesma altura.
Cor, variáveis e movimento
Cor pode usar nomes, hexadecimal, rgb ou hsl. O fundo pertence à caixa definida por background; background-size: cover pode cortar a imagem para cobrir a área. A fonte deve ter uma família de recurso, e line-height sem unidade acompanha o tamanho da letra.
:root {
--cor-livro: #345;
}
.livro {
border-color: var(--cor-livro);
}
button {
transition: transform 150ms ease;
}
button:hover {
transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
button {
transition: none;
}
}
Uma custom property participa na cascata e costuma herdar. transform muda o desenho da caixa sem reservar um novo lugar no fluxo. transition interpola uma alteração de propriedade; não inicia por si uma sequência repetida.
Testa o layout com textos longos, zoom e largura pequena; uma media query responde ao ambiente, mas não corrige uma largura mínima que já não cabe.
Repartir o espaço com fr
Uma grelha tem 720px de largura de conteúdo, sem padding nem borda, e três colunas: 120px, uma fração e duas frações. Os dois intervalos entre colunas medem 30px cada:
.grelha {
display: grid;
width: 720px;
grid-template-columns: 120px minmax(0, 1fr) minmax(0, 2fr);
gap: 30px;
}
Primeiro retiramos o que não é repartido: a coluna fixa e os dois gaps. Restam 720 - 120 - 2×30 = 540px. As frações somam 3, por isso uma unidade vale 540 / 3 = 180px.
| Parte | Largura |
|---|---|
| Coluna fixa | 120px |
1fr | 180px |
2fr | 360px |
| Dois gaps | 60px |
| Total | 720px |
Não dividas a largura inteira por três: fr reparte o espaço que sobra. Com conteúdo de 480px e as restantes medidas iguais, sobram 300px e as colunas fracionárias passam a 100px e 200px.
minmax(0, ...) fixa em zero o mínimo dos dois tracks flexíveis neste cálculo. Com o mínimo automático de um simples 1fr, um conteúdo longo pode impor uma largura maior e mudar a distribuição; os itens também precisam de tratamento para não transbordarem. O exemplo de largura fixa serve para calcular, não para substituir o layout responsivo do catálogo.
Um item com grid-column: 2 / 4 atravessa as colunas 2 e 3 e o gap entre elas. No caso de 720px, ocupa 180 + 30 + 360 = 570px. As linhas delimitam o intervalo; o espaço entre os tracks faz parte do item que os atravessa.
Exercícios
Selecionar e decidir a cor
Prevê a cor de B. Considera regras normais do autor sem camadas e sem outras declarações.
<section id="zona">
<ul class="lista">
<li>A</li>
<li class="ativo">B</li>
</ul>
</section>#zona {
color: blue;
}
.lista li {
color: green;
}
section .lista > li.ativo {
color: red;
}
.lista li:first-child + li {
color: inherit;
}Pista
A regra no section fornece herança; as restantes atingem diretamente B.
Mais uma pista
As duas últimas têm (0,2,2). A última declara inherit.
Solução
B fica azul. Segue as etapas. Primeiro separa herança de declaração direta: #zona declara azul diretamente na secção; o ul herda esse azul. Depois compara as regras que atingem B diretamente. A segunda tem (0,1,1). A terceira e a quarta têm (0,2,2), por isso a quarta ganha pela posição e declara inherit. B herda então o azul do ul, que por sua vez herdou de #zona. A especificidade do avô não compete diretamente com as declarações do neto.
Confere a tua resposta:
- Contei as duas especificidades empatadas.
- Expliquei inherit a partir do ul azul.
Erros frequentes
Não comparar a especificidade do pai com declarações diretas do filho.
Medir uma caixa
Uma caixa tem width 180px, padding 8px de cada lado, border 3px de cada lado e margin 5px de cada lado. Calcula a largura até à borda e a ocupação com margens em content-box, e repete em border-box.
Pista
Padding e border somam-se ao conteúdo em content-box.
Mais uma pista
Em border-box, width já inclui padding e border, mas não margin.
Solução
Em content-box, soma ao conteúdo: 180 + 2x8 + 2x3 = 202px até à borda, e 202 + 2x5 = 212px com margens. Em border-box, os 180px já incluem padding e borda: 180px até à borda e 190px com margens. O conteúdo mede 180 - 16 - 6 = 158px. O colapso de margens verticais não altera estas contas horizontais.
Confere a tua resposta:
- Obtive 202/212 em content-box.
- Obtive 180/190 e conteúdo 158 em border-box.
Erros frequentes
Não somar padding e border de novo ao width de border-box.
Distinguir filho e irmão
Num ul há quatro li, com classes primeiro, segundo, terceiro e quarto. O segundo tem um span filho. Quais li escolhem li.primeiro + li, li.primeiro ~ li e li > span? Justifica.
Pista
Os combinadores + e ~ ligam irmãos posteriores.
Solução
O primeiro seletor escolhe só o segundo li. O segundo escolhe segundo, terceiro e quarto. O terceiro não escolhe nenhum li: escolhe o span filho direto do segundo li. Combinadores descrevem relações, não mudam o tipo da seleção final.
Confere a tua resposta:
- Distingui um irmão de todos os posteriores.
- Identifiquei span como alvo do terceiro seletor.
Erros frequentes
Não dizer que li > span escolhe o pai li.
Construir duas colunas
Escreve CSS para .catalogo com uma coluna abaixo de 700px e duas colunas iguais a partir de 700px, com gap: 16px. Cada .livro é um filho direto do catálogo e contém um título, um parágrafo e um botão, por essa ordem. Os textos têm comprimentos diferentes. Alinha os botões no fundo dos cartões da mesma linha, sem fixar a altura do texto.
Pista
Usa grid no catálogo e flex em coluna no cartão.
Mais uma pista
A media query pode mudar grid-template-columns; a margem automática absorve espaço antes do botão.
Solução
Uma solução é:
.catalogo {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
.livro {
display: flex;
flex-direction: column;
}
.livro button {
margin-top: auto;
}
@media (min-width: 700px) {
.catalogo {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}A grelha estica os cartões na linha por defeito. A margem automática aproveita o espaço vertical livre antes do botão.
Confere a tua resposta:
- Defini a largura pequena e a condição >=700px.
- Usei espaço automático antes do botão.
Erros frequentes
Não mudar apenas width dos cartões sem definir o layout, nem trocar o eixo principal pelo transversal.
Descontar os espaços antes de repartir
Uma grelha tem largura de conteúdo 620px, sem padding nem borda, três colunas 80px minmax(0,1fr) minmax(0,3fr) e gap de 20px. Não há outras restrições de largura impostas pelos itens. Calcula os três tracks e a largura de um item com grid-column: 2 / 4. Repete os tracks quando a largura de conteúdo passa a 320px. Explica por que dividir 620 por quatro não dá a unidade fr.
Solução
Com três colunas, há dois gaps. Retiramos 80 + 2×20 = 120px antes de repartir. Sobram 500px e as frações somam 4: cada fr vale 125px. Os tracks são 80px, 125px e 375px.
O item entre as linhas 2 e 4 atravessa os dois tracks flexíveis e o gap interior: 125 + 20 + 375 = 520px.
Com conteúdo de 320px, sobram 320 - 120 = 200px, dando 50px por fr. Os tracks são 80px, 50px e 150px. Dividir a largura inteira incluiria novamente a coluna fixa e os gaps na distribuição. minmax(0,...) retira o mínimo automático que poderia fazer o conteúdo alterar estas contas.
Confere a tua resposta:
- Obtive colunas 80, 125 e 375 com conteúdo 620.
- Obtive 520 para o item entre as linhas 2 e 4.
- Recalculei 80, 50 e 150 com conteúdo 320.