Skip to Content
PatternsResponsive Design

Responsive Design

반응형 디자인 구현 패턴입니다.

개요

Responsive Design 패턴은 다양한 화면 크기와 디바이스에서 최적의 사용자 경험을 제공하는 검증된 구현 방법입니다. 모바일 우선 접근, 브레이크포인트 전략, 유동적 레이아웃, 터치 최적화 등 프로덕션 환경에서 필요한 모든 반응형 시나리오를 다룹니다.

사용 사례:

  • 모바일/태블릿/데스크톱 레이아웃
  • 반응형 그리드 시스템
  • 유동적 타이포그래피
  • 터치 친화적 UI
  • 적응형 이미지

사용하지 말아야 할 때:

  • 데스크톱 전용 애플리케이션
  • 고정 크기 대시보드 (예: TV 디스플레이)
  • 프린트 전용 문서

기본 패턴

1. 모바일 우선 레이아웃

모바일부터 시작하여 점진적으로 확장하는 기본 패턴입니다.

"use client" import { Card, Button } from "@vortex/ui-foundation" export default function MobileFirstLayout() { return ( <div className="min-h-screen bg-gray-50"> {/* Header - 모든 화면 크기에서 고정 */} <header className="bg-white shadow-sm sticky top-0 z-10"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="flex justify-between items-center h-16"> <h1 className="text-xl sm:text-2xl font-bold">Logo</h1> <nav className="hidden md:flex space-x-4"> <a href="#" className="text-gray-700 hover:text-gray-900"> 링크 1 </a> <a href="#" className="text-gray-700 hover:text-gray-900"> 링크 2 </a> </nav> </div> </div> </header> {/* Main Content - 모바일: 1열, 태블릿: 2열, 데스크톱: 3열 */} <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"> {[1, 2, 3, 4, 5, 6].map((item) => ( <Card key={item} className="p-4 sm:p-6"> <h3 className="text-lg sm:text-xl font-bold mb-2">Card {item}</h3> <p className="text-sm sm:text-base text-gray-600 mb-4"> 반응형 카드 콘텐츠입니다. </p> <Button className="w-full sm:w-auto">자세히 보기</Button> </Card> ))} </div> </main> </div> ) }

2. 브레이크포인트 시스템

Tailwind CSS 브레이크포인트를 활용한 반응형 패턴입니다.

// tailwind.config.js module.exports = { theme: { screens: { sm: "640px", // Mobile landscape, small tablets md: "768px", // Tablets lg: "1024px", // Small laptops xl: "1280px", // Desktops "2xl": "1536px", // Large desktops }, }, }
"use client" export default function ResponsiveBreakpoints() { return ( <div className="p-4 sm:p-6 md:p-8 lg:p-12"> {/* 텍스트 크기 */} <h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold mb-4"> 반응형 제목 </h1> {/* 간격 */} <div className="space-y-2 sm:space-y-4 md:space-y-6"> <p className="text-sm sm:text-base md:text-lg"> 화면 크기에 따라 텍스트와 간격이 조정됩니다. </p> </div> {/* 레이아웃 전환 */} <div className="flex flex-col md:flex-row gap-4 mt-8"> <div className="flex-1 bg-blue-100 p-4 rounded"> 모바일: 세로 배치 <br /> 데스크톱: 가로 배치 </div> <div className="flex-1 bg-green-100 p-4 rounded">유동적 레이아웃</div> </div> {/* 표시/숨김 */} <div className="mt-8"> <div className="block md:hidden bg-yellow-100 p-4 rounded"> 모바일에만 표시 </div> <div className="hidden md:block bg-purple-100 p-4 rounded"> 데스크톱에만 표시 </div> </div> </div> ) }

고급 패턴

3. Container Query 활용

컨테이너 크기 기반 반응형 패턴입니다.

"use client" import { Card } from "@vortex/ui-foundation" export default function ContainerQueryPattern() { return ( <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> {/* 각 카드가 자신의 컨테이너 크기에 따라 반응 */} <div className="@container"> <Card className="p-4"> <div className="@sm:flex @sm:items-center @sm:gap-4"> <img src="/image.jpg" alt="썸네일" className="w-full @sm:w-24 h-24 object-cover rounded mb-4 @sm:mb-0" /> <div className="flex-1"> <h3 className="text-lg @sm:text-xl font-bold">카드 제목</h3> <p className="text-sm @sm:text-base text-gray-600"> Container Query로 카드 내부 레이아웃 조정 </p> </div> </div> </Card> </div> <div className="@container"> <Card className="p-4"> <div className="@sm:flex @sm:items-center @sm:gap-4"> <img src="/image2.jpg" alt="썸네일" className="w-full @sm:w-24 h-24 object-cover rounded mb-4 @sm:mb-0" /> <div className="flex-1"> <h3 className="text-lg @sm:text-xl font-bold">카드 제목 2</h3> <p className="text-sm @sm:text-base text-gray-600"> 동일한 패턴, 독립적 반응 </p> </div> </div> </Card> </div> </div> ) }

4. 유동적 타이포그래피

Clamp를 활용한 유동적 폰트 크기 패턴입니다.

// tailwind.config.js module.exports = { theme: { extend: { fontSize: { "fluid-sm": "clamp(0.875rem, 0.8rem + 0.4vw, 1rem)", // 14-16px "fluid-base": "clamp(1rem, 0.9rem + 0.5vw, 1.125rem)", // 16-18px "fluid-lg": "clamp(1.125rem, 1rem + 0.625vw, 1.5rem)", // 18-24px "fluid-xl": "clamp(1.25rem, 1rem + 1.25vw, 2rem)", // 20-32px "fluid-2xl": "clamp(1.5rem, 1rem + 2.5vw, 3rem)", // 24-48px }, }, }, }
"use client" export default function FluidTypography() { return ( <div className="p-8"> <h1 className="text-fluid-2xl font-bold mb-4">유동적 제목</h1> <h2 className="text-fluid-xl font-semibold mb-3">유동적 부제목</h2> <p className="text-fluid-base text-gray-700 leading-relaxed"> 이 텍스트는 화면 크기에 따라 부드럽게 크기가 조정됩니다. 브레이크포인트 없이 자연스러운 확대/축소가 이루어집니다. </p> <p className="text-fluid-sm text-gray-500 mt-4"> 작은 텍스트도 동일한 패턴 적용 </p> </div> ) }

5. 터치 최적화 UI

모바일 터치에 최적화된 인터랙션 패턴입니다.

"use client" import { useState } from "react" import { Button } from "@vortex/ui-foundation" export default function TouchOptimizedUI() { const [activeTab, setActiveTab] = useState(0) const [startX, setStartX] = useState(0) const handleTouchStart = (e: React.TouchEvent) => { setStartX(e.touches[0].clientX) } const handleTouchEnd = (e: React.TouchEvent) => { const endX = e.changedTouches[0].clientX const diff = startX - endX // 스와이프 감지 (최소 50px 이동) if (Math.abs(diff) > 50) { if (diff > 0 && activeTab < 2) { // 왼쪽 스와이프 (다음 탭) setActiveTab(activeTab + 1) } else if (diff < 0 && activeTab > 0) { // 오른쪽 스와이프 (이전 탭) setActiveTab(activeTab - 1) } } } return ( <div className="max-w-md mx-auto"> {/* 터치 친화적 버튼 (최소 44x44px) */} <div className="grid grid-cols-3 gap-2 mb-6"> <Button className="h-12 text-lg">버튼 1</Button> <Button className="h-12 text-lg">버튼 2</Button> <Button className="h-12 text-lg">버튼 3</Button> </div> {/* 스와이프 가능한 탭 */} <div className="overflow-hidden" onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd} > <div className="flex transition-transform duration-300" style={{ transform: `translateX(-${activeTab * 100}%)` }} > {[0, 1, 2].map((index) => ( <div key={index} className="w-full shrink-0 p-8 bg-gray-100 rounded-lg" > <h3 className="text-2xl font-bold mb-4">탭 {index + 1}</h3> <p className="text-gray-600">좌우로 스와이프하세요</p> </div> ))} </div> </div> {/* 탭 인디케이터 */} <div className="flex justify-center gap-2 mt-4"> {[0, 1, 2].map((index) => ( <button key={index} onClick={() => setActiveTab(index)} className={`w-2 h-2 rounded-full transition-colors ${ activeTab === index ? "bg-blue-600" : "bg-gray-300" }`} aria-label={`탭 ${index + 1}로 이동`} /> ))} </div> </div> ) }

6. 반응형 이미지

다양한 화면에 최적화된 이미지 로딩 패턴입니다.

"use client" import Image from "next/image" export default function ResponsiveImages() { return ( <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> {/* Next.js Image - 자동 최적화 */} <div className="relative aspect-video"> <Image src="/hero.jpg" alt="히어로 이미지" fill className="object-cover rounded-lg" sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" /> </div> {/* Picture 태그 - 아트 디렉션 */} <picture> <source media="(min-width: 1024px)" srcSet="/image-desktop.jpg" /> <source media="(min-width: 640px)" srcSet="/image-tablet.jpg" /> <img src="/image-mobile.jpg" alt="반응형 이미지" className="w-full h-auto rounded-lg" /> </picture> {/* SVG - 벡터 이미지 */} <div className="bg-linear-to-br from-blue-500 to-purple-600 aspect-video rounded-lg flex items-center justify-center"> <svg className="w-1/2 h-1/2 text-white" fill="currentColor" viewBox="0 0 24 24" > <path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z" /> </svg> </div> </div> ) }

Best Practices

✅ 권장 사항

  1. 모바일 우선 (Mobile First)

    • 작은 화면부터 디자인
    • sm:, md:, lg: 순서로 확장
    • 불필요한 요소 제거 (Progressive Enhancement)
  2. 터치 타겟 크기

    • 최소 44x44px (Apple HIG)
    • 버튼 간 충분한 간격 (8px 이상)
    • 스와이프 제스처 지원
  3. 성능 최적화

    • 이미지 lazy loading
    • 적절한 이미지 크기 (sizes 속성)
    • Critical CSS 인라인
    • 불필요한 JS 제거
  4. 접근성

    • 텍스트 대비 4.5:1 이상
    • 확대 가능한 텍스트
    • 키보드 네비게이션
    • 스크린 리더 지원
  5. 테스트

    • 실제 디바이스 테스트
    • Chrome DevTools 디바이스 모드
    • 가로/세로 모드 모두 확인
    • 느린 네트워크 시뮬레이션

⚠️ 피해야 할 것

  1. UX 문제

    • 데스크톱 UI를 모바일에 그대로 축소
    • 작은 터치 타겟 (44px 미만)
    • 가로 스크롤 발생
    • 뷰포트 너비 고정 (width=device-width 필수)
  2. 성능 문제

    • 모바일에 큰 이미지 로드
    • 불필요한 애니메이션
    • 과도한 JS 번들
    • 서버 사이드 렌더링 없음
  3. 접근성 문제

    • 확대 불가능 (user-scalable=no)
    • 낮은 색상 대비
    • 키보드 네비게이션 불가

성능 고려사항

Critical CSS

<!-- 초기 렌더링에 필요한 CSS만 인라인 --> <style> .hero { min-height: 100vh; } .nav { position: sticky; top: 0; } </style> <!-- 나머지 CSS는 비동기 로드 --> <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'" />

Image Optimization

// Next.js Image 최적화 <Image src="/hero.jpg" alt="Hero" width={1920} height={1080} priority // LCP 이미지는 우선 로드 quality={85} // 품질 조정 (75-90 권장) />

Foundation 예제

범용 반응형 레이아웃

Foundation 컴포넌트로 구현한 중립적인 반응형 레이아웃입니다.

import { Card, Button } from "@vortex/ui-foundation" export default function FoundationResponsive() { return ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4"> {[1, 2, 3].map((i) => ( <Card key={i} className="p-6"> <h3 className="text-lg md:text-xl font-bold mb-2">Card {i}</h3> <p className="text-sm md:text-base mb-4">반응형 카드</p> <Button className="w-full md:w-auto">더보기</Button> </Card> ))} </div> ) }

전체 예제 보기 →


iCignal 예제

Analytics 반응형 대시보드

iCignal Blue 브랜드를 적용한 반응형 대시보드입니다.

import "@vortex/ui-icignal/theme" import { Card } from "@vortex/ui-icignal" export default function ISignalDashboard() { return ( <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 p-4"> <Card className="p-4 border-blue-500"> <h3 className="text-sm text-gray-600">방문자</h3> <p className="text-2xl sm:text-3xl font-bold text-blue-600">1,234</p> </Card> <Card className="p-4 border-green-500"> <h3 className="text-sm text-gray-600">전환율</h3> <p className="text-2xl sm:text-3xl font-bold text-green-600">12.3%</p> </Card> </div> ) }

전체 예제 보기 →


Cals 예제

예약 시스템 반응형 카드

Cals Pink 브랜드를 적용한 반응형 예약 카드입니다.

import "@vortex/ui-cals/theme" import { Card, Badge, Button } from "@vortex/ui-cals" export default function CalsBookingCard() { return ( <Card className="p-4 border-pink-500"> <div className="flex flex-col sm:flex-row sm:items-center gap-4"> <div className="flex-1"> <div className="flex flex-col sm:flex-row sm:items-center gap-2 mb-2"> <h3 className="text-lg font-bold">김예약 고객님</h3> <Badge variant="confirmed">확정</Badge> </div> <p className="text-sm text-gray-600">2024-01-15 10:00</p> </div> <Button variant="primary" className="bg-pink-500 w-full sm:w-auto"> 상세보기 </Button> </div> </Card> ) }

전체 예제 보기 →


CodeSandbox

CodeSandbox 예제는 곧 제공될 예정입니다.

로컬에서 실행하기

  1. 프로젝트 생성

    npx @vortex/cli init my-responsive-project --template next-app cd my-responsive-project
  2. 컴포넌트 추가

    # Foundation npx @vortex/cli add card button --package foundation # iCignal npx @vortex/cli add card --package icignal # Cals npx @vortex/cli add card badge button --package cals
  3. 코드 복사 및 실행

    pnpm dev
  4. 반응형 테스트

    • Chrome DevTools (F12) → Device Toolbar (Ctrl+Shift+M)
    • 다양한 디바이스 프리셋 테스트

관련 패턴

Last updated on