본문 바로가기

웹 접근성에 맞게 Cmarket 리팩토링하기

웹 접근성에 맞게 Cmarket 리팩토링하기

 

1. 삭제 버튼 재확인

 

기존 코드
  const handleDelete = (itemId) => {
    setCheckedItems(checkedItems.filter((el) => el !== itemId))
    dispatch(removeFromCart(itemId))
}

 

문제점
장바구니 화면에서 '삭제' 버튼을 누르면 '삭제 버튼'이라고 읽어줌과 동시에 해당 항목이 바로 삭제가 되는 문제가 발생했다.
청각장애인이 해당 서비스를 사용하는 경우, 해당 버튼의 의미를 알아보기 위해 클릭했을 때 의도치 않는 -물건이 삭제 되는- 상황이 발생할 수 있다.

 

리팩토링
const handleDelete = (itemId) => {
  if (window.confirm(`장바구니에서 삭제할까요?`)) {
    setCheckedItems(checkedItems.filter((el) => el !== itemId));
    dispatch(removeFromCart(itemId));
    alert('삭제되었습니다.');
  } else {
    alert('취소되었습니다.');
  }
};
  • 해결 : 정말로 삭제할 것인지 의사를 묻기 위한 confirm 창을 만든다.
  • 또 다른 문제 : 설치한 크롬 익스텐션 스크린 리더가 confirm 창을 읽어주지 않는다.
    (Mac 설정에서 Voice Over을 활성화하면 confirm 창을 잘 읽어준다.)


나랑 비슷한 고민을 가진 사람들이 이미 많았고 이에 대한 질문과 대답이 있어, 발췌해왔다.

 

크롬 익스텐션 스크린 리더(Chrome Vox)는 alert 및 confirm 창의 내용을 읽어주지 못 한다. 그 외 다른 스크린 리더들은 잘 읽어준다. (Voice Over나 가장 많이 사용되는 JAWS 같은 스크린 리더)

하지만 Voice Over는 맥북 내장 스크린리더라 맥북의 모든 화면 구성 요소를 읽어주기 때문에 조금 불편하고, JAWS는 유료 프로그램이라 사용하기 어려운 면이 있다.

  이런 상황에서 아래와 같은 이유로 alert, confirm 등을 사용하지 않고  role을 줄 수 있는 모달을 만들어 aria-live 속성을 사용해서 해결하는 것이 가장 바람직 할 것으로 보인다.

 이유 1 - alert, confirm 창은 브라우저 자체에서 띄우는 것이기 때문에 창을 닫기 전에는 다른 상호작용을 할 수 없다. 스크린 리더 사용자의 경우에는 큰 차이가 없을 수 있지만, 그렇지 않은 경우에는 사용자의 UX를 저해하는 대표적인 요소이기도 하다. 따라서 꼭 필요한 경우가 아니라면 alert나 confirm 대신 모달 창을 이용하는 것이 좋다.

 이유 2 - Chrome Vox와 같이 alert, confirm 창을 읽지 못하는 스크린 리더들이 존재한다. 이런 스크린 리더를 사용하는 사용자들도 있을 것을 고려해서 aria-live 속성을 사용하면 좋겠다.

 

모달을 만들고 aria-live 속성을 사용하면 사용자가 어떤 액션을 취했는지 즉시 통보받을 수 있으므로 접근성을 향상시킬 수 있다. 

 

리팩토링 결과

 

엄청나게 헤맨 끝에 모달창 완성 !!!! 스크린 리더기가 훌륭하게 읽어주고 있다.

 

삭제 버튼. 삭제 정말로 뜯어온 보도블럭을를 삭제하시겠습니까?

 

코드
 // ShoppingCart.js
 
 // 모달 상태 추가 
 let [modal, setModal] = useState( { open: false } );  
 
 // 삭제 의사를 재확인하고, 삭제하는 경우
 const handleDeleteConfirm = (itemId) => {
    setCheckedItems(checkedItems.filter((el) => el !== itemId));
    dispatch(removeFromCart(itemId));
};
// CartItem.js

import styled from 'styled-components';

const ModalOverlay = styled.div`
  .
  . (모달 컴포넌트 스타일링은 생략)
  .
  .
`;

export default function CartItem({
  item,
  checkedItems,
  handleCheckChange,
  handleQuantityChange,
  handleDeleteConfirm,
  quantity,
  modal,
  setModal
}) {

  const handleModalOpen = (itemId) => {  // '삭제' 버튼을 눌렀을 때 모달창이 뜬다.
    setModal({ open: true, itemId });    // modal에 해당 item의 id가 들어간다.
  };

  return (
  .
  .
  .
  .
  .
  // '삭제' 버튼을 누르면 handleModalOpen 함수 호출
  <button title="장바구니 아이템 삭제" className="cart-item-delete" 
          onClick={() => {handleModalOpen(item.id);} }> 삭제 </button>
          
  // 조건을 이렇게 작성해주어야, 원하는 아이템을 삭제할 수 있다.
  // 🟣 (설명1)
  {modal.itemId === item.id ?

      
      (
      <ModalOverlay>
      <ModalWrapper>
        <ModalTitle>삭제</ModalTitle>
        // 동적인 요소를 스크린 리더기가 읽어줘야 하기 때문에 aria-live 속성을 넣어주었다.
        <ModalContent aria-live='assertive'>{`정말로 ${item.name}을(를) 삭제하시겠습니까?`}</ModalContent>
        <ModalButtonWrapper>
          <ModalButton cancel onClick={() => setModal({ open: false }) }>
            취소
          </ModalButton>
          <ModalButton onClick={() => handleOk(item.id) }>
            확인
          </ModalButton>
        </ModalButtonWrapper>
      </ModalWrapper>
    </ModalOverlay> )
    
    : null}
    
    
    .
    .
    .

 

🟣 (설명 1) 적합한 모달 렌더링

 

평화로운 주말 오전을 통으로 날려버린 삽질의 원인이었다..!
아무 아이템을 '삭제'하려고 해도 모달창에 카트에 마지막으로 담은 아이템만을 삭제하는 버그가 있어서 어찌저찌 해결!!!해보았다. 

modal.itemId === item.id는 현재 모달이 열려있는 상태에서 해당 모달에 보여줄 아이템 정보가 현재 렌더링 중인 item 정보와 일치하는지를 확인하는 조건이다.

  • 즉, 현재 렌더링 중인 item이 모달에 보여지는 아이템이라면 해당 모달이 열려있는 것으로 판단하여 ModalOverlay와 ModalWrapper를 렌더링한다.
  • 이 조건이 없으면 다른 아이템의 모달이 열려있을 때도 현재 렌더링 중인 item에 대해 ModalOverlay와 ModalWrapper가 렌더링되어 모달이 중복으로 나타나는 문제가 발생할 수 있다.

 

 


 

2. 이미지 대체 텍스트

 

쇼핑몰 상품 이미지

 

기존 코드
<img className="item-img" src={item.img} alt={item.name}></img>

 

이 코드의 경우 스크린 리더는 이렇게 읽어준다.

 

 

 

 

개구리 안대, 개구리 안대, 2900, 장바구니 담기

 

 

 

 

 

 

 

  • 이미지를 충분히 설명해주는 인접 요소가 있다면 대체 텍스트로 빈 문자열을 작성하는 것이 좋다.
  • 해당 원칙에 의거하여 아래와 같이 바꿔준다.

 

중복을 피하기 위한 리팩토링
  <img className="item-img" src={item.img} alt=""></img>

 



   여기서 궁금증!


상품 아이템들에서 img에 대한 alt 속성을 빈 문자열로 두면 이미지만을 클릭했을 때 그냥 목록 항목이라고 읽는다. 이렇게 되면 사용자는 클릭한 부분이 이미지 항목이라는 것을 인지할 수 없을 것 같은데, 사용자가 항목 전체를 읽어줄 수 있는 부분을 클릭해 정보를 얻길 기대해야 하는 걸까?

 

 

 

 

 


보통 스크린 리더 사용자는 클릭보다는 키보드 제어를 통해 요소를 선택하는 경우가 많다.

이 경우 화면의 요소를 하나하나 순차적으로 읽어주게 되고, alt로 빈 문자열을 작성해준 이미지는 아예 선택조차 되지 않고 스킵해버린다. 따라서 해당 이미지를 의도적으로 클릭하여 선택하지 않는 이상 스크린 리더 사용자는 아예 이미지 요소의 존재를 인식하지 못하게 된다.

‘이미지의 존재 자체를 인지 못하는건 괜찮을까? 이미지가 있다는건 알려주는게 좋지 않을까?’ 

해당 위치에 이미지가 있다는 사실이 사용자가 페이지를 조작하는데 중요한 정보가 아닌 이상 빈 문자열로 alt 속성을 작성하는 것이 좋은 경우에는 빈 문자열로 남겨두는 것이 좋다.

(예를 들어, ‘~~ 이미지 아래에 링크가 있습니다.’ 와 같이 이미지의 존재가 특정 요소의 위치에 대한 가이드가 되는 경우 정도가 있겠지만 추천하는 작성법은 아니다. 관련 정보나 링크는 바로바로 전달해주는 것이 좋다.)

📌  참고하면 좋은 레퍼런스 :  alt 속성 작성법에 대한 공식 가이드



728x90

'FE > UI & UX' 카테고리의 다른 글

디자인 시스템은 언제 도입해야 할까?  (0) 2023.05.29
디자인 시스템  (0) 2023.05.24
웹 접근성 개선해보기  (0) 2023.04.27
웹 접근성 & 웹 콘텐츠 접근성 지침  (0) 2023.04.26
SEO  (0) 2023.04.26
⬆︎