색상 적응형 접근성 있는 도움말 텍스트 맞춤 요소를 빌드하는 방법에 관한 기본 개요입니다.
이 게시물에서는 색상 적응형이며 접근성이 뛰어난 <tool-tip>
맞춤 요소를 빌드하는 방법에 관한 생각을 공유하고자 합니다. 데모를 사용해 보고 소스를 확인해 보세요.
동영상을 선호하는 경우 이 게시물의 YouTube 버전을 참고하세요.
개요
도움말은 사용자 인터페이스에 대한 추가 정보를 포함하는 모달이 아니고 차단되지 않으며 상호작용이 없는 오버레이입니다. 기본적으로 숨겨져 있으며 연결된 요소에 마우스를 가져가거나 포커스를 맞추면 숨겨지지 않습니다. 도움말은 직접 선택하거나 상호작용할 수 없습니다. 도움말은 라벨이나 기타 가치가 높은 정보를 대체하는 것이 아닙니다. 사용자는 도움말 없이도 작업을 완전히 완료할 수 있어야 합니다.
전환 도움말과 도움말 비교
많은 구성요소와 마찬가지로 도움말의 정의도 다양합니다(예: MDN, WAI ARIA, Sarah Higley, 포용적 구성요소). 도움말과 전환 도움말이 구분된 것이 좋습니다. 도움말에는 상호작용이 없는 보충 정보가 포함되어야 하지만 전환 도움말에는 상호작용과 중요한 정보가 포함될 수 있습니다. 이러한 구분의 주된 이유는 접근성입니다. 사용자가 팝업으로 이동하여 내부 정보와 버튼에 액세스하는 방법입니다. 전환 도움말은 금방 복잡해집니다.
다음은 사용자가 고정하여 열고 탐색한 후 밝은 닫기 버튼이나 Esc 키로 닫을 수 있는 상호작용이 있는 오버레이인 Designcember 사이트의 전환 도움말 동영상입니다.
이 GUI 챌린지는 CSS로 거의 모든 작업을 수행하는 툴팁 경로를 따랐습니다. 다음은 빌드하는 방법입니다.
마크업
맞춤 요소 <tool-tip>
를 사용하기로 했습니다. 작성자는 원하지 않는 경우 맞춤 요소를 웹 구성요소로 만들 필요가 없습니다. 브라우저는 <foo-bar>
를 <div>
와 마찬가지로 취급합니다. 맞춤 요소는 클래스 이름과 같이 구체성이 떨어진다고 생각할 수 있습니다. JavaScript는 관련이 없습니다.
<tool-tip>A tooltip</tool-tip>
이는 내부에 텍스트가 있는 div와 같습니다. [role="tooltip"]
를 추가하여 지원되는 스크린 리더의 접근성 트리에 연결할 수 있습니다.
<tool-tip role="tooltip">A tooltip</tool-tip>
이제 스크린 리더에 도움말로 인식됩니다. 다음 예에서 첫 번째 링크 요소의 트리에 인식된 도움말 요소가 있고 두 번째 링크 요소에는 인식된 도움말 요소가 없는 것을 확인할 수 있습니다. 두 번째 사용자에게는 역할이 없습니다. 스타일 섹션에서 이 트리 뷰를 개선합니다.
다음으로 도움말에 포커스를 설정할 수 없도록 해야 합니다. 스크린 리더가 도움말 역할이 무엇인지 이해하지 못하면 사용자가 <tool-tip>
에 포커스를 맞춰 콘텐츠를 읽을 수 있게 되며, 사용자 환경에는 이 기능이 필요하지 않습니다. 스크린 리더는 콘텐츠를 상위 요소에 추가하므로 액세스할 수 있도록 포커스가 필요하지 않습니다. 여기서는 inert
를 사용하여 사용자가 탭 흐름에서 실수로 이 도움말 콘텐츠를 찾지 못하도록 할 수 있습니다.
<tool-tip inert role="tooltip">A tooltip</tool-tip>
그런 다음 속성을 인터페이스로 사용하여 도움말의 위치를 지정했습니다. 기본적으로 모든 <tool-tip>
는 'top' 위치를 가정하지만 tip-position
를 추가하여 요소에서 위치를 맞춤설정할 수 있습니다.
<tool-tip role="tooltip" tip-position="right ">A tooltip</tool-tip>
저는 <tool-tip>
에 동시에 여러 위치가 할당되지 않도록 이와 같은 작업에 클래스 대신 속성을 사용하는 경향이 있습니다.
하나만 있거나 없을 수 있습니다.
마지막으로 도움말을 제공하려는 요소 내에 <tool-tip>
요소를 배치합니다. 여기서는 <picture>
요소 내에 이미지와 <tool-tip>
를 배치하여 alt
텍스트를 시각 장애가 없는 사용자와 공유합니다.
<picture>
<img alt="The GUI Challenges skull logo" width="100" src="...">
<tool-tip role="tooltip" tip-position="bottom">
The <b>GUI Challenges</b> skull logo
</tool-tip>
</picture>
여기서는 <abbr>
요소 내에 <tool-tip>
를 배치합니다.
<p>
The <abbr>HTML <tool-tip role="tooltip" tip-position="top">Hyper Text Markup Language</tool-tip></abbr> abbr element.
</p>
접근성
전환 도움말이 아닌 도움말을 빌드하기로 선택했기 때문에 이 섹션은 훨씬 간단합니다. 먼저 Google에서 원하는 사용자 환경을 간략히 설명해 드리겠습니다.
- 제약된 공간이나 복잡한 인터페이스에서는 보조 메시지를 숨깁니다.
- 사용자가 마우스를 가져가거나, 포커스를 맞추거나, 터치를 사용하여 요소와 상호작용할 때 메시지를 표시합니다.
- 마우스 오버, 포커스 또는 터치가 종료되면 메시지를 다시 숨깁니다.
- 마지막으로 사용자가 모션 감소 환경설정을 지정한 경우 모션이 감소하는지 확인합니다.
Google의 목표는 주문형 보충 메시지를 제공하는 것입니다. 시각 장애가 없는 마우스 또는 키보드 사용자는 마우스를 가져가 메시지를 표시하고 눈으로 읽을 수 있습니다. 시각장애인 스크린 리더 사용자는 메시지를 표시하도록 포커스를 맞추고 도구를 통해 소리로 메시지를 수신할 수 있습니다.
이전 섹션에서는 접근성 트리, 도움말 역할, 비활성 상태를 다뤘습니다. 이제 테스트를 진행하고 사용자 환경이 사용자에게 도움말 메시지를 적절하게 표시하는지 확인하면 됩니다. 테스트 결과, 오디오 메시지의 어느 부분이 도움말인지 명확하지 않습니다. 접근성 트리에서 디버깅하는 동안에도 확인할 수 있습니다. 'top'의 링크 텍스트가 'Look, tooltips!'와 함께 주저함 없이 실행됩니다. 스크린 리더가 텍스트를 끊거나 도움말 콘텐츠로 식별하지 않습니다.
<tool-tip>
에 스크린 리더 전용 가상 요소를 추가하면 시각 장애가 있는 사용자를 위한 자체 프롬프트 텍스트를 추가할 수 있습니다.
&::before {
content: "; Has tooltip: ";
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
아래에서 업데이트된 접근성 트리를 확인할 수 있습니다. 이제 링크 텍스트 뒤에 세미콜론이 있고 도움말 프롬프트인 '도움말 있음: '이 표시됩니다.
이제 스크린 리더 사용자가 링크에 포커스를 맞추면 '상단'이라고 말한 후 잠시 멈추었다가 '도움말이 있음: 도움말 보기'라고 알려줍니다. 이렇게 하면 화면 리더 사용자에게 몇 가지 유용한 UX 힌트를 제공할 수 있습니다. 지연을 사용하면 링크 텍스트와 도움말 텍스트를 깔끔하게 구분할 수 있습니다. 또한 'has tooltip'이 공지되면 화면 리더 사용자가 이전에 이미 들었을 경우 쉽게 취소할 수 있습니다. 이미 보셨듯이 마우스 오버했다가 빠르게 마우스 오버를 해제하는 것과 매우 유사합니다. 이는 UX 패리티가 잘 지켜진 것처럼 느껴졌습니다.
스타일
<tool-tip>
요소는 보조 메시지를 나타내는 요소의 하위 요소가 되므로 먼저 오버레이 효과의 기본사항부터 살펴보겠습니다. position absolute
를 사용하여 문서 흐름에서 제외합니다.
tool-tip {
position: absolute;
z-index: 1;
}
상위 요소가 스택 컨텍스트가 아닌 경우 툴팁이 가장 가까운 스택 컨텍스트에 배치되며 이는 바람직하지 않습니다. 블록에 도움이 되는 새로운 선택기가 있습니다. :has()
:has(> tool-tip) {
position: relative;
}
브라우저 지원에 대해 너무 걱정하지 마세요. 먼저 이러한 도움말은 보조적인 기능이라는 점을 기억하세요. 작동하지 않는 경우에도 괜찮습니다. 두 번째로, JavaScript 섹션에서 :has()
지원이 없는 브라우저에 필요한 기능을 폴리필하는 스크립트를 배포합니다.
다음으로, 상위 요소에서 포인터 이벤트를 훔치지 않도록 도움말을 비상호작용 상태로 만들어 보겠습니다.
tool-tip {
…
pointer-events: none;
user-select: none;
}
그런 다음 크로스페이드로 도움말을 전환할 수 있도록 불투명도로 도움말을 숨깁니다.
tool-tip {
opacity: 0;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
}
:is()
및 :has()
가 여기에서 대부분의 작업을 실행하며, 상위 요소가 포함된 tool-tip
가 하위 도움말 표시를 전환하는 데 필요한 사용자 상호작용을 인식하도록 합니다. 마우스 사용자는 마우스 오버할 수 있고, 키보드 및 스크린 리더 사용자는 포커스를 맞출 수 있으며, 터치 사용자는 탭할 수 있습니다.
시각 장애가 없는 사용자를 위해 오버레이 표시 및 숨기기가 작동하는 상태에서 테마 설정, 배치, 풍선에 삼각형 도형을 추가하기 위한 스타일을 추가해 보겠습니다. 다음 스타일은 맞춤 속성을 사용하여 시작하고 지금까지의 내용을 기반으로 그림자, 서체, 색상을 추가하여 플로팅 도움말처럼 보이게 합니다.
tool-tip {
--_p-inline: 1.5ch;
--_p-block: .75ch;
--_triangle-size: 7px;
--_bg: hsl(0 0% 20%);
--_shadow-alpha: 50%;
--_bottom-tip: conic-gradient(from -30deg at bottom, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) bottom / 100% 50% no-repeat;
--_top-tip: conic-gradient(from 150deg at top, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) top / 100% 50% no-repeat;
--_right-tip: conic-gradient(from -120deg at right, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) right / 50% 100% no-repeat;
--_left-tip: conic-gradient(from 60deg at left, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) left / 50% 100% no-repeat;
pointer-events: none;
user-select: none;
opacity: 0;
transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
transition: opacity .2s ease, transform .2s ease;
position: absolute;
z-index: 1;
inline-size: max-content;
max-inline-size: 25ch;
text-align: start;
font-size: 1rem;
font-weight: normal;
line-height: normal;
line-height: initial;
padding: var(--_p-block) var(--_p-inline);
margin: 0;
border-radius: 5px;
background: var(--_bg);
color: CanvasText;
will-change: filter;
filter:
drop-shadow(0 3px 3px hsl(0 0% 0% / var(--_shadow-alpha)))
drop-shadow(0 12px 12px hsl(0 0% 0% / var(--_shadow-alpha)));
}
/* create a stacking context for elements with > tool-tips */
:has(> tool-tip) {
position: relative;
}
/* when those parent elements have focus, hover, etc */
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
/* prepend some prose for screen readers only */
tool-tip::before {
content: "; Has tooltip: ";
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
/* tooltip shape is a pseudo element so we can cast a shadow */
tool-tip::after {
content: "";
background: var(--_bg);
position: absolute;
z-index: -1;
inset: 0;
mask: var(--_tip);
}
/* top tooltip styles */
tool-tip:is(
[tip-position="top"],
[tip-position="block-start"],
:not([tip-position]),
[tip-position="bottom"],
[tip-position="block-end"]
) {
text-align: center;
}
테마 조정
텍스트 색상은 시스템 키워드 CanvasText
를 통해 페이지에서 상속되므로 도움말에는 관리할 색상이 몇 개만 있습니다. 또한 값을 저장할 맞춤 속성을 만들었으므로 이러한 맞춤 속성만 업데이트하고 테마에서 나머지를 처리하도록 할 수 있습니다.
@media (prefers-color-scheme: light) {
tool-tip {
--_bg: white;
--_shadow-alpha: 15%;
}
}
밝은 테마의 경우 배경을 흰색으로 조정하고 불투명도를 조정하여 그림자를 훨씬 약하게 만듭니다.
오른쪽에서 왼쪽으로
오른쪽에서 왼쪽으로 읽기 모드를 지원하기 위해 맞춤 속성은 문서 방향 값을 각각 -1 또는 1 값으로 저장합니다.
tool-tip {
--isRTL: -1;
}
tool-tip:dir(rtl) {
--isRTL: 1;
}
이는 도움말 표시 위치를 지정하는 데 사용할 수 있습니다.
tool-tip[tip-position="top"]) {
--_x: calc(50% * var(--isRTL));
}
삼각형의 위치를 파악하는 데도 도움이 됩니다.
tool-tip[tip-position="right"]::after {
--_tip: var(--_left-tip);
}
tool-tip[tip-position="right"]:dir(rtl)::after {
--_tip: var(--_right-tip);
}
마지막으로 translateX()
의 논리 변환에도 사용할 수 있습니다.
--_x: calc(var(--isRTL) * -3px * -1);
도움말 위치 지정
inset-block
또는 inset-inline
속성을 사용하여 도움말을 논리적으로 배치하여 물리적 및 논리적 도움말 위치를 모두 처리합니다. 다음 코드는 4가지 위치 각각에 왼쪽에서 오른쪽 및 오른쪽에서 왼쪽 방향 모두에 맞게 스타일이 지정되는 방식을 보여줍니다.
상단 및 블록 시작 정렬
tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position])) {
inset-inline-start: 50%;
inset-block-end: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))::after {
--_tip: var(--_bottom-tip);
inset-block-end: calc(var(--_triangle-size) * -1);
border-block-end: var(--_triangle-size) solid transparent;
}
오른쪽 및 인라인 끝 정렬
tool-tip:is([tip-position="right"], [tip-position="inline-end"]) {
inset-inline-start: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
tool-tip:is([tip-position="right"], [tip-position="inline-end"])::after {
--_tip: var(--_left-tip);
inset-inline-start: calc(var(--_triangle-size) * -1);
border-inline-start: var(--_triangle-size) solid transparent;
}
tool-tip:is([tip-position="right"], [tip-position="inline-end"]):dir(rtl)::after {
--_tip: var(--_right-tip);
}
하단 및 블록 끝 정렬
tool-tip:is([tip-position="bottom"], [tip-position="block-end"]) {
inset-inline-start: 50%;
inset-block-start: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
tool-tip:is([tip-position="bottom"], [tip-position="block-end"])::after {
--_tip: var(--_top-tip);
inset-block-start: calc(var(--_triangle-size) * -1);
border-block-start: var(--_triangle-size) solid transparent;
}
왼쪽 및 인라인 시작 정렬
tool-tip:is([tip-position="left"], [tip-position="inline-start"]) {
inset-inline-end: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
tool-tip:is([tip-position="left"], [tip-position="inline-start"])::after {
--_tip: var(--_right-tip);
inset-inline-end: calc(var(--_triangle-size) * -1);
border-inline-end: var(--_triangle-size) solid transparent;
}
tool-tip:is([tip-position="left"], [tip-position="inline-start"]):dir(rtl)::after {
--_tip: var(--_left-tip);
}
애니메이션
지금까지는 도움말의 표시 여부만 전환했습니다. 이 섹션에서는 일반적으로 안전한 감소된 모션 전환이므로 먼저 모든 사용자의 불투명도에 애니메이션을 적용합니다. 그런 다음 도움말이 상위 요소에서 슬라이드 아웃되는 것처럼 보이도록 변환 위치에 애니메이션을 적용합니다.
안전하고 의미 있는 기본 전환
다음과 같이 불투명도와 변형을 전환하도록 도움말 요소의 스타일을 지정합니다.
tool-tip {
opacity: 0;
transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
transition: opacity .2s ease, transform .2s ease;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
전환에 모션 추가
툴팁이 표시될 수 있는 각 측면에서 사용자가 모션을 허용하는 경우 이동할 작은 거리를 지정하여 translateX 속성을 약간 배치합니다.
@media (prefers-reduced-motion: no-preference) {
:has(> tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_y: 3px;
}
:has(> tool-tip:is([tip-position="right"], [tip-position="inline-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_x: -3px;
}
:has(> tool-tip:is([tip-position="bottom"], [tip-position="block-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_y: -3px;
}
:has(> tool-tip:is([tip-position="left"], [tip-position="inline-start"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_x: 3px;
}
}
'in' 상태가 translateX(0)
에 있으므로 'out' 상태가 설정됩니다.
자바스크립트
제 생각에는 JavaScript는 선택사항입니다. 이는 UI에서 작업을 완료하는 데 이러한 도움말을 읽을 필요가 없기 때문입니다. 따라서 도움말이 완전히 표시되지 않더라도 큰 문제는 아닙니다. 또한 도움말을 점진적으로 개선된 것으로 간주할 수 있습니다. 결국 모든 브라우저에서 :has()
를 지원하게 되면 이 스크립트는 완전히 사라질 수 있습니다.
폴리필 스크립트는 두 가지 작업을 실행하며 브라우저가 :has()
를 지원하지 않는 경우에만 실행됩니다. 먼저 :has()
지원을 확인합니다.
if (!CSS.supports('selector(:has(*))')) {
// do work
}
그런 다음 <tool-tip>
의 상위 요소를 찾아 사용할 클래스 이름을 지정합니다.
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
}
그런 다음 이 클래스 이름을 사용하는 스타일 세트를 삽입하여 정확히 동일한 동작을 위한 :has()
선택기를 시뮬레이션합니다.
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
let styles = document.createElement('style')
styles.textContent = `
.has_tool-tip {
position: relative;
}
.has_tool-tip:is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
`
document.head.appendChild(styles)
}
이제 :has()
가 지원되지 않는 경우 모든 브라우저에서 도움말을 표시합니다.
결론
이제 제가 어떻게 했는지 알았으니 여러분은 어떻게 하시겠어요? 🙂 전환 도움말을 더 쉽게 만들 수 있는 popup
API, z-index 싸움을 피할 수 있는 top 레이어, 창에서 항목을 더 효과적으로 배치할 수 있는 anchor
API를 정말 기대하고 있습니다. 그때까지는 도움말을 작성하겠습니다.
접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다.
데모를 만들어 트윗해 주시면 아래의 커뮤니티 리믹스 섹션에 추가해 드리겠습니다.
커뮤니티 리믹스
아직 표시할 내용이 없습니다.
리소스
- GitHub의 소스 코드