React

리액트 - 모달 구현

favor 2026. 6. 25. 16:17

모달(Modal)

✔️ 모달이란?

모달 : 기존 화면 위에 겹쳐서 나타나는 대화 상자나 팝업 창을 의미

 

✔️ 모달 vs 팝업

  • 모달 
    • 모달 창을 닫거나, 내부에서 확인 또는 취소 액션을 하기 전까지는 원래 보던 화면(부모)을 건드릴 수 X
  • 팝업
    • 팝업 : 원래 보던 화면과 완전히 독립되어 띄워지는 새로운 창
    • 팝업 창이 떠있어도 사용자는 뒤에 있는 화면을 클릭하거나 스크롤할 수 O

 

✔️ 모달 구현하기 

// src/App.tsx

import { useState } from "react";
import Modal from "./components/Modal";
import ModalContent from "./components/ModalContent";
import ModalContent2 from "./components/ModalContent2";

export default function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const toggleModal = () => setIsModalOpen((prev) => !prev);

  return (
    <div className="flex items-center justify-center min-h-screen  bg-yellow-200">
      <button
        className="border-3 border-blue-500 bg-white px-2 py-1 rounded-lg cursor-pointer"
        onClick={toggleModal}
      >
        모달 열기
      </button>
      {isModalOpen && (
        <Modal onClose={toggleModal}>
          <ModalContent />
          <ModalContent2 />
        </Modal>
      )}
    </div>
  );
}
  • const [isModalOpen, setIsModalOpen] = useState(false)
    • isModalOpen : 현재 모달의 상태를 담는 변수 
    • setIsModalOpen : 모달을 켰다 껐다할 수 있는 변경 함수 
  • const toggleModal = () => setIsModalOpen((prev) => !prev)
    • 이전 상태가 false --> !false --> true
    • 이전 상태가 true --> !true --> false
// src/components/Modal.tsx

import type { ComponentPropsWithoutRef } from "react";
import cancelIcon from "../assets/closeIcon.png";

interface ModalProps extends ComponentPropsWithoutRef<"div"> {
  onClose: () => void;
}

export default function Modal({ children, onClose, ...props }: ModalProps) {
  return (
    // 모달 창 바깥 영역
    <div
      role="presentation"
      className="fixed inset-0 z-99 flex w-full h-full items-center justify-center bg-gray-900/50 backdrop-blur-sm"
      onClick={onClose}
      {...props}
    >
      {/* 모달 영역 */}
      <section
        role="dialog"
        className="relative rounded-xl bg-[#1b1b22] px-5 py-10"
        onClick={(e) => e.stopPropagation()}
      >
        {/* close 버튼 */}
        <button
          className="absolute right-4 top-4 w-3 h-3 cursor-pointer"
          onClick={onClose}
        >
          <img
            src={cancleIcon}
            className="w-full h-full object-contain invert brightness-200"
          />
        </button>
        {children}
      </section>
    </div>
  );
}
  • extends ComponentPropsWithoutRef<"div">
    • ModalProps에는 onClose, children만 존재 --> 모달 컴포넌트 자체에 id, className 등의 속성 사용 불가능
    • <div> 태그가 가질 수 있는 모든 속성들을 가져와서 사용하겠다는 의미 
    • ㄴ ref(리액트에서 DOM을 직접 가리키는 역할)만 빼고
    • if 모달이 아니라 커스텀 버튼을 만드는 중이었다면?) <"button">
  • ({ children, onClos, ...props }: ModalProps)
    • 구조 분해
    • children, onClose만 골라 쓰고 나머지는 props로 받겠다는 의미 
  • <div role="presentation" className="fixed inset-0 z-99" onClick={onClose}>{...props}</div>
    • role="presentation" : 웹 접근성 표준 속성 --> 시각용 레이어임을 선언 
    • fixed inset-0 z-99 : 뒤의 일반 본문 글자나 이미지들이 모달 배경 위로 오지 못하도록 설정 
    • onClick={onClose} : 모달 바깥을 클릭해도 모달 창이 닫힐 수 있도록 설정
    • {...props} : 부모 컴포넌트가 모달을 부를 때 넘겨준 속성 중에서, children과 onClose를 제외한 나머지 속성(id, 추가 className 등)을 spread하여 주입하겠다는 의미 
  • <section role="dialog" onClick={(e) => e.stopPropagation()}>
    • role="dialog" : 웹 접근성 표준 속성 --> 여기서부터는 사용자가 상호작용해야 하는 영역임을 선언 
    • onClick={(e) => e.stopPropagation() : 버블링 방지 
  • <img className="object-contain invert brightness-200" alt="close" />
    • object-contain : 이미지의 가로세로 원본 비율 유지 
    • invert : 일반 텍스트가 아닌 PNG 이미지 파일이기 때문에 색상을 정반대로 반전 
    • brightness-200 : 밝기를 200% 증가 
    • alt="close" : 웹 접근성 표준 속성  --> 이미지의 정체를 알려주는 대체 텍스트 
  • {children}
    • App 컴포넌트에서 <Modal></Modal> 태그 사이에 다른 컴포넌트를 집어넣음 
    • ㄴ 해당 컴포넌트들을 children으로 묶어 Modal 컴포넌트에게 전달 

'React' 카테고리의 다른 글

리액트 애니메이션 - Framer Motion  (0) 2026.06.13
리액트 - Toggle Button 구현  (0) 2026.05.28