정렬 및 필터링 사용자 환경을 위한 반응형, 적응형, 접근성 있는 다중 선택 구성요소를 빌드하는 방법에 관한 기본 개요입니다.
이 게시물에서는 다중 선택 구성요소를 빌드하는 방법에 대한 생각을 공유하고자 합니다. 데모를 사용해 보세요.
동영상을 선호하는 경우 이 게시물의 YouTube 버전을 참고하세요.
개요
사용자에게 항목이 표시되는 경우가 많으며 때로는 많은 항목이 표시됩니다. 이 경우 선택 과부하를 방지하기 위해 목록을 줄이는 방법을 제공하는 것이 좋습니다. 이 블로그 게시물에서는 선택사항을 줄이는 방법으로 필터링 UI를 살펴봅니다. 이를 위해 사용자가 선택하거나 선택 해제할 수 있는 상품 속성을 표시하여 결과를 줄이고 선택 과부하를 줄입니다.
상호작용 수
목표는 모든 사용자와 다양한 입력 유형에 대해 필터 옵션을 빠르게 탐색할 수 있도록 하는 것입니다. 이는 적응 가능하고 반응이 빠른 구성요소 쌍으로 제공됩니다. 데스크톱, 키보드, 스크린 리더용 체크박스의 기존 사이드바와 터치 사용자용 <select
multiple>
데스크톱이 아닌 터치에 내장 멀티셀렉트를 사용하기 위한 이 결정은 작업을 절약하고 작업을 생성하지만, 한 구성요소에서 전체 반응형 환경을 빌드하는 것보다 적은 코드 빚으로 적절한 환경을 제공한다고 생각합니다.
터치
터치 구성요소는 공간을 절약하고 휴대기기에서 사용자 상호작용 정확성을 높여줍니다. 체크박스의 전체 사이드바를 <select>
내장 오버레이 터치 환경으로 접어 공간을 절약합니다. 시스템에서 제공하는 대형 터치 오버레이 환경을 표시하여 입력 정확성을 높여줍니다.
키보드 및 게임패드
다음은 키보드에서 <select multiple>
를 사용하는 방법을 보여주는 데모입니다.
이 내장 멀티셀렉션은 스타일을 지정할 수 없으며 많은 옵션을 표시하는 데 적합하지 않은 좁은 레이아웃으로만 제공됩니다. 작은 상자에서는 다양한 옵션을 확인하기 어렵다는 것을 알 수 있나요? 크기를 변경할 수는 있지만 체크박스의 사이드바만큼 유용하지는 않습니다.
마크업
두 구성요소는 동일한 <form>
요소에 포함됩니다. 이 양식의 결과(체크박스 또는 멀티셀렉트)는 관찰되고 그리드를 필터링하는 데 사용되지만 서버에 제출될 수도 있습니다.
<form>
</form>
체크박스 구성요소
체크박스 그룹은 <fieldset>
요소로 래핑하고 <legend>
를 지정해야 합니다.
HTML이 이런 식으로 구성되면 스크린 리더와 FormData가 요소의 관계를 자동으로 이해합니다.
<form>
<fieldset>
<legend>New</legend>
… checkboxes …
</fieldset>
</form>
그룹화가 완료되면 각 필터에 <label>
및 <input type="checkbox">
를 추가합니다. CSS gap
속성이 라벨이 여러 줄로 표시될 때 균등하게 간격을 두고 정렬을 유지할 수 있도록 <div>
로 래핑했습니다.
<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>
<select multiple>
구성요소
<select>
요소의 거의 사용되지 않는 기능은 multiple
입니다.
이 속성이 <select>
요소와 함께 사용되면 사용자는 목록에서 여러 항목을 선택할 수 있습니다. 라디오 목록에서 체크박스 목록으로 상호작용을 변경하는 것과 같습니다.
<form>
<select multiple="true" title="Filter results by category">
…
</select>
</form>
<select>
내에서 그룹을 라벨링하고 만들려면 <optgroup>
요소를 사용하고 label
속성과 값을 지정합니다. 이 요소와 속성 값은 <fieldset>
및 <legend>
요소와 유사합니다.
<form>
<select multiple="true" title="Filter results by category">
<optgroup label="New">
…
</optgroup>
</select>
</form>
이제 필터의 <option>
요소를 추가합니다.
<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>
카운터로 입력 추적하여 보조 기술에 알림
이 사용자 환경에서는 상태 역할 기법을 사용하여 스크린 리더 및 기타 보조 기술의 필터 집계를 추적하고 유지합니다. YouTube 동영상에서 이 기능을 보여줍니다. 통합은 HTML 및 role="status"
속성으로 시작합니다.
<div role="status" class="sr-only" id="applied-filters"></div>
이 요소는 콘텐츠에 적용된 변경사항을 소리 내어 읽습니다. 사용자가 체크박스와 상호작용할 때 CSS 카운터를 사용하여 콘텐츠를 업데이트할 수 있습니다. 이를 위해 먼저 입력 및 상태 요소의 상위 요소에 이름이 있는 카운터를 만들어야 합니다.
aside {
counter-reset: filters;
}
기본적으로 개수는 0
입니다. 이 디자인에서는 기본적으로 아무것도 :checked
가 아닙니다.
다음으로 새로 만든 카운터를 증가시키기 위해 :checked
인 <aside>
요소의 하위 요소를 타겟팅합니다. 사용자가 입력 상태를 변경하면 filters
카운터가 집계됩니다.
aside :checked {
counter-increment: filters;
}
이제 CSS는 체크박스 UI의 일반 집계를 인식하고 상태 역할 요소는 비어 있으며 값을 기다리고 있습니다. CSS는 메모리에서 집계를 유지하므로 counter()
함수를 사용하면 의사 요소 콘텐츠에서 값에 액세스할 수 있습니다.
aside #applied-filters::before {
content: counter(filters) " filters ";
}
이제 상태 역할 요소의 HTML이 스크린 리더에 '필터 2개'를 알립니다. 좋은 시작이지만 필터가 업데이트한 결과 집계를 공유하는 등 더 나은 방법이 있습니다. 카운터가 할 수 있는 작업이 아니므로 JavaScript에서 이 작업을 실행합니다.
기대감 중첩
모든 로직을 하나의 블록에 넣을 수 있었기 때문에 CSS nesting-1을 사용하면 카운터 알고리즘이 매우 좋았습니다. 휴대하고 읽고 업데이트할 수 있는 중앙 집중식 느낌
aside {
counter-reset: filters;
& :checked {
counter-increment: filters;
}
& #applied-filters::before {
content: counter(filters) " filters ";
}
}
레이아웃
이 섹션에서는 두 구성요소 간의 레이아웃을 설명합니다. 대부분의 레이아웃 스타일은 데스크톱 체크박스 구성요소용입니다.
양식
사용자의 가독성과 검색 가능성을 최적화하기 위해 양식의 최대 너비는 30자(영문 기준)로 설정되며, 이는 각 필터 라벨의 광학 선 너비를 설정하는 것과 같습니다. 이 양식은 그리드 레이아웃과 gap
속성을 사용하여 필드 셋의 간격을 띄웁니다.
form {
display: grid;
gap: 2ch;
max-inline-size: 30ch;
}
<select>
요소
라벨 및 체크박스 목록이 모두 모바일에서 너무 많은 공간을 차지합니다. 따라서 레이아웃은 사용자의 기본 포인팅 기기를 확인하여 터치 환경을 변경합니다.
@media (pointer: coarse) {
select[multiple] {
display: block;
}
}
값이 coarse
이면 사용자가 기본 입력 장치로 화면과 높은 정확도로 상호작용할 수 없음을 나타냅니다. 휴대기기에서는 기본 상호작용이 터치이므로 포인터 값이 coarse
인 경우가 많습니다. 데스크톱 기기에서는 마우스나 기타 고정밀 입력 장치가 연결되어 있는 경우가 많으므로 포인터 값이 fine
인 경우가 많습니다.
필드 세트
<legend>
가 있는 <fieldset>
의 기본 스타일과 레이아웃은 고유합니다.
일반적으로 하위 요소의 간격을 두려면 gap
속성을 사용하지만 <legend>
의 고유한 위치로 인해 간격이 균등한 하위 요소 집합을 만들기가 어렵습니다. gap
대신 인접한 형제 선택자 및 margin-block-start
가 사용됩니다.
fieldset {
padding: 2ch;
& > div + div {
margin-block-start: 2ch;
}
}
이렇게 하면 <div>
하위 요소만 타겟팅하여 <legend>
의 공간이 조정되지 않습니다.
필터 라벨 및 체크박스
<fieldset>
의 직접 하위 요소이며 양식의 30ch
최대 너비 내에 있으므로 라벨 텍스트가 너무 길면 줄바꿈될 수 있습니다. 텍스트 줄바꿈은 좋지만 텍스트와 체크박스가 잘못 정렬된 것은 좋지 않습니다. Flexbox가 여기에 적합합니다.
fieldset > div {
display: flex;
gap: 2ch;
align-items: baseline;
}
애니메이션된 그리드
레이아웃 애니메이션은 Isotope에서 실행합니다. 대화형 정렬 및 필터링을 위한 강력하고 성능이 우수한 플러그인입니다.
자바스크립트
JavaScript는 깔끔한 애니메이션이 적용된 대화형 그리드를 조정하는 데 도움을 줄 뿐만 아니라 몇 가지 미흡한 부분을 다듬는 데도 사용됩니다.
사용자 입력 정규화
이 디자인에는 입력을 제공하는 두 가지 방법이 있는 하나의 양식이 있으며, 두 방법은 동일하게 직렬화되지 않습니다. 하지만 JavaScript를 사용하면 데이터를 정규화할 수 있습니다.
<select>
요소 데이터 구조를 그룹화된 체크박스 구조에 맞추기로 했습니다. 이렇게 하려면 input
이벤트 리스너를 <select>
요소에 추가합니다. 그러면 selectedOptions
가 매핑됩니다.
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
}, [])
})
이제 양식을 제출하거나 이 데모의 경우 Isotope에 필터링할 항목을 지시할 수 있습니다.
상태 역할 요소 완료
이 요소는 체크박스 상호작용에 따라 필터 수를 집계하고 공지하지만 결과 수를 추가로 공유하고 <select>
요소 선택도 집계하는 것이 좋을 것 같았습니다.
<select>
요소 선택사항이 counter()
에 반영됨
데이터 정규화 섹션에서 입력 시 리스너가 이미 생성되었습니다. 이 함수의 끝에서 선택한 필터의 수와 해당 필터의 결과 수가 알려집니다. 값은 다음과 같이 상태 역할 요소에 전달할 수 있습니다.
let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length
role="status"
요소에 반영된 결과
:checked
는 선택한 필터 수를 상태 역할 요소에 전달하는 내장된 방법을 제공하지만 필터링된 결과 수를 볼 수 없습니다.
JavaScript는 체크박스와의 상호작용을 감시하고 그리드를 필터링한 후 <select>
요소와 마찬가지로 textContent
를 추가할 수 있습니다.
document
.querySelector('aside form')
.addEventListener('input', e => {
// isotope demo code
let filterResults = IsotopeGrid.getFilteredItemElements().length
document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})
이렇게 하면 '2개의 필터로 25개의 결과를 얻음'이라는 공지사항이 완성됩니다.
이제 Google의 우수한 보조 기술 환경이 상호작용하는 방식과 관계없이 모든 사용자에게 제공됩니다.
결론
이제 제가 어떻게 했는지 알았으니 어떻게 하시겠어요? 🙂
접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다. 데모를 만들어 트윗해 주시면 아래의 커뮤니티 리믹스 섹션에 추가해 드리겠습니다.
커뮤니티 리믹스
아직 표시할 내용이 없습니다.