Comparar e comparar

O atributo lang pode ter apenas um idioma associado a ele. Isso significa que o atributo <html> só pode ter um idioma, mesmo que haja vários na página. Defina lang como o idioma principal da página.

O que não fazer
<html lang="ar,en,fr,pt">...</html>
Não há suporte para vários idiomas.
O que fazer
<html lang="ar">...</html>
Defina apenas o idioma principal da página. Nesse caso, o idioma é o árabe.

Assim como os botões, os links têm o nome acessível principalmente pelo conteúdo de texto. Um bom truque ao criar um link é inserir o texto mais significativo no próprio link, em vez de preencher palavras como "aqui" ou "leia mais".

Não é descritivo o suficiente
Check out our guide to web performance <a href="/guide">here</a>.
Conteúdo útil!
Check out <a href="/guide">our guide to web performance</a>.

Verificar se uma animação aciona o layout

Uma animação que move um elemento usando algo diferente de transform provavelmente será lenta. No exemplo abaixo, alcancei o mesmo resultado visual com a animação de top e left e o uso de transform.

O que não fazer
.box {
  position: absolute;
  top: 10px;
  left: 10px;
  animation: move 3s ease infinite;
}

@keyframes move {
  50% {
     top: calc(90vh - 160px);
     left: calc(90vw - 200px);
  }
}
O que fazer
.box {
  position: absolute;
  top: 10px;
  left: 10px;
  animation: move 3s ease infinite;
}

@keyframes move {
  50% {
     transform: translate(calc(90vw - 200px), calc(90vh - 160px));
  }
}

É possível testar isso nos dois exemplos do Glitch a seguir e explorar a performance usando o DevTools.

Com a mesma marcação, podemos substituir: padding-top: 56.25% por aspect-ratio: 16 / 9, definindo aspect-ratio como uma proporção especificada de width / height.

Como usar padding-top
.container {
  width: 100%;
  padding-top: 56.25%;
}
Como usar proporção
.container {
  width: 100%;
  aspect-ratio: 16 / 9;
}

O uso de aspect-ratio em vez de padding-top é muito mais claro e não substitui a propriedade de padding para fazer algo fora do escopo habitual.

Isso mesmo. Estou usando reduce para encadear uma sequência de promessas. Sou muito inteligente. Mas essa é uma codificação inteligente que facilita o trabalho sem ela.

No entanto, ao converter o item acima para uma função assíncrona, é tentador ser sequencial demais:

Não recomendado: sequencial demais
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
Parece muito mais elegante, mas minha segunda busca não começa até que a primeira tenha sido totalmente lida e assim por diante. Isso é muito mais lento do que o exemplo de promessas que executa as buscas em paralelo. Felizmente há um meio-termo ideal.
Recomendado: bom e paralelo
function markHandled(...promises) {
  Promise.allSettled(promises);
}

async function logInOrder(urls) {
  // fetch all the URLs in parallel
  const textPromises = urls.map(async (url) => {
    const response = await fetch(url);
    return response.text();
  });

  markHandled(...textPromises);

  // log them in sequence
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
Neste exemplo, os URLs são buscados e lidos em paralelo, mas o bit reduce "inteligente" é substituído por um loop for padrão, chato e legível.

Como gravar propriedades personalizadas do Houdini

Veja um exemplo de como configurar uma propriedade personalizada (como a variável CSS), mas agora com sintaxe (tipo), valor inicial (substituto) e booleano de herança (ele herda o valor do pai ou não?). A maneira atual de fazer isso é com CSS.registerProperty() em JavaScript, mas no Chromium 85 e versões posteriores, a sintaxe @property será compatível com seus arquivos CSS:

Arquivo JavaScript separado (Chromium 78)
CSS.registerProperty({
  name: '--colorPrimary',
  syntax: '',
  initialValue: 'magenta',
  inherits: false
});
Incluído no arquivo CSS (Chromium 85)
@property --colorPrimary {
  syntax: '';
  initial-value: magenta;
  inherits: false;
}

Agora é possível acessar --colorPrimary como qualquer outra propriedade personalizada de CSS usando var(--colorPrimary). No entanto, a diferença é que --colorPrimary não é apenas lido como uma string. Ela tem dados!

A CSS backdrop-filter aplica um ou mais efeitos a um elemento translúcido ou transparente. Para entender isso, considere as imagens abaixo.

Sem transparência de primeiro plano
Um triângulo sobreposto em um círculo. Não é possível ver o círculo dentro do triângulo.
.frosty-glass-pane {
  backdrop-filter: blur(2px);
}
Transparência em primeiro plano
Um triângulo sobreposto em um círculo. O triângulo é translúcido, o que permite que o círculo seja visto através dele.
.frosty-glass-pane {
  opacity: .9;
  backdrop-filter: blur(2px);
}

A imagem à esquerda mostra como elementos sobrepostos seriam renderizados se o backdrop-filter não fosse usado nem compatível. A imagem à direita aplica um efeito de desfoque usando backdrop-filter. Observe que ele usa opacity, além de backdrop-filter. Sem o opacity, não há nada para aplicar o desfoque. É quase óbvio que, se opacity for definida como 1 (totalmente opaco), não haverá efeito no segundo plano.

No entanto, ao contrário do evento unload, há usos legítimos para beforeunload. Por exemplo, quando você quiser avisar ao usuário que ele fez alterações não salvas, ele será perdido se sair da página. Nesse caso, recomendamos adicionar listeners beforeunload somente quando um usuário tiver mudanças não salvas e, em seguida, removê-los imediatamente após salvar as mudanças.

O que não fazer
window.addEventListener('beforeunload', (event) => {
  if (pageHasUnsavedChanges()) {
    event.preventDefault();
    return event.returnValue = 'Are you sure you want to exit?';
  }
});
O código acima adiciona um listener beforeunload incondicionalmente.
O que fazer
function beforeUnloadListener(event) {
  event.preventDefault();
  return event.returnValue = 'Are you sure you want to exit?';
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  window.addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  window.removeEventListener('beforeunload', beforeUnloadListener);
});
O código acima só adiciona o listener beforeunload quando necessário (e o remove quando não é).

Minimizar o uso de Cache-Control: no-store

Cache-Control: no-store é um cabeçalho HTTP que os servidores da Web podem definir em respostas que instrui o navegador a não armazenar a resposta em nenhum cache HTTP. Ela deve ser usada para recursos que contêm informações sensíveis do usuário, como páginas protegidas por login.

O elemento fieldset, que contém cada grupo de entrada (.fieldset-item), está usando gap: 1px para criar as bordas finas entre elementos. Nenhuma solução de borda complicada.

Lacuna preenchida
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
Truque de limites
.grid {
  display: grid;

  & > .fieldset-item {
    background: var(--bg-surface-2);

    &:not(:last-child) {
      border-bottom: 1px solid var(--bg-surface-1);
    }
  }
}

Quebra natural de grade

O layout mais complexo acabou sendo o layout de macro, o sistema lógico de layout entre <main> e <form>.

entrada
<input
  type="checkbox"
  id="text-notifications"
  name="text-notifications"
>
Identificador
<label for="text-notifications">
  <h3>Text Messages</h3>
  <small>Get notified about all text messages sent to your device</small>
</label>

O elemento fieldset, que contém cada grupo de entrada (.fieldset-item), está usando gap: 1px para criar as bordas finas entre elementos. Nenhuma solução de borda complicada.

Lacuna preenchida
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
Truque de limites
.grid {
  display: grid;

  & > .fieldset-item {
    background: var(--bg-surface-2);

    &:not(:last-child) {
      border-bottom: 1px solid var(--bg-surface-1);
    }
  }
}

Layout das guias <header>

O próximo layout é quase o mesmo: uso flexível para criar ordenação vertical.

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

O .snap-indicator precisa viajar horizontalmente com o grupo de links, e esse layout de cabeçalho ajuda a definir esse estágio. Não há elementos de posição absoluta aqui.

O Gentle Flex é uma verdadeira estratégiaúnica. É suave e suave, porque, ao contrário da place-content: center, nenhum tamanho de caixa infantil é modificado durante a centralização. Da forma mais suave possível, todos os itens são empilhados, centralizados e espaçados.

.gentle-flex {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1ch;
}
Prós
  • Processa apenas o alinhamento, a direção e a distribuição.
  • Edições e manutenção em um só lugar
  • A lacuna garante espaçamento igual entre n filhos
Desvantagens
  • A maioria das linhas de código

Ótimo para layouts macro e micro.

Uso

gap aceita qualquer comprimento ou porcentagem de CSS como um valor.

.gap-example {
  display: grid;
  gap: 10px;
  gap: 2ch;
  gap: 5%;
  gap: 1em;
  gap: 3vmax;
}


A lacuna pode ser transmitida em um comprimento, que será usado para linha e coluna.

Forma abreviada
.grid {
  display: grid;
  gap: 10px;
}
Defina as linhas e colunas de uma só vez
Aberto
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 10px;
}


A lacuna pode ser transmitida para dois comprimentos, que serão usados para linha e coluna.

Forma abreviada
.grid {
  display: grid;
  gap: 10px 5%;
}
Defina as linhas e colunas separadamente de uma só vez
Aberto
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 5%;
}