Disabled Button, 과연 최선일까?

뮤돔면·2025년 8월 2일
post-thumbnail

째깍악어 디자인 시스템의 버튼 컴포넌트는 사실 조금 특별합니다. 표준이라고 알려진 disabled 프로퍼티를 사용하지 않아 비활성화된 버튼에 포커스가 가능한 것입니다! 이 글에서 왜 저는 이런 무모해 보이는 선택을 했는지, 어떤 속사정과 고민들이 담겨있는지, 또 어떤 대안을 제시하고 있는지 알아보고자 합니다.

포커스가 가능한 @dotss/ui의 Disabled Button
포커스가 가능한 비활성화 버튼

누군가에게는 불편한 Disabled Button

버튼을 비활성화할 때 흔히 사용하는 disabled 프로퍼티가 모든 사용자에게 편리한 것은 아닙니다. 특히 키보드나 스크린 리더를 사용하는 사용자에게는 불편을 초래할 수 있습니다.

disabled 버튼은 키보드의 Tab 키를 통한 탐색 순서에서 완전히 제외됩니다. 따라서 키보드만으로 웹사이트를 이용하는 사용자는 해당 버튼이 존재한다는 사실 자체를 알기 어렵습니다. "이름과 비밀번호를 입력해야 로그인이 가능하다"는 정보가 있더라도, 로그인 버튼이 보이지 않으면 사용자는 다음 단계를 예측하기 힘들어지는 것입니다.

스크린 리더 사용자도 마찬가지입니다. 포커스를 통해 비활성화된 버튼에 접근할 수 없습니다. 때문에 버튼이 존재하는지 존재하지 않는지 파악조차 어렵습니다.

혹자는 스크린 리더의 탐색 모드를 통해 비활성화된 버튼에 접근할 수 있지 않느냐고 반문할 수 있습니다. 맞습니다, 가능하긴 합니다! 다만 혼란스러울 수 있습니다. 폼(form)을 채우는 아래 케이스를 떠올려봅시다!

일반적인 스크린 리더 사용자는 탐색 모드로 페이지를 훑어본 후, 상호작용이 필요한 순간에는 포커스 모드(Focus Mode)로 전환하여 Tab 키로 원하는 요소에 빠르게 접근할 수 있습니다. 그런데 disabled 처리된 버튼은 이 포커스 모드에서 건너뛰어지기 때문에 사용자는 "방금 탐색 모드에서 들었던 그 버튼이 왜 포커스 모드에서는 사라졌지?"라는 의문을 품을 수 있습니다. 이는 논리적인 흐름을 깨뜨려 사용자를 혼란스럽게 만드는 것입니다.

째깍악어의 디자인 시스템은 기본적으로 인클루시브한 디자인을 목표로 했습니다. 모두가 쉽게 사용할 수 있는 컴포넌트를 만들고자 한 것입니다. 때문에 이 일반적인 disabled 프로퍼티를 품을 수 없었습니다. 일부 사용자들, 어쩌면 더 세심한 배려가 필요한 사용자들을 배척할 수 있었기 때문입니다. 그래서 저는, 우리 FE팀은 disabled를 버리고, aria-disabled를 채택했습니다.

디자인 팀을 설득하는 게 가장 어려웠다.
Disabled 안써요 PR

더 나은 대안: aria-disabled

aria-disabled 프로퍼티는 disabled와 달리 버튼의 포커스를 막지 않으면서도 스크린 리더에게 비활성화 상태임을 알립니다. 단순히 disabled를 버리고 aria-disabled만 붙여주는 것만으로도 키보드 사용자, 스크린 리더 사용자도 쉽게 버튼 컴포넌트를 사용할 수 있는 것입니다.

째깍악어의 버튼 컴포넌트를 개략적으로 살펴봅시다!

<StyledButton
  ...
  aria-disabled={disabled}
  aria-label={getAriaLabel()} // ** iconOnly 버튼에서는 해당 아이콘의 "기본" 레이블을 읽어줍니다
  onClick={handleClick}
  onKeyDown={handleKeyDown}
  {...props}
  css={inlineCSS}
>
  ...
</StyledButton>

별거 없습니다. 어떤 복잡한 로직도 없습니다. disabled 프로퍼티 없이 aria-disabled 프로퍼티만 달아주었습니다. 외부에서 <Button disabled /> 만 넘겨버리면 aria-disabled=true가 추가되는 것입니다!

스타일링도 단순합니다. aria-disabled=true인 케이스에서만 비활성화 스타일링을 적용하면 됩니다. 단순화하면 아래와 같습니다.

&[aria-disabled="true"] {
    cursor: not-allowed;
    backgroundColor: brand.primary.disable,
    ...
};

다만 disabled와 달리 aria-disabled는 버튼에서 발생하는 클릭 이벤트, 키보드 이벤트를 막지 못하기 때문에 스크립트를 통해 꼭! 이를 막아주어야 합니다. 째깍악어의 버튼 컴포넌트에서는 내부적으로 이벤트 핸들러를 래핑하여 문제를 해결했습니다.

function handleClick(event: MouseEvent<HTMLButtonElement>) {
  if (event.currentTarget.getAttribute('aria-disabled') === 'true') {
    event.preventDefault();
    event.stopPropagation();
    return;
  }
  ...
}
  
function handleKeyDown(event: KeyboardEvent<HTMLButtonElement>) {
  if (event.currentTarget.getAttribute('aria-disabled') === 'true' && event.key === 'Enter' || event.key === ' ') {
    event.preventDefault();
    event.stopPropagation();
    return;
  }
  ...
}

규칙은 규칙일 뿐이다.

때로는 '표준'이라는 이름 아래 익숙해진 규칙들이 모두에게 최선이 아닐 때가 있습니다. disabled 속성이 오랜 기간 웹 개발의 표준으로 여겨져 왔지만 째깍악어의 FE팀은 그 규칙을 맹목적으로 따르지 않고 '모든 사용자를 포용하는 경험'이라는 본질적인 가치를 좇았습니다.

개발자에게 있어 가장 중요한 덕목 중 하나는 기술 스택을 깊이 이해하는 것이지만, 그에 못지않게 중요한 것은 우리가 만드는 결과물이 실제로 사용자에게 어떤 영향을 미치는지 끊임없이 고민하는 것이라고 생각합니다. disabled 버튼을 사용하는 것이 개발 편의성 측면에서는 더 쉬운 선택이었을지 모르겠습니다. 그러나 몇몇 사용자에게는 혼란과 소외감을 줄 수 있다는 작은 단서를 놓치지 않는 것, 그리고 그 단서를 해결하기 위해 더 나은 방법을 찾아내는 것이야말로 진정한 의미의 '잘 만든 프로덕트'를 향한 첫걸음이라는 생각이 듭니다.

aria-disabled를 채택하고 추가적인 스크립트와 스타일링을 더하는 과정은 분명 조금 더 번거로운 작업이었습니다. 하지만 이 선택은 단지 '접근성 지침'을 준수하는 것을 넘어, "우리는 모든 사용자를 생각하고 있다"라는 나의, 우리의 철학을 코드 한 줄 한 줄에 담아낸 결과라고 생각합니다. 이처럼 익숙한 규칙에 질문을 던지고 본질적인 가치를 향해 나아가는 고민이야말로 우리가 추구해야 할 프론트엔드 개발의 진정한 방향성이라고 생각합니다.


여담: 비활성화된 컴포넌트는 명암대비를 만족하지 않아도 된다?

사실 아직도 WCAG 2.2에는 비활성화된 컴포넌트의 명암 대비에 대한 규칙이 없습니다. 비활성화된 컴포넌트는 사실상 명암비를 준수할 필요가 없다고 규정되어 있는 것입니다.

Text or images of text that are part of an inactive user interface component, that are pure decoration, that are not visible to anyone, or that are part of a picture that contains significant other visual content, have no contrast requirement(WCAG 2.2, Success Criterion 1.4.3 Contrast (Minimum), 참고)

비활성화된 컴포넌트(버튼)의 정보 자체가 사용자에게 전달되지 않아도 된다는 전제가 깔려 있는 규정인데, 저는 사실 쉽게 이 규정에 동의할 수 없습니다. 비활성화된 상태는 식별되지 않아도 정말 상관없을까요? 저는 아니라고 생각합니다. 오히려 비활성화된 컴포넌트(버튼)은 '비활성화' 되어있다는 그 상태 자체가 일종의 핵심 정보이기 때문에 색약, 색맹, 저시력자 모두에게 분명히 전달될 수 있어야 합니다. 이 지점은 어쩌면 WCAG 2.2의 핵심 방향성 POURPercievability(인식성) 를 어기는 것이고, 규정 간의 충돌, 모순으로도 해석될 여지가 있다고 생각합니다.

아쉽지만 작년 말 초안이 나온 WCAG 3.0에서도 해당 규정에 대한 수정이 발생하지 않았습니다. 해당 규정에 대한 논의가 활발하게 이루어졌음에도 규정화되지 않은 것이 아쉬운 것은 사실입니다. 실제로 WCAG 공식 GitHub 이슈에서도 논의되고, 비활성화 상태의 표현 방식은 여전히 접근성 커뮤니티 내에서 중요한 주제로 다뤄지고 있었기 때문에 더 아쉽습니다(참고). 3.1에서는 이에 대한 논의가 담겨있었으면 좋겠다는 바람으로 글을 마칩니다.

profile
스크립트가 중심이 되는 프론트엔드에서 개발하고 있습니다. 서비스의 철학을 고민합니다. 배려하고 포용하는 모든 것들을 사랑합니다.

1개의 댓글

comment-user-thumbnail
2025년 10월 26일

wcag 이라면 무조건 지켜야 한다고 그냥 생각했었는데.. 이런 관점도 있군요 많이 배워갑니다

답글 달기