[React] HTML <dialog> 태그로 Modal 구현하기

유튜브에서 우연히 HTML dialog 태그 관련 영상을 보고 이번 프로젝트에 너무 적용해 보고 싶었다! 많은 시행착오가 있었지만 결국 dialog 태그를 이용해서 모달을 구현하는 데 성공했다. 구현 과정에서 React에 dialog 태그를 적용하는 자료를 많이 찾을 수 없었기 때문에 코드를 기록으로 남기려 한다.

 

HTML dialog 태그를 사용했을 때의 장점

  • backdrop을 포함한 기본적인 모달의 UI가 제공된다. (CSS 커스텀도 가능)
  • dialog에서 제공되는 showModal(), close() 등의 API를 이용하여 모달을 쉽게 구현할 수 있다.
  • esc 버튼을 클릭하여 모달을 닫을 수 있다.

 

기본적인 모달 UI 제공 (CSS 수정 방법)

backdrop과 함께 정가운데 모달이 생성된다. 만약 backdrop이나 모달 내부의 css를 수정하고 싶다면 css 파일에서 dialog 태그에 직접 접근하거나 tailwind로도 수정이 가능하다.

 

dialog {
  z-index: 100;
  padding: 1rem 0rem 3rem 3.5rem;
  border-radius: 15px;
  max-width: 90rem;
  overflow-y: hidden;
}

dialog::backdrop {
  background: rgba(0, 0, 0, 0.3);
}

globals.css 파일에서 css 적용하는 경우

 

<dialog className="max-w-[90rem] max-lg:w-[90%] pt-[1rem] pr-[0rem] pb-[3rem] pl-[3.5rem] rounded-[15px]"> 
 ...
</dialog>

tailwind를 사용하는 경우 태그에 직접 css 속성을 지정할 수도 있다.

 

CSS 수정 후 모달

 

dialog 태그 이용한 모달 열고 닫기

const CardModal = ({ isOpen, setIsOpen }: ModalProps) => {
  const dialogRef = useRef<HTMLDialogElement>(null);

  // 모달 open, close
  const openModal = () => {
    if (isOpen && dialogRef.current) {
      dialogRef.current.showModal();
    }
    if (!isOpen && dialogRef.current) {
      dialogRef.current.close();
    }
  };
  
  // 모달 open, close
  useEffect(() => {
    openModal();
  }, [isOpen]);

  return (
    <dialog
      ref={dialogRef}
      className="max-w-[90rem] max-lg:w-[90%] pt-[1rem] pr-[0rem] pb-[3rem] pl-[3.5rem] rounded-[15px]"
    >
	...
    </dialog>
  );
};

export default CardModal;

dialog 태그에 useRef를 연결하고 isOpen이라는 boolean 값을 가진 setState를 통해 모달을 열고 닫는다. useState의 의존성 배열을 이용해서 isOpen이 변경될 때마다 함수를 실행해서 dialog를 showModal() 혹은 close() 하도록 했다. 모달을 열고 닫기를 원하는 요소에 onClick={() => setIsOpen(true)} 를 추가하여 작동한다.

 

 

모달 외부 영역 클릭 시 모달 닫기

const clickOutsideModal = (e: MouseEvent) => {
    if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
      setIsOpen(false);
    }
};
  
useEffect(() => {
	document.addEventListener('mousedown', clickOutsideModal);

	return () => {
  		document.removeEventListener('mousedown', clickOutsideModal);
	};
}, [isOpen]);

return (
   <dialog ref={dialogRef} > // dialog show, close를 위한 ref
      <div ref={modalRef}> // 모달 외부 영역 클릭을 위한 ref
      ...
      </div>
   </dialog>
)

이 기능을 구현하기 위해서는 dialog 태그 하위에 div를 하나 더 생성해서 여기에 따로 useRef를 연결해야 한다. 만약 dialog 태그에 useRef를 직접 연결해서 사용한다면 dialog 태그에 뒷배경까지 모두 포함하고 있기 때문에 화면의 어느 영역을 클릭해도 dialog를 클릭하는 것으로 인식한다.

 

 


참고

https://dev.to/elsyng/react-modal-dialog-using-html-dialog-element-5afk

https://blog.webdevsimplified.com/2023-04/html-dialog/

https://nuli.navercorp.com/community/article/1133173