Pular para o conteúdo

A primeira escola especializada em tecnologia do Alto Solimões, com corpo técnico formado na área.

Módulo 4: Estado e movimento

Estados: o cartão que responde e o foco que fica

A página que responde a quem a usa

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;
}
PseudoclasseQuando
:hovero cursor está por cima
:focuso elemento está focado
:focus-visiblefocado e o navegador julga útil indicar
:activeestá sendo pressionado
:visitedo 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-visible com anel de realce em todo link, botão e campo;
  • nenhum outline: none na folha inteira - procure;
  • :hover e :focus-within no cartão de projeto;
  • a classe .situacao com a palavra escrita, não só a cor;
  • os estados do formulário, com :invalid:not(:placeholder-shown);
  • a classe .acao completa, 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

  1. Estilize os quatro estados de todos os links do portfólio.
  2. Procure outline: none no seu CSS. Se houver, substitua por :focus-visible.
  3. Navegue as seis páginas com Tab e confirme que dá para ver o foco em tudo.
  4. Ponha :hover e :focus-within nos cartões de projeto. Depois tire o :focus-within e percorra com Tab: sentiu falta?
  5. Faça o botão .acao com os cinco estados, e use-o no link da abertura e no botão do formulário.
  6. Estilize :invalid num campo e compare com e sem :not(:placeholder-shown), recarregando a página nos dois casos.

Referências

Testa o que você leu

6 perguntas sobre esta aula. Errar aqui não custa nada - a explicação vem junto da correção.

Pergunta 1 de 6
O que `:focus-visible` faz que `:focus` não faz?