Estados: o cartão que responde e o foco que fica
Os estados de interação
a {
color: var(--realce);
}
a:hover {
text-decoration: underline;
}
a:focus-visible {
outline: 2px solid var(--realce);
outline-offset: 2px;
border-radius: 4px;
}
a:active {
opacity: 0.7;
}
| Pseudoclasse | Quando |
|---|---|
:hover | o cursor está por cima |
:focus | o elemento está focado |
:focus-visible | focado e o navegador julga útil indicar |
:active | está sendo pressionado |
:visited | o link já foi visitado |
O anel de foco, que não se apaga
/* NUNCA faça isto */
*:focus {
outline: none;
}
Este é o erro de acessibilidade mais comum do CSS, e ele costuma entrar por motivo estético: o contorno azul do navegador não combina com o design.
O que ele custa: quem navega por teclado aperta Tab e não tem como saber onde está. A página vira um campo minado.
A correção não é apagar; é substituir:
:focus-visible {
outline: 2px solid var(--realce);
outline-offset: 2px;
}
:focus-visible é o meio-termo que resolve a queixa original: ele aparece na
navegação por teclado e não aparece a cada clique de mouse num botão.
:hover não existe no celular
Numa tela de toque não há cursor pairando. O que estiver só no :hover é
invisível para quem usa o dedo - que, no seu portfólio, é a maioria.
/* Ruim: no celular, ninguém descobre que o projeto tem link */
.cartao footer {
opacity: 0;
}
.cartao:hover footer {
opacity: 1;
}
/* Bom: sempre visível, e o hover só reforça */
.cartao footer a {
color: var(--realce);
}
.cartao:hover footer a {
text-decoration: underline;
}
Regra: o hover enfeita, nunca revela.
O cartão de projeto que responde
O efeito mais comum de portfólio - o cartão que se levanta um pouco quando o cursor passa - são quatro linhas:
.cartao {
border: 1px solid var(--borda);
background: var(--superficie);
}
.cartao:hover {
border-color: var(--borda-forte);
background: var(--superficie-alta);
}
/* O cartão reage quando o link de dentro recebe foco pelo teclado */
.cartao:focus-within {
border-color: var(--realce);
}
:focus-within é o par de :hover para o teclado: ele acerta o pai quando
qualquer coisa dentro dele está focada. Sem ele, quem navega por Tab vê o anel no
link e o cartão em volta continua morto - e é justamente a pessoa que mais
precisa da pista visual.
O selo de estado
O portfólio tem estados que não são de interação: "no ar", "arquivado", "em andamento". Eles são conteúdo, e a cor é só reforço:
.situacao {
font-family: var(--fonte-mono);
font-size: 0.6875rem;
text-transform: uppercase;
letter-spacing: 0.1em;
padding: var(--espaco-1) var(--espaco-2);
border: 1px solid var(--borda);
border-radius: 999px;
color: var(--suave);
}
.situacao--no-ar {
color: var(--realce);
border-color: color-mix(in srgb, var(--realce) 40%, transparent);
}
Repare que a palavra "no ar" está escrita dentro do selo. Se a diferença fosse só a cor da borda, quem não distingue verde de cinza não receberia a informação - e é a mesma regra que a aula 13 desenvolve.
Estados do formulário de contato
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 2px solid var(--realce);
outline-offset: 1px;
}
input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Só marca de vermelho depois que a pessoa digitou algo */
input:invalid:not(:placeholder-shown),
textarea:invalid:not(:placeholder-shown) {
border-color: #f0736a;
}
O último merece atenção. input:invalid sozinho pinta de vermelho desde o
primeiro instante todo campo obrigatório vazio; antes de a pessoa ter a
chance de preencher. :not(:placeholder-shown) adia isso para depois da
digitação.
Um botão completo
.acao {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--espaco-2);
min-height: 44px;
padding-inline: var(--espaco-4);
border: none;
border-radius: 999px;
background: var(--realce);
color: var(--realce-tinta);
font: inherit;
font-weight: 600;
text-decoration: none;
cursor: pointer;
}
.acao:hover {
filter: brightness(1.1);
}
.acao:focus-visible {
outline: 2px solid var(--realce);
outline-offset: 3px;
}
.acao:active {
transform: translateY(1px);
}
.acao:disabled {
opacity: 0.5;
cursor: not-allowed;
}
filter: brightness(1.1) clareia sem exigir uma segunda cor declarada: e
funciona para qualquer cor de fundo que o botão venha a ter - inclusive depois de
você trocar --realce.
E a mesma classe serve ao <a> "Vamos conversar" e ao <button> "Enviar
mensagem". Eles ficam idênticos na tela e continuam sendo coisas diferentes por
baixo, que é o que a aula 11 do curso de HTML pediu.
No portfólio
A página passa a responder:
:focus-visiblecom anel de realce em todo link, botão e campo;- nenhum
outline: nonena folha inteira - procure; :hovere:focus-withinno cartão de projeto;- a classe
.situacaocom a palavra escrita, não só a cor; - os estados do formulário, com
:invalid:not(:placeholder-shown); - a classe
.acaocompleta, com os cinco estados.
Depois percorra as seis páginas com Tab, do primeiro ao último elemento. Em nenhum momento você pode perder de vista onde está - e, ao chegar num cartão, ele deve reagir junto com o link.
Exercícios
- Estilize os quatro estados de todos os links do portfólio.
- Procure
outline: noneno seu CSS. Se houver, substitua por:focus-visible. - Navegue as seis páginas com Tab e confirme que dá para ver o foco em tudo.
- Ponha
:hovere:focus-withinnos cartões de projeto. Depois tire o:focus-withine percorra com Tab: sentiu falta? - Faça o botão
.acaocom os cinco estados, e use-o no link da abertura e no botão do formulário. - Estilize
:invalidnum campo e compare com e sem:not(:placeholder-shown), recarregando a página nos dois casos.
Referências
- Pseudoclasses - MDN - a lista completa
:focus-visible- MDN - quando o navegador decide mostrar- Never remove focus outlines - a11yproject - por que, com exemplos
Testa o que você leu
6 perguntas sobre esta aula. Errar aqui não custa nada - a explicação vem junto da correção.