개발/CSS

[React/CSS] React 파일업로드 컴포넌트 접근성 개선하기 (tab key로 접근 후 enter key 이벤트도 가능하도록 기능 구현하기)

hayeonn 2023. 4. 5. 15:28
반응형

리액트 프로젝트에서 POST 기능을 구현하는데 파일을 업로드하는 부분도 만들어야 했다.

이 페이지는 내가 마크업을 하지 않아서 기능만 구현하고 있었다.

프로젝트 발표 후 웹접근성을 다시 고려해 리팩토링을 하고 있던 도중 문제를 발견했다.

파일업로드 부분은 tab key로 접근이 안된다는 점이다.

마크업을 살펴봤을 때 input이 display: none; 상태여서 접근이 불가능 했다.

그래서 고민을 하다가 tabIndex 를 주자 싶어서 코드를 수정했다.

코드를 수정한 부분은 div에 tabIndex 속성을 부여했다.

<div className={styles.imgBox} tabIndex={0}>

근데 여기서 tabIndex 만 주면 밑과 같이 eslint 가 경고를 한다.

이 오류를 해석했을 때 tabindex는 대화형 요소에서만 선언해야 합니다. 라고 알려주는 것이다.

잘 이해가 안되어서 링크로 들어가서 문제점을 해석할 수 있었다.

div에 tabindex를 주는 것 자체가 별로인 관점이긴 하다만... 일단 쓰려면 이런식으로 경고를 해결할 수 있다.

<div role="button" className={styles.imgBox} tabIndex={0}>

 

근데 여기서 또 두번째로 발생하는 문제가 있다.

 

tab key로 접근은 가능하지만 enter key를 눌렀을 때 이벤트가 작동하지 않는다.

어찌보면 당연한 결과다. input 태그가 숨겨져있으니 이벤트가 작동할리가 없다.

그래서 새로 enter key 이벤트를 추가해줘야 하나 고민을 하다가 하나의 조언을 얻었다!

 

div에 tabindex를 추가하고 또 거기에 이벤트를 거는 것은 너무 비효율적이며 접근성에도 맞지 않다. 

따라서, input 을 보여지게 만들고 position 속성을 이용해서 겹치게 만들어버리자!

 

이렇게도 할 수 있다는 사실을 깨닫고 바로 적용시켜봤다.

 

div에 줬던 tabindex, role 속성을 모두 지우고 css 스타일을 변경했다.

먼저 부모 요소에게 position : relative를 준다.

.imgBox {
  position: relative;
}

 

그리고 input을 이런식으로 변경한다.

.profile_form {
  appearance: none;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  color: white;
}

display 대신 appearance를 사용해준다! 

appearance

: 웹 브라우저 내장 컴포넌트 모양(appearance)을 표시하지 않도록 설정하는데 사용한다. 이 속성을 설정한 이유는 웹 브라우저마다 파일 업로드 인풋의 모양이 다를 수 있기 때문이다. 

 

그리고 position : absolute; 를 사용해서 부모 크기 위치로 조정해주고 너비, 높이값을 줘서 tab key로 접근했을 때 전체가 잡히도록 만들어 준다. 그러면 결과적으로 이렇게 나오게 된다.

이 파일선택 버튼은 어떻게 없애는걸까.. 어떤 방법을 시도해도 안되어서 구글링을 해봤다.

바로 file-selector-button 속성을 사용해주면 된다!

input[type="file"]::file-selector-button {
  border: 0;
  font-size: 0;
  background-color: transparent;
}

이 속성을 이용해서 파일 선택 버튼 스타일을 아예 안보이게 처리해줬다. 

 

다 된줄 알았는데 사진 넣으면 폰트 색상이 white라 위에 나오게 됨.. 이부분을 다시 고쳐줬다.

color 속성을 삭제했더니 이젠 또 미리보기가 없을 때 글자가 나온다 😮

다시 침착하게 이번엔 그냥 font-size 를 0으로 처리해버리자

 

 

그러면 이렇게 정상적으로 작동이 된다! 

 

접근성을 고려하기 위해서 tabindex를 div에 속성으로 추가한 짓은 바보같은 짓이였다.🥲

애초에 div에 tabindex 속성을 부여하는 것 자체가 별로고 input의 스타일을 수정해주는 방법이 옳았다.

 

+ 추가적으로 마우스로 클릭하는 경우 마우스 커서(cursor) 모양이 pointer가 되도록 CSS를 사용해 설정하면 사용자로 하여금 마우스로 클릭 가능함을 식별할 수 있다.

 

[추가 리팩토링]

1. 업로드 영역 모양이 자세히 보니까 살짝 여백이 남는다. 그래서 box-sizing 속성을 통해 거슬리는 점선을 보이지 않도록 설정했다.

.profile_label {
  box-sizing: border-box;
  overflow: hidden;
  display: block;
  width: 100px;
  height: 100px;
  border-radius: 5px;
  border: 1px dashed var(--gray-500);
}

 

2. 업로드 인풋의 선택된 파일 이름 텍스트 값을 감출 목적으로 글자 크기(font-size)를 0으로 설정하는 것은 적합하지 않다고 한다. 일부 스크린 리더에서 콘텐츠를 읽지 못할 수 있기 때문이다. 이런 경우엔 불투명도(opacity) 속성을 사용해 화면에서 감추는 것이 적절하다고 해서 font-size: 0 에서 opacity: 0으로 바꿔주었다!

.profile_form {
  opacity: 0;
  cursor: pointer;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

input[type="file"]::file-selector-button {
  opacity: 0;
}

 

3. 위의 과정(opacity: 0)으로 인해 초점이 표시되지 않는 문제가 발생한다. 이 문제는 업로드 인풋 요소 래퍼인 imgBox 요소에 :focus-within 가상 클래스를 설정해 해결할 수 있었다.

.imgBox {
  position: relative;
}

.imgBox:focus-within {
  box-shadow: 0 0 0 2px #015fcc;
  border-radius: 4px;
}