디자인 시스템에 Dialog 가 있는데 CallConfirmationDialog 를 직접 만들었다. Badge 가 있는데 InquiryStatusBadge 를 직접 만들었다.

안 A   있는 건 무조건 가져다 쓴다
안 B   필요하면 직접 만든다. 기준은 그때그때 판단한다

둘 다 답이 아니다. 하나는 10줄짜리 뱃지에도 4,000줄 번들을 끌어오게 하고, 다른 하나는 판단을 매번 새로 하게 만든다. 그리고 매번 새로 하다가 이번에 하나를 빠뜨렸다.


먼저: 50개 중 3개만 쓰고 있다

이건 원칙이 아니라 이 표준을 만든 실측이다.

design-system 이 주는 것    50 개
실제로 import 하는 것        3 개

Button      13 회
Icon        10 회
IconButton   1 회
────────────────────
나머지 47개   0 회

Dialog · Badge · Card · Carousel · TextField · DatePicker 가 전부 안 쓰인다. 대신 비슷한 것을 직접 만들었다.

CallConfirmationDialog · InquiryPasswordDialog   ←  Dialog 가 있다
InquiryStatusBadge                               ←  Badge 가 있다
ReviewCard                                       ←  Card 가 있다
ReviewImageSlider                                ←  Carousel 이 있다

왜 그랬는지 어디에도 안 적혀 있다. 몰랐는지, 안 맞았는지, 판단이 있었는지 알 수 없다.


먼저: 그러다 하나를 빠뜨렸다

문의 비밀번호를 묻는 팝업을 직접 만들면서 이것들을 손으로 처리했다.

ESC 로 닫기                      ✓
뒤 페이지 스크롤 잠그기            ✓
열리면 입력칸에 커서 두기          ✓
role="dialog" · aria-modal 붙이기  ✓
정리(cleanup)                    ✓
────────────────────────────────
Tab 이 팝업 밖으로 못 나가게 가두기  ✗

role="dialog"는 이 요소가 보통의 페이지 글이 아니라 떠 있는 팝업이라고 화면 읽기 도구에 알려주는 HTML 속성이다. 정리(cleanup)는 팝업을 열 때 걸어 둔 이벤트 리스너나 타이머를 닫을 때 다시 떼어내는 작업이다 — 안 떼면 팝업이 사라진 뒤에도 그 코드가 남아 엉뚱한 시점에 실행된다.

aria-modal="true" 는 화면 읽기 도구에만 「이 밖은 없는 셈 쳐라」라고 알린다. 키보드 Tab 이동은 안 막는다 — 그건 JS 로 가둬야 한다.

그래서 팝업이 떠 있는데 Tab 을 누르면 뒤 페이지의 링크로 커서가 옮겨간다. 키보드로만 쓰는 사람은 팝업에서 빠져나와 길을 잃는다.

마우스로 쓰면 아무 문제가 없다. 화면도 멀쩡하다. 그래서 빠뜨렸는지도 모른다.

그리고 같은 코드가 두 파일에 복제돼 있다. ESC · 첫 포커스 · cleanup 이 CallConfirmationDialog(48줄)와 InquiryPasswordDialog(76줄)에 각각 있고, 비밀번호 팝업이 call-modal CSS 클래스를 빌려 쓴다(여기서 클래스는 지금까지 봐온 자바의 클래스가 아니라, 화면 요소에 스타일을 입히려고 붙이는 이름이다) — 이름이 안 맞는데 스타일이 필요해서다.


원칙 1. 「직접 만들면 무엇을 다시 만들어야 하나」로 가른다

무엇을 기준으로 고르는가.

안 A — 디자인 시스템에 있으면 무조건 쓴다. 판단이 필요 없다. 버린 이유는 10줄짜리에도 그게 적용된다는 것이다. InquiryStatusBadge 는 아이콘 하나에 글자 하나가 전부다.

<span className={...}>
  {answered ? <CheckIcon /> : <ClockIcon />}
  {status}
</span>

한 줄씩 따라가기. 이 코드는 JSX 문법이다 — HTML과 비슷한 태그 안에 자바스크립트 값을 { }로 섞어 쓴다. <span className={...}>는 뱃지를 감싸는 태그이고, 보여줄 색이 상태마다 달라지므로 className 자리를 {...}로 비워 뒀다. 그 안의 {answered ? <CheckIcon /> : <ClockIcon />}는 answered가 참이면 체크 아이콘을, 아니면 시계 아이콘을 보여주는 조건문이고, {status}는 상태를 나타내는 글자를 그대로 넣는다.

여기엔 빠뜨릴 것이 없다. 키보드로 누를 것도 포커스가 갈 곳도 없다. 4,429줄 번들에서 Badge 를 끌어오는 것이 오히려 비싸다.

안 B — 필요할 때 직접 만든다. 기준은 그때 판단한다. 지금 방식이다. 버린 이유는 판단이 매번 새로 이뤄진다는 것이고, 실제로 그러다 focus trap 을 빠뜨렸다.

골랐다 — 안 C, 「직접 만들면 무엇을 다시 만들어야 하는가」로 묻는다.

다시 만들 게 없다   →  직접 만든다      뱃지 · 카드 · 구분선처럼 글자와 색만 있는 것
다시 만들 게 있다   →  가져다 쓴다      팝업 · 선택 상자 · 날짜 고르기처럼 키보드와 포커스가 얽힌 것

안 A 와 안 B 가 못 지켜주는 것은 판단이 매번 같은 답을 내는 것이다. 「이거 복잡한가」는 사람마다 다르지만 「Tab 을 가둬야 하나」는 예/아니오로 답한다.


원칙 2. 「다시 만들 것」의 목록을 못 박는다

원칙 1이 서려면 「다시 만들 것」이 무엇인지 알아야 한다. 그런데 그것들은 눈에 안 보여서 그 자리에서 생각이 안 난다.

ESC 로 닫기
Tab 을 그 안에 가두기 (focus trap)
열릴 때 어디에 커서를 둘지
닫힐 때 원래 자리로 커서 되돌리기
바깥을 눌렀을 때 닫기
화면 읽기 도구가 읽을 역할과 이름
목록에서 위아래 키로 이동하기
열려 있는 동안 뒤 페이지 스크롤 잠그기

이 중 하나라도 해당하면 가져다 쓴다.

목록이 있으면 「이 중에 해당하는 게 있나」로 물을 수 있다. 없으면 매번 처음부터 떠올려야 하고, 그러다 이번처럼 하나가 빠진다. 빠뜨려도 화면은 멀쩡해서 아무도 안 알려준다.

대가는 목록이 낡을 수 있다는 것이다. 새로운 접근성 요구가 생기면 여기에 더해야 한다. 그래도 목록이 없는 것보다는 낫다 — 없으면 아예 안 떠오른다.


원칙 3. 직접 만들었으면 왜 그랬는지 그 파일에 적는다

원칙 1로 골랐어도 다음 사람은 그 판단을 못 본다.

지금 CallConfirmationDialog 에는 왜 직접 만들었는지가 한 줄도 없다. 그래서 다음에 비슷한 팝업을 만들 때 같은 고민을 처음부터 한다.

반대로 admin 의 AdminSelect 에는 적혀 있다.

기본 select 를 쓰지 않는 이유는 하나입니다 — 펼쳐진 목록은 CSS 로 바꿀 수 없습니다. 그 목록은 브라우저가 페이지 밖에서 OS 위젯으로 그리고, option 에 준 색·모서리·글꼴은 대부분 무시됩니다. … 대신 기본 select 가 공짜로 주던 것들을 여기서 다시 만들어야 합니다 — 키보드 이동, 바깥을 눌러 닫기, 화면 읽기 도구가 읽을 역할 이름입니다. 그 셋이 이 파일 분량의 대부분입니다.

여기서 OS 위젯은 브라우저가 아니라 운영체제가 자체 메뉴창처럼 그려서 화면에 띄우는 조각을 뜻한다 — 웹페이지의 HTML·CSS가 미치지 못하는 영역이라 색이나 모양을 못 바꾼다.

이 문장이 이 표준의 기준을 준 것이다. 같은 판단을 이미 했는데 한 파일의 주석으로만 남아 규칙이 되지 못했다.

적을 것은 둘이다.

왜 가져다 쓰지 않았나        예: 펼친 목록을 CSS 로 못 바꾼다
무엇을 다시 만들어야 했나     예: 키보드 이동 · 바깥 클릭 · 역할 이름

둘째가 특히 중요하다. 나중에 그 컴포넌트를 고칠 때 무엇을 안 깨뜨려야 하는지가 거기 적혀 있다.


판단 기준 정리

질문 답 결론
무엇으로 가르나 직접 만들면 무엇을 다시 만들어야 하나 「복잡한가」는 사람마다 다르지만 「Tab 을 가둬야 하나」는 예/아니오다
글자와 색만 있다 직접 만든다 10줄짜리 뱃지에 4,000줄 번들을 끌어오지 않는다
키보드·포커스가 얽힌다 가져다 쓴다 빠뜨려도 화면은 멀쩡해서 아무도 안 알려준다
목록에 없는 새 요구가 있다 목록에 더한다 낡는 것보다 없는 게 나쁘다
직접 만들기로 했다 그 파일에 이유를 적는다 왜 안 썼는지 + 무엇을 다시 만들었는지
이유를 안 적으면 다음 사람이 같은 고민을 처음부터 한다 실제로 CallConfirmationDialog 가 그 상태다

이 표준을 정하기까지

세어 보니 50개 중 3개만 쓰고 있었다. Button · Icon · IconButton 이 전부고 나머지 47개는 한 번도 안 쓴다. 그런데 직접 만든 것들 중에 Dialog · Badge · Card · Carousel 과 겹치는 것이 여럿이었다. 있는데 안 쓰고 만든 것이다.

왜 그랬는지 찾으러 파일을 열었는데 이유가 없었다. 이 프로젝트는 주석에 근거를 잘 적는 편인데, CallConfirmationDialog 에도 InquiryStatusBadge 에도 왜 직접 만들었는지가 한 줄도 없었다. 몰랐는지 판단이 있었는지 알 수가 없었다.

대신 두 파일의 성격이 정반대라는 게 보였다. InquiryStatusBadge 는 10줄이고 아이콘 하나에 글자 하나가 전부다. CallConfirmationDialog 는 포커스와 스크롤 잠금과 ESC 를 손으로 짰다. 하나는 직접 만드는 게 맞고 하나는 아니었다.

그래서 무엇이 빠졌나 보다가 focus trap 을 찾았다. ESC 도 있고 첫 포커스도 있고 aria-modal 도 있는데 Tab 을 가두는 것만 없었다. aria-modal 은 화면 읽기 도구에만 알리지 키보드를 막지 않는다. 마우스로 쓰면 아무 문제가 없어서 빠뜨렸는지도 모르는 상태였다.

기준은 이미 코드에 적혀 있었다. admin 의 AdminSelect 주석이 「기본 select 가 공짜로 주던 것들을 여기서 다시 만들어야 합니다」라고 적고 그 목록까지 나열해 뒀다. 같은 판단을 이미 한 것이고, 한 파일의 주석으로만 남아 규칙이 되지 못했을 뿐이다. 이 저장소의 표준이 대부분 그렇게 나온다 — 이미 하고 있는데 말로 꺼내본 적이 없는 것.

목록을 못 박은 것은 「눈에 안 보인다」가 이유다. 「복잡한 컴포넌트는 가져다 쓴다」로 적으면 복잡의 기준에서 또 갈린다. 그런데 「Tab 을 가둬야 하나」 · 「닫을 때 커서를 되돌려야 하나」는 예/아니오로 답한다. 목록이 판단을 검문으로 바꾼다.


정리

  • 「직접 만들면 무엇을 다시 만들어야 하나」로 가른다. 「복잡한가」는 사람마다 다르지만 이건 예/아니오다.
  • 글자와 색만 있으면 직접 만든다. 10줄짜리 뱃지에 4,000줄 번들을 끌어오지 않는다.
  • 키보드·포커스가 얽히면 가져다 쓴다. 빠뜨려도 화면은 멀쩡해서 아무도 안 알려준다.
  • 「다시 만들 것」 목록을 못 박는다. 눈에 안 보이는 것들이라 그 자리에서는 생각이 안 난다.
  • 직접 만들었으면 왜 그랬는지와 무엇을 다시 만들었는지를 그 파일에 적는다. 안 적으면 다음 사람이 같은 고민을 처음부터 한다.

자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기