Uma visão geral básica sobre como criar um componente de seleção múltipla responsivo, adaptável e acessível para classificar e filtrar experiências do usuário.
Nesta postagem, quero compartilhar ideias sobre uma forma de criar um componente de seleção múltipla. Experimente o demonstração.
Se você preferir o vídeo, aqui está uma versão do YouTube desta postagem:
Visão geral
Muitas vezes, os usuários recebem itens, às vezes muitos, e, casos, pode ser uma boa ideia fornecer uma forma de reduzir a lista para evitar sobrecarga de opções. Isso postagem do blog explora a interface de filtragem como uma forma de reduzir as opções. Ele faz isso apresentando atributos de item que os usuários podem marcar ou desmarcar, reduzindo os resultados reduzindo a sobrecarga de escolhas.
Interações
O objetivo é possibilitar a passagem rápida das opções de filtro para todos os usuários e seus
diferentes tipos de entrada. Ela será entregue com uma resposta
um par de componentes. Uma barra lateral tradicional de caixas de seleção para computador, teclado
e leitores de tela, e um <select
multiple>
para usuários de toque.
Essa decisão de usar a seleção múltipla integrada para toque, e não para computador, economiza trabalho e cria trabalho, mas acredito que ofereça experiências adequadas com menos débito de código do que a criação de toda a experiência responsiva em um componente.
Toque
O componente de toque economiza espaço e ajuda a precisão da interação do usuário
dispositivos móveis. Para economizar espaço, recolhe uma barra lateral inteira de caixas de seleção em uma
<select>
experiência de toque de sobreposição integrada. Ele ajuda na precisão da entrada, mostrando
uma grande experiência de sobreposição de toque fornecida pelo sistema.
Teclado e gamepad
Abaixo está uma demonstração de como usar uma <select multiple>
do teclado.
Esta seleção múltipla integrada não pode ser estilizada e só é oferecida em um formato compacto o layout não é adequado para apresentar muitas opções. Veja como você não pode realmente vê a amplitude de opções naquela pequena caixa? Embora seja possível alterar o tamanho, ainda não são tão utilizáveis quanto uma barra lateral de caixas de seleção.
Marcação
Os dois componentes estarão no mesmo elemento <form>
. Os resultados de
este formulário, seja de caixas de seleção ou de seleção múltipla, será observado e usado para
filtrar a grade, mas também podem ser enviados a um servidor.
<form>
</form>
Componente "Caixas de seleção"
Os grupos de caixas de seleção devem ser inseridos
<fieldset>
elemento e recebe um
<legend>
Quando o HTML é estruturado dessa forma, leitores de tela e
FormData vai
a entender automaticamente a relação entre os elementos.
<form>
<fieldset>
<legend>New</legend>
… checkboxes …
</fieldset>
</form>
Com o agrupamento pronto, adicione um <label>
e um <input type="checkbox">
para
cada um dos filtros. Optei por unir o meu em um <div>
para que a propriedade gap
do CSS
pode espaçá-los uniformemente e manter o alinhamento quando os rótulos ficam multilinhas.
<form>
<fieldset>
<legend>New</legend>
<div>
<input type="checkbox" id="last 30 days" name="new" value="last 30 days">
<label for="last 30 days">Last 30 Days</label>
</div>
<div>
<input type="checkbox" id="last 6 months" name="new" value="last 6 months">
<label for="last 6 months">Last 6 Months</label>
</div>
</fieldset>
</form>
Componente <select multiple>
Um recurso raramente usado do elemento <select>
é
multiple
.
Quando o atributo é usado com um elemento <select>
, o usuário pode
escolher várias da lista. É como mudar a interação de uma lista de opções
a uma lista de caixas de seleção.
<form>
<select multiple="true" title="Filter results by category">
…
</select>
</form>
Para rotular e criar grupos dentro de uma <select>
, use o
<optgroup>
e atribuir a ele um atributo e um valor label
. Esse elemento e atributo
são semelhantes aos elementos <fieldset>
e <legend>
.
<form>
<select multiple="true" title="Filter results by category">
<optgroup label="New">
…
</optgroup>
</select>
</form>
Agora adicione
<option>
elementos para o filtro.
<form>
<select multiple="true" title="Filter results by category">
<optgroup label="New">
<option value="last 30 days">Last 30 Days</option>
<option value="last 6 months">Last 6 Months</option>
</optgroup>
</select>
</form>
Acompanhamento de entradas com contadores para informar a tecnologia assistiva
O status
cargo
é usada nesta experiência de usuário, para acompanhar e manter o registro de
filtros para leitores de tela e outras tecnologias assistivas. O vídeo do YouTube
demonstra o recurso. A integração começa com HTML e o atributo
role="status"
:
<div role="status" class="sr-only" id="applied-filters"></div>
Este elemento lê em voz alta as mudanças feitas no conteúdo. Podemos atualizar conteúdos com CSS contadores à medida que os usuários interagem com as caixas de seleção. Para fazer isso, primeiro precisamos criar um contador com um nome em um elemento pai das entradas e do elemento de estado.
aside {
counter-reset: filters;
}
Por padrão, a contagem será 0
, o que é ótimo, nada é :checked
por
padrão neste design.
Em seguida, para incrementar nosso contador recém-criado, segmentaremos filhos do
<aside>
que são :checked
. Conforme o usuário muda o estado das entradas,
o contador filters
será registrado.
aside :checked {
counter-increment: filters;
}
O CSS agora está ciente da contagem geral da interface da caixa de seleção e da função de status
está vazio e aguardando valores. Como o CSS está mantendo o número em
a memória, os
counter()
permite acessar o valor de pseudo
do elemento:
aside #applied-filters::before {
content: counter(filters) " filters ";
}
O HTML do elemento de função de status agora anunciará "2 filtros " para uma tela leitor de tela. Esse é um bom começo, mas podemos fazer melhor. Por exemplo, compartilhar os resultados resultados atualizados pelos filtros. Faremos isso em JavaScript, pois é fora do que os contadores podem fazer.
Empolgação com o aninhamento
O algoritmo de contadores funcionou muito bem com o CSS nesting-1, porque consegui colocar todas lógica em um bloco. Parece portátil e centralizado para leitura e atualização.
aside {
counter-reset: filters;
& :checked {
counter-increment: filters;
}
& #applied-filters::before {
content: counter(filters) " filters ";
}
}
Layouts
Esta seção descreve os layouts entre os dois componentes. A maioria estilos de layout são para o componente de caixa de seleção para computadores.
O formulário
Para otimizar a legibilidade e a facilidade de leitura para os usuários, o formulário recebe um máximo
largura de 30 caracteres, definindo essencialmente uma largura de linha óptica para cada
rótulo de filtro. O formulário usa o layout de grade e a propriedade gap
para espaçar o
Conjuntos de campo.
form {
display: grid;
gap: 2ch;
max-inline-size: 30ch;
}
O elemento <select>
A lista de marcadores e caixas de seleção ocupam muito espaço em dispositivos móveis. Portanto, o layout verifica se é possível mudar o dispositivo apontador principal do usuário a experiência do toque.
@media (pointer: coarse) {
select[multiple] {
display: block;
}
}
Um valor de coarse
indica que o usuário não poderá interagir com
na tela com altas quantidades de precisão com seu dispositivo de entrada principal. Em um
dispositivo móvel, o valor do ponteiro geralmente é coarse
, como a interação principal.
é o toque. Em um dispositivo desktop, o valor do ponteiro geralmente é fine
, porque é comum
conectar um mouse ou outro dispositivo de entrada de alta precisão.
Os camposets
O estilo e o layout padrão de uma <fieldset>
com <legend>
são exclusivos:
Normalmente, para espaçar meus elementos filhos, eu usaria a propriedade gap
, mas o
o posicionamento do <legend>
dificulta a criação de um conjunto com espaçamento uniforme
das crianças. Em vez de gap
, o irmão adjacente
seletor e
margin-block-start
são usados.
fieldset {
padding: 2ch;
& > div + div {
margin-block-start: 2ch;
}
}
Isso evita que o <legend>
tenha o espaço ajustado, segmentando apenas o
<div>
crianças.
O rótulo do filtro e a caixa de seleção
Como um filho direto de um <fieldset>
e dentro da largura máxima do elemento
30ch
, o texto do rótulo poderá ser quebrado se for muito longo. Ajustar texto é ótimo, mas
o desalinhamento entre o texto e a caixa de seleção não é. O Flexbox é ideal para isso.
fieldset > div {
display: flex;
gap: 2ch;
align-items: baseline;
}
A grade animada
A animação do layout é feita por Isotope. Um um plug-in eficiente e eficiente para classificação e filtro interativos.
JavaScript
Além de ajudar a orquestrar uma grade animada e interativa, o JavaScript é usado para polir algumas bordas ásperas.
Normalizar a entrada do usuário
Esse design tem um formato com duas maneiras diferentes de fornecer informações não serialize o mesmo. Com um pouco de JavaScript, é possível normalizar os dados.
Escolhi alinhar a estrutura de dados do elemento <select>
às caixas de seleção agrupadas
na estrutura dos preços. Para fazer isso, um
input
o listener de eventos é adicionado ao elemento <select>
, quando
selectedOptions
são mapeados.
document.querySelector('select').addEventListener('input', event => {
// make selectedOptions iterable then reduce a new array object
let selectData = Array.from(event.target.selectedOptions).reduce((data, opt) => {
// parent optgroup label and option value are added to the reduce aggregator
data.push([opt.parentElement.label.toLowerCase(), opt.value])
return data
}, [])
})
Você já pode enviar o formulário ou, no caso desta demonstração, instrua a Isotope sobre os itens a serem filtrados.
Finalizar o elemento de função de status
O elemento só está contabilizando e anunciando a contagem de filtros com base na caixa de seleção
de interação, mas achei que seria uma boa ideia compartilhar também o número de
resultados e garantir que as escolhas do elemento <select>
também sejam contadas.
A escolha do elemento <select>
é refletida em counter()
.
Na seção de normalização de dados, um listener já foi criado na entrada. Em ao final desta função, o número de filtros escolhidos e o número de resultados para esses filtros são conhecidas. Os valores podem ser passados para o elemento de papel de estado assim.
let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length
Resultados refletidos no elemento role="status"
:checked
fornece uma maneira integrada de transmitir o número de filtros escolhidos para
o elemento do papel de status, mas não tem visibilidade para o número filtrado de resultados.
O JavaScript pode observar a interação com as caixas de seleção e, depois de filtrar o
grade, adicione textContent
da mesma forma que o elemento <select>
.
document
.querySelector('aside form')
.addEventListener('input', e => {
// isotope demo code
let filterResults = IsotopeGrid.getFilteredItemElements().length
document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})
Juntos, esse trabalho completa o anúncio "2 filtros que fornecem 25 resultados".
Agora, nossa excelente experiência de tecnologia assistiva será entregue a todos os independentemente da interação dos usuários com eles.
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 um tweet para mim e adicione os links acesse a seção "Remixes da comunidade" abaixo.
Remixes da comunidade
Não há nada aqui ainda.