Comparar e comparar

O atributo lang só pode ter um idioma associado a ele. Isso significa que o atributo <html> só pode ter um idioma, mesmo que haja vários idiomas 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 é árabe.

Assim como os botões, os links recebem o nome acessível principalmente do conteúdo de texto. Um bom truque ao criar um link é colocar o texto mais significativo no próprio link, em vez de palavras de enchimento como "Aqui" ou "Leia mais".

Não é suficientemente descritivo
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, consegui o mesmo resultado visual animando top e left e usando 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));
  }
}

Você pode testar isso nos dois exemplos de Glitch a seguir e conferir 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 a proporção
.container {
  width: 100%;
  aspect-ratio: 16 / 9;
}

Usar aspect-ratio em vez de padding-top é muito mais claro e não altera a propriedade de padding para fazer algo fora do escopo normal.

Sim, isso mesmo, estou usando reduce para ligar de uma sequência de promessas. Eu sou tão inteligente. Mas isso é uma codificação muito inteligente que é melhor não usar.

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

Não recomendado: muito sequencial
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
Ficou muito mais elegante, mas minha segunda busca não começa até que a primeira busca 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, enfadonho e legível.

Como gravar propriedades personalizadas do Houdini

Confira um exemplo de como definir uma propriedade personalizada (pense em uma variável CSS), mas agora com uma sintaxe (tipo), um valor inicial (padrão) e um booleano de herança (ele herda o valor do pai ou não?). Atualmente, isso é feito usando CSS.registerProperty() no JavaScript, mas no Chromium 85 e versões mais recentes, a sintaxe @property será compatível com os 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 do CSS, por meio de var(--colorPrimary). No entanto, a diferença aqui é que --colorPrimary não é lido apenas como uma string. Ele tem dados!

O 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 em primeiro plano
Um triângulo sobreposto a um círculo. O círculo não pode ser visto pelo triângulo.
.frosty-glass-pane {
  backdrop-filter: blur(2px);
}
Transparência do 1º plano
Um triângulo sobreposto a um círculo. O triângulo é translúcido, permitindo que o círculo seja visto por ele.
.frosty-glass-pane {
  opacity: .9;
  backdrop-filter: blur(2px);
}

A imagem à esquerda mostra como os elementos sobrepostos seriam renderizados se backdrop-filter não fosse usado ou não tivesse suporte. A imagem à direita aplica um efeito de desfoque usando backdrop-filter. Ele usa opacity, além de backdrop-filter. Sem opacity, não haveria nada para aplicar o desfoque. Se opacity for definido como 1 (totalmente opaco), não haverá efeito no plano de fundo.

No entanto, ao contrário do evento unload, há usos legítimos para beforeunload. Por exemplo, quando você quer avisar o usuário de que ele tem mudanças não salvas que serão perdidas se ele sair da página. Nesse caso, é recomendado adicionar listeners beforeunload apenas quando um usuário tiver alterações não salvas e removê-los imediatamente após as alterações não salvas serem salvas.

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 instruem o navegador a não armazenar a resposta em nenhum cache HTTP. Isso deve ser usado para recursos que contêm informações sensíveis do usuário, por exemplo, páginas protegidas por login.

O elemento fieldset, que contém cada grupo de entrada (.fieldset-item), usa gap: 1px para criar as bordas finas entre os elementos. Não há solução de fronteira complicada.

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

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
Truque da borda
.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 macro layout, o sistema de layout lógico entre <main> e <form>.

entrada
<input
  type="checkbox"
  id="text-notifications"
  name="text-notifications"
>
o rótulo.
<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), usa gap: 1px para criar as bordas finas entre os elementos. Não há solução de fronteira complicada.

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

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

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

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

Layout de <header> das guias

O próximo layout é quase o mesmo: uso o flex para criar a 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 se mover horizontalmente com o grupo de links, e esse layout de cabeçalho ajuda a definir esse estágio. Não há elementos posicionados de forma absoluta aqui.

O Gentle Flex é uma estratégia de centralização mais verdadeira. Ele é suave e suave, porque diferente de place-content: center, nenhum tamanho de caixa de crianças é alterado durante a centralização. Com cuidado, 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
  • As edições e a manutenção ficam em um só lugar
  • O intervalo garante o mesmo espaçamento entre n crianças
Contras
  • A maioria das linhas de código

Ótimo para layouts macro e micro.

Uso

gap aceita qualquer length ou percentage CSS como valor.

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


O intervalo pode ser transmitido com 1 comprimento, que será usado para linha e coluna.

Abreviatura
.grid {
  display: grid;
  gap: 10px;
}
Definir linhas e colunas juntos de uma só vez
Expandido
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 10px;
}


O intervalo pode receber duas medidas, que serão usadas para linha e coluna.

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