Uma visão geral básica de como criar um componente de navegação estruturada responsivo e acessível para que os usuários naveguem pelo seu site.
Neste post, quero compartilhar uma maneira de criar componentes de trilha de navegação. Teste a demonstração.
Se preferir vídeos, confira a versão desta postagem no YouTube:
Visão geral
Um componente de localização atual mostra em que parte da hierarquia do site o usuário está. O nome vem de Hansel e Gretel, que deixaram pedaços de pão para trás em uma floresta escura e conseguiram encontrar o caminho de casa rastreando os pedaços de volta.
Os breadcrumbs nesta postagem não são padrões,
são semelhantes a breadcrumbs. Eles oferecem mais funcionalidade colocando páginas irmãs
diretamente na navegação com um <select>
, tornando possível o acesso em vários níveis.
UX em segundo plano
No vídeo de demonstração de componentes acima, as categorias de marcador de posição são gêneros de
videogames. Essa trilha é criada ao navegar pelo seguinte caminho: home »
rpg » indie » on sale
, conforme mostrado abaixo.
Esse componente de breadcrumbs precisa permitir que os usuários naveguem por essa hierarquia de informações, pulando ramificações e selecionando páginas com velocidade e precisão.
Arquitetura de informações
Acho útil pensar em termos de coleções e itens.
Coleções
Uma coleção é uma matriz de opções para escolher. Na página inicial do protótipo de breadcrumb desta postagem, as coleções são FPS, RPG, brawler, dungeon crawler, esportes e quebra-cabeça.
Itens
Um videogame é um item, e uma coleção específica também pode ser um item se representar outra coleção. Por exemplo, RPG é um item e uma coleção válida. Quando é um item, o usuário acessa a página da coleção. Por exemplo, eles estão na página "RPG", que mostra uma lista de jogos desse tipo, incluindo as subcategorias adicionais "AAA", "Indie" e "Autopublicação".
Em termos de ciência da computação, esse componente de trilha de navegação representa uma matriz multidimensional:
const rawBreadcrumbData = {
"FPS": {...},
"RPG": {
"AAA": {...},
"indie": {
"new": {...},
"on sale": {...},
"under 5": {...},
},
"self published": {...},
},
"brawler": {...},
"dungeon crawler": {...},
"sports": {...},
"puzzle": {...},
}
Seu app ou site terá uma arquitetura de informação (IA) personalizada que cria uma matriz multidimensional diferente, mas espero que o conceito de páginas de destino de coleção e a travessia de hierarquia também possam ser incluídos nos seus breadcrumbs.
Layouts
Marcação
Bons componentes começam com HTML apropriado. Na próxima seção, vou abordar minhas opções de marcação e como elas afetam o componente geral.
Esquema claro e escuro
<meta name="color-scheme" content="dark light">
A metatag color-scheme
no snippet acima
informa ao navegador que a página precisa dos estilos claro e escuro
do navegador. O exemplo de trilhas de navegação não inclui CSS para esses esquemas de cores.
Por isso, as trilhas de navegação usam as cores padrão fornecidas pelo navegador.
Elemento de navegação
<nav class="breadcrumbs" role="navigation"></nav>
É apropriado usar o elemento
<nav>
para a navegação do site, que tem um papel de
navegação ARIA implícito.
Durante os testes, notei que o atributo role
mudou a maneira como um
Leitor de tela interagiu com o elemento. Ele foi anunciado como
navegação, e por isso decidi adicioná-lo.
Ícones
Quando um ícone é repetido em uma página, o elemento SVG
<use>
significa que você pode definir o path
uma vez e usá-lo para todas as instâncias do
ícone. Isso impede que as mesmas informações de caminho sejam repetidas, causando
documentos maiores e o potencial de inconsistência de caminho.
Para usar essa técnica, adicione um elemento SVG oculto à página e envolva os ícones
em um elemento <symbol>
com um ID exclusivo:
<svg style="display: none;">
<symbol id="icon-home">
<title>A home icon</title>
<path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
</symbol>
<symbol id="icon-dropdown-arrow">
<title>A down arrow</title>
<path d="M19 9l-7 7-7-7"/>
</symbol>
</svg>
O navegador lê o HTML SVG, coloca as informações do ícone na memória e continua com o restante da página referenciando o ID para outros usos do ícone, desta forma:
<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
<use href="#icon-home" />
</svg>
<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
<use href="#icon-dropdown-arrow" />
</svg>
Defina uma vez e use quantas vezes quiser, com um impacto mínimo na performance da página
e um estilo flexível. Observe que aria-hidden="true"
foi adicionado ao elemento SVG.
Os ícones não são úteis para alguém que está navegando e só ouve o conteúdo. Ocultar
os ícones desses usuários evita que eles adicionem ruído desnecessário.
Link dividido .crumb
É aqui que o caminho de navegação tradicional e os caminhos neste componente divergem.
Normalmente, esse seria apenas um link <a>
, mas adicionei a UX de travessia com um
seletor disfarçado. A classe .crumb
é responsável por posicionar o link e
o ícone, enquanto o .crumbicon
é responsável por empilhar o ícone e o elemento
selecionado. Chamei de link dividido porque as funções dele são muito
semelhantes a um botão dividido,
mas para navegação de página.
<span class="crumb">
<a href="#sub-collection-b">Category B</a>
<span class="crumbicon">
<svg>...</svg>
<select class="disguised-select" title="Navigate to another category">
<option>Category A</option>
<option selected>Category B</option>
<option>Category C</option>
</select>
</span>
</span>
Um link e algumas opções não são nada especiais, mas adicionam mais funcionalidade a um
breadcrumb simples. Adicionar um title
ao elemento <select>
é útil para usuários de leitores
de tela, fornecendo informações sobre a ação do botão. No entanto, ele
oferece a mesma ajuda para todos, e você vai encontrar essa informação no
iPad. Um atributo fornece contexto de botão para muitos usuários.
Decorações de separadores
<span class="crumb-separator" aria-hidden="true">→</span>
Os separadores são opcionais, mas adicionar apenas um também funciona (confira o terceiro exemplo no vídeo
acima). Em seguida, atribuo cada aria-hidden="true"
, já que eles são decorativos e não
algo que um leitor de tela precisa anunciar.
A propriedade gap
, abordada a seguir, simplifica o espaçamento entre esses elementos.
Estilos
Como a cor usa cores do sistema, ela tem principalmente lacunas e pilhas para estilos.
Direção e fluxo do layout
O elemento de navegação principal nav.breadcrumbs
define uma propriedade personalizada com escopo
para os filhos usarem. Caso contrário, estabelece um layout horizontal
alinhado verticalmente. Isso garante que as trilhas, os divisores e os ícones fiquem alinhados.
.breadcrumbs {
--nav-gap: 2ch;
display: flex;
align-items: center;
gap: var(--nav-gap);
padding: calc(var(--nav-gap) / 2);
}
Cada .crumb
também estabelece um layout alinhado verticalmente com alguma
diferença, mas segmenta especificamente os links filhos e especifica o estilo
white-space: nowrap
. Isso é crucial para breadcrumbs com várias palavras, porque não
queremos que eles tenham várias linhas. Mais adiante nesta postagem, vamos adicionar estilos para processar o
overflow horizontal causado por essa propriedade white-space
.
.crumb {
display: inline-flex;
align-items: center;
gap: calc(var(--nav-gap) / 4);
& > a {
white-space: nowrap;
&[aria-current="page"] {
font-weight: bold;
}
}
}
aria-current="page"
foi adicionado para ajudar a destacar o link da página atual dos demais. Os usuários de leitores de tela terão uma indicação clara de que o link é
para a página atual, mas também estilizamos o elemento visualmente para ajudar os usuários com deficiência visual
a ter uma experiência do usuário semelhante.
O componente .crumbicon
usa a grade para empilhar um ícone SVG com um elemento <select>
"quase invisível".
.crumbicon {
--crumbicon-size: 3ch;
display: grid;
grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
place-items: center;
& > * {
grid-area: stack;
}
}
O elemento <select>
é o último no DOM e fica no topo da pilha e é interativo. Adicione um estilo de opacity: .01
para que o elemento ainda seja utilizável,
e o resultado será uma caixa de seleção que se encaixa perfeitamente na forma do ícone.
Essa é uma boa maneira de personalizar a aparência de um elemento <select>
,
mantendo a funcionalidade integrada.
.disguised-select {
inline-size: 100%;
block-size: 100%;
opacity: .01;
font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}
Menu flutuante
Os breadcrumbs precisam representar um caminho muito longo. Sou fã de permitir que as coisas saiam da tela horizontalmente, quando apropriado, e senti que esse componente de breadcrumbs se qualificou bem.
.breadcrumbs {
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x proximity;
scroll-padding-inline: calc(var(--nav-gap) / 2);
& > .crumb:last-of-type {
scroll-snap-align: end;
}
@supports (-webkit-hyphens:none) { & {
scroll-snap-type: none;
}}
}
Os estilos de overflow configuram a seguinte UX:
- Rolagem horizontal com contenção de rolagem.
- Padding de rolagem horizontal.
- Um ponto de ajuste na última marca. Isso significa que, no carregamento da página, o primeiro breadcrumb é carregado fixado e visível.
- Remove o ponto de ajuste do Safari, que sofre com as combinações de rolagem horizontal e ajuste de efeitos.
Consultas de mídia
Um ajuste sutil para janelas de visualização menores é ocultar o rótulo "Página inicial", deixando apenas o ícone:
@media (width <= 480px) {
.breadcrumbs .home-label {
display: none;
}
}
Acessibilidade
Movimento
Não há muito movimento nesse componente, mas, ao agrupar a
transição em uma verificação prefers-reduced-motion
, podemos evitar movimentos indesejados.
@media (prefers-reduced-motion: no-preference) {
.crumbicon {
transition: box-shadow .2s ease;
}
}
Nenhum dos outros estilos precisa mudar. Os efeitos de foco e de passar o cursor são ótimos
e significativos sem um transition
, mas, se o movimento estiver correto, vamos adicionar uma transição
sutil à interação.
JavaScript
Primeiro, independentemente do tipo de roteador usado no site ou aplicativo,
quando um usuário muda os breadcrumbs, o URL precisa ser atualizado e a página
adequada precisa ser mostrada ao usuário. Em segundo lugar, para normalizar a experiência do usuário, verifique se
não há navegações inesperadas quando os usuários estão apenas navegando pelas opções
<select>
.
Duas medidas importantes de experiência do usuário que precisam ser tratadas pelo JavaScript: o elemento select mudou
e a prevenção de disparo de evento de mudança <select>
ansioso.
A prevenção de eventos apressados é necessária devido ao uso de um elemento
<select>
. No Windows Edge e provavelmente em outros navegadores também, o evento changed
selecionado é disparado à medida que o usuário navega pelas opções com o teclado. Por isso,
chamamos de "ansioso", já que o usuário apenas selecionou a opção, como um passar o cursor
ou o foco, mas não confirmou a escolha com enter
ou click
. O evento
apressado torna esse recurso de mudança de categoria de componente inacessível, porque
abrir a caixa de seleção e simplesmente navegar por um item aciona o evento e
muda a página antes que o usuário esteja pronto.
Um evento <select>
melhor mudou
const crumbs = document.querySelectorAll('.breadcrumbs select')
const allowedKeys = new Set(['Tab', 'Enter', ' '])
const preventedKeys = new Set(['ArrowUp', 'ArrowDown'])
// watch crumbs for changes,
// ensures it's a full value change, not a user exploring options via keyboard
crumbs.forEach(nav => {
let ignoreChange = false
nav.addEventListener('change', e => {
if (ignoreChange) return
// it's actually changed!
})
nav.addEventListener('keydown', ({ key }) => {
if (preventedKeys.has(key))
ignoreChange = true
else if (allowedKeys.has(key))
ignoreChange = false
})
})
A estratégia para isso é observar os eventos de tecla pressionada em cada elemento <select>
e determinar se a tecla pressionada foi a confirmação de navegação (Tab
ou
Enter
) ou a navegação espacial (ArrowUp
ou ArrowDown
). Com essa
determinação, o componente pode decidir esperar ou ir, quando o evento do
elemento <select>
é acionado.
Conclusão
Agora que você sabe como eu fiz isso, o que você faria‽ 🙂
Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie uma demonstração, envie links para mim e vou adicionar à seção de remixes da comunidade abaixo.
Remixes da comunidade
- Tux Solbakk como um componente da Web: demonstração e código