Skip to content

refactor: Web Interface Guidelines 기반 리팩토링 목록 #205

@tnemnorivnelee

Description

@tnemnorivnelee

Web Interface Guidelines 기반 리팩토링 목록

담당 파일들을 Web Interface Guidelines 기준으로 분석한 결과입니다.


CRITICAL (WCAG 위반 수준)

1. ProfileTabs — ARIA 탭 패턴 미적용

  • 파일: src/features/member/components/Profile/ProfileTabs.tsx
  • 규칙: Interactive elements need proper ARIA roles
  • 현재: <Link> 기반 탭 네비게이션이지만 role="tablist", role="tab", aria-selected 미적용. 스크린 리더 사용자에게 현재 활성 탭 정보가 전달되지 않음
  • 개선: 컨테이너에 role="tablist" + aria-label, 각 탭에 role="tab" + aria-selected 추가

2. ProfileSummary — 파일 업로드 키보드/터치 접근 불가

  • 파일: src/features/member/components/Profile/ProfileSummary.tsx
  • 규칙: Interactive elements need keyboard handlers; <button> for actions
  • 현재: 프로필 이미지 변경이 hover 기반 (onMouseEnter/onMouseLeave)으로만 동작. hidden file input은 키보드/터치 유저가 접근 불가
  • 개선: 키보드로 접근 가능한 편집 버튼 추가, 터치 디바이스에서도 항상 편집 버튼이 표시되도록 개선

3. SearchFilterSection — 검색 입력/버튼 aria-label 누락

  • 파일: src/features/session/components/SearchFilterSection/SearchFilterSection.tsx
  • 규칙: Form controls need <label> or aria-label
  • 현재: 검색 input에 placeholder만 있고 aria-label 없음. <section>aria-label 없음. 검색 버튼에 aria-label 없음
  • 개선:
    • input에 aria-label="세션 검색"
    • section에 aria-label="검색 및 필터"
    • 검색 버튼에 aria-label="검색"

4. StatsContent / SessionHistoryContent — 에러 시 throw (graceful UI 없음)

  • 파일: src/features/member/components/Profile/Report/StatsContent.tsx, SessionHistoryContent.tsx
  • 규칙: Handle empty states—don't render broken UI for empty strings/arrays
  • 현재: 데이터 fetch 실패 시 throw new Error()로 처리. Error Boundary가 상위에 없으면 전체 페이지 깨짐
  • 개선: Error Boundary 추가 또는 에러 fallback UI 제공

HIGH (중요)

5. 전역 — prefers-reduced-motion 미처리

  • 파일: 전체 컴포넌트 (Header, Footer, Banner, SessionDetailModal, ProfileTabs 등)
  • 규칙: Honor prefers-reduced-motion (provide reduced variant or disable)
  • 현재: transition-colors, transition-opacity, animate-[fadeIn] 등 애니메이션 사용하지만 prefers-reduced-motion 미적용
  • 개선: 글로벌 CSS에 @media (prefers-reduced-motion: reduce) 규칙 추가하거나, Tailwind의 motion-reduce: 유틸리티 활용

6. ProfileSummary — 통계 섹션 시맨틱 마크업 부재

  • 파일: src/features/member/components/Profile/ProfileSummary.tsx
  • 규칙: Use semantic HTML (<button>, <a>, <label>, <table>) before ARIA
  • 현재: 참여한 세션, 누적 시간 등 통계 데이터가 <div> 나열로만 구성. 의미 있는 시맨틱 그룹핑 없음
  • 개선: <dl>/<dt>/<dd> (정의 목록)으로 통계 항목 마크업 개선

7. ProfileDropdown — aria-modal 값 오류

  • 파일: ProfileDropdown 관련 컴포넌트
  • 규칙: ARIA attributes must have correct values
  • 현재: aria-modal="false" 설정 — 팝업(popup) 드롭다운이므로 부적절
  • 개선: aria-modal 속성 제거 (popup이므로 modal이 아님)

8. Banner — section aria-label 누락

  • 파일: src/features/session/components/Banner/Banner.tsx
  • 규칙: Semantic elements need accessible names when multiple exist on page
  • 현재: <section> 사용하지만 aria-label 또는 aria-labelledby 없음
  • 개선: aria-labelledby<h2> heading ID로 연결

MEDIUM (개선 권장)

9. 로딩 텍스트 — ellipsis 형식 통일

  • 규칙: not ... / Loading states end with
  • 현재: "확인 중..." 등 ASCII 마침표 3개(...) 사용
  • 개선: 모든 로딩 텍스트에서 ... (유니코드 ellipsis) 통일

10. 전역 — transition: all 사용 여부 점검

  • 규칙: Never transition: all — list properties explicitly
  • 현재: 대부분 transition-colors, transition-opacity 등 명시적 사용 (양호)
  • 개선: 프로젝트 전체에서 transition-all 사용 부분 grep하여 명시적 속성으로 교체

11. 모달 — overscroll-behavior 미설정

  • 파일: src/features/auth/components/LoginModal.tsx, SessionDetailModal.tsx
  • 규칙: overscroll-behavior: contain in modals/drawers/sheets
  • 현재: 모달에서 스크롤 시 배경 페이지까지 스크롤 전파 가능
  • 개선: 모달 컨테이너에 overscroll-behavior: contain CSS 추가

12. ProfileEditForm — 카테고리 최대 선택 수 스크린 리더 안내 부재

  • 파일: src/features/member/components/Profile/ProfileEditForm.tsx
  • 규칙: Async updates (toasts, validation) need aria-live="polite"
  • 현재: 카테고리 최대 3개 선택 제한이 있으나, 스크린 리더에 현재 선택 수 알림 없음
  • 개선: 선택 수 변경 시 aria-live="polite" 영역으로 현재 선택 수 / 최대 수 안내 (예: "3개 중 2개 선택됨")

13. SessionDetailModal — 중첩 모달 포커스 관리

  • 파일: src/features/session/components/SessionDetailModal/SessionDetailModal.tsx
  • 규칙: Focus management for compound controls
  • 현재: SessionJoinModal이 SessionDetailModal 위에 중첩될 때 포커스 관리가 충돌할 수 있음
  • 개선: 중첩 모달 시나리오에서 포커스 복원 로직 검증 및 개선

14. 숫자 포맷 — tabular-nums 미적용

  • 파일: src/features/member/components/Profile/ProfileSummary.tsx, StatsContent.tsx
  • 규칙: font-variant-numeric: tabular-nums for number columns/comparisons
  • 현재: 세션 수, 누적 시간 등 숫자 표시에 tabular-nums 미적용. 숫자 변동 시 레이아웃 시프트 발생 가능
  • 개선: 숫자 통계 영역에 tabular-nums 클래스 적용

15. Footer — 모바일 터치 타겟 크기 점검

  • 파일: src/components/Footer/Footer.tsx
  • 규칙: Touch targets need adequate size (최소 44x44px)
  • 현재: 하단 문구 텍스트(text-xs)가 모바일에서 작을 수 있음
  • 개선: 링크 요소의 최소 터치 타겟 44x44px 확보 점검

시맨틱 태그 점검 (Semantic HTML Audit)

파일별로 <div> 대신 적절한 시맨틱 태그를 사용해야 하는 부분을 점검한 결과입니다.

변경 필요

S1. ProfileTabs — <div><nav>

  • 파일: src/features/member/components/Profile/ProfileTabs.tsx
  • 현재: 탭 컨테이너가 <div>로 마크업
  • 개선: 탭은 페이지 내 섹션 간 네비게이션이므로 <nav>가 적절
// Before
<div className="border-border-subtle flex w-full items-center border-b-[2px]">

// After
<nav className="border-border-subtle flex w-full items-center border-b-[2px]" aria-label="프로필 탭">

S2. ProfileSummary — 통계 항목 <div>/<span><dl>/<dt>/<dd>

  • 파일: src/features/member/components/Profile/ProfileSummary.tsx
  • 현재: 참여한 세션, 누적 시간, 투두 달성률, 집중률 등 레이블-값 쌍이 <div>/<span>으로 구성
  • 개선: 레이블-값 쌍은 정의 목록(<dl>/<dt>/<dd>)이 시맨틱적으로 가장 적절
// Before (4개 통계 각각)
<div className="flex w-[88px] flex-col items-start gap-1">
  <span className="...">참여한 세션</span>
  <span className="...">5</span>
</div>

// After
<dl className="flex w-[88px] flex-col items-start gap-1">
  <dt className="...">참여한 세션</dt>
  <dd className="...">5</dd>
</dl>

S3. ProfileEditForm — 폼 섹션 <div>/<h3><fieldset>/<legend>

  • 파일: src/features/member/components/Profile/ProfileEditForm.tsx
  • 현재: "프로필 정보"와 "관심 카테고리" 두 폼 섹션이 <div> + <h3>로 구성
  • 개선: 관련 입력 그룹은 <fieldset> + <legend>가 시맨틱적으로 올바름. 스크린 리더가 폼 섹션 경계를 인식 가능
// Before
<div className="gap-2xl flex flex-col">
  <h3 className="text-text-primary text-lg font-bold">프로필 정보</h3>
  ...form inputs...
</div>

// After
<fieldset className="gap-2xl flex flex-col">
  <legend className="text-text-primary text-lg font-bold">프로필 정보</legend>
  ...form inputs...
</fieldset>
  • 참고: <fieldset>은 기본 border/padding 스타일이 있으므로 CSS 리셋 필요 (border: none; padding: 0; margin: 0;)

S4. LoginCard — 카드 컨테이너 <div><section>

  • 파일: src/features/auth/components/LoginCard.tsx
  • 현재: 로그인 카드의 최상위 컨테이너가 <div>
  • 개선: 독립적인 기능 단위(로그인 폼)이므로 <section>이 적절
// Before
<div className="border-sm border-border-gray-stronger ...">

// After
<section className="border-sm border-border-gray-stronger ...">

S5. SessionDetailModal — 모달 내 헤딩 영역 <div><header>

  • 파일: src/features/session/components/SessionDetailModal/SessionDetailModal.tsx
  • 현재: dialog 내부 제목 + 설명 영역이 <div>로 래핑
  • 개선: dialog 내 헤딩 그룹은 <header>가 시맨틱적으로 적절
// Before
<div className="gap-xs flex flex-col">
  <h2>...</h2>
  <p>...</p>
</div>

// After
<header className="gap-xs flex flex-col">
  <h2>...</h2>
  <p>...</p>
</header>

S6. StatsContent — 통계 그리드 <div><section>

  • 파일: src/features/member/components/Profile/Report/StatsContent.tsx
  • 현재: 통계 카드 그리드가 <div>로 래핑
  • 개선: 의미 있는 콘텐츠 그룹이므로 <section> + aria-label 추가
// Before
<div className="gap-lg grid grid-cols-2">

// After
<section aria-label="활동 통계" className="gap-lg grid grid-cols-2">

S7. 홈 페이지 — Suspense 래퍼 섹션에 시맨틱 추가

  • 파일: src/app/(with-header)/page.tsx
  • 현재: 각 Suspense 블록이 부모 <div> 안에 나열. 스크린 리더가 각 섹션을 구분하기 어려움
  • 개선: 주요 콘텐츠 영역에 <section> + aria-label 추가
// Before
<Suspense fallback={<RecommendedSectionSkeleton />}>
  <RecommendedSection />
</Suspense>

<Suspense fallback={<SessionListSkeleton />}>
  <SessionListPrefetch listParams={listParams} />
</Suspense>

// After
<section aria-label="맞춤 추천 세션">
  <Suspense fallback={<RecommendedSectionSkeleton />}>
    <RecommendedSection />
  </Suspense>
</section>

<section aria-label="모집 중인 세션">
  <Suspense fallback={<SessionListSkeleton />}>
    <SessionListPrefetch listParams={listParams} />
  </Suspense>
</section>

이미 양호한 부분

파일 시맨틱 태그 비고
Header.tsx <header> 글로벌 네비게이션 랜드마크
Footer.tsx <footer>, <nav>, <ul>/<li> 완벽한 시맨틱 구조
LoginPage.tsx <main> 메인 콘텐츠 랜드마크
LoginModal.tsx <dialog> HTML5 네이티브 다이얼로그
SessionDetailModal.tsx <dialog> HTML5 네이티브 다이얼로그
Banner.tsx <section>, <h2> 적절한 섹션 + 헤딩
SearchFilterSection.tsx <section>, <form> 적절한 폼 구조
with-header layout.tsx <main> 메인 콘텐츠 영역
root layout.tsx <html lang="ko">, <body> 루트 구조
ProfileEditForm.tsx <form>, <label> 폼 + 라벨 연결
ProfileSummary.tsx <h1>, <h2> 헤딩 계층 구조

요약

실행 가능한 작업 목록

# 영향도 파일 작업
1 CRITICAL ProfileTabs.tsx ARIA 탭 패턴 (role, aria-selected) 추가
2 CRITICAL ProfileSummary.tsx 파일 업로드 키보드/터치 접근성 개선
3 CRITICAL SearchFilterSection.tsx 검색 input/section/button aria-label 추가
4 CRITICAL StatsContent.tsx, SessionHistoryContent.tsx 에러 fallback UI 또는 Error Boundary 추가
5 HIGH globals.css 또는 tailwind config prefers-reduced-motion 글로벌 처리
6 HIGH ProfileSummary.tsx 통계 항목 dl/dt/dd 시맨틱 마크업
7 HIGH ProfileDropdown 관련 aria-modal 속성 제거
8 HIGH Banner.tsx section aria-labelledby 추가
9 MEDIUM 전역 로딩 텍스트 ... 통일
10 MEDIUM 전역 transition-all 사용 부분 점검/제거
11 MEDIUM LoginModal, SessionDetailModal overscroll-behavior: contain 추가
12 MEDIUM ProfileEditForm.tsx 카테고리 선택 수 aria-live 안내
13 MEDIUM SessionDetailModal.tsx 중첩 모달 포커스 관리 검증
14 MEDIUM ProfileSummary.tsx, StatsContent.tsx 숫자에 tabular-nums 적용
15 MEDIUM Footer.tsx 모바일 터치 타겟 크기 점검

시맨틱 태그 교체 작업 목록

# 파일 변경 이유
S1 ProfileTabs.tsx <div><nav> 섹션 간 네비게이션 랜드마크
S2 ProfileSummary.tsx <div>/<span><dl>/<dt>/<dd> (4개 통계) 레이블-값 쌍의 시맨틱 표현
S3 ProfileEditForm.tsx <div>/<h3><fieldset>/<legend> (2개 섹션) 폼 그룹 경계 인식
S4 LoginCard.tsx <div><section> 독립 기능 단위
S5 SessionDetailModal.tsx 헤딩 래퍼 <div><header> dialog 내 헤딩 그룹
S6 StatsContent.tsx 그리드 <div><section aria-label> 콘텐츠 그룹 랜드마크
S7 홈 page.tsx Suspense 영역에 <section aria-label> 추가 페이지 섹션 구분

이미 잘 되어 있는 부분

항목 관련 규칙 파일
Header icon button들의 aria-label Accessibility Header.tsx
Footer의 <nav> 시맨틱 구조 Semantic HTML Footer.tsx
native <dialog> 요소 사용 Semantic HTML LoginModal, SessionDetailModal
useDialog 훅의 포커스 관리 (showModal, ESC, backdrop) Focus States useDialog hook
ProfileDropdown의 aria-haspopup, aria-expanded, aria-controls Accessibility ProfileDropdown.tsx
TextInput/Textarea의 aria-invalid, aria-describedby Forms TextInput, Textarea
CategoryFilterButton의 aria-pressed Accessibility CategoryFilterButton
TextInput clear 버튼 aria-label Accessibility TextInput
캐릭터 카운트의 aria-live="polite" Forms TextInput, Textarea
ProfileEditForm의 label + htmlFor 구조 Forms ProfileEditForm.tsx

검증 방법

  1. pnpm build — 빌드 성공 확인
  2. pnpm lint — lint 통과 확인
  3. 키보드만으로 전체 플로우 탐색 (Tab, Enter, Escape)
  4. 스크린 리더(VoiceOver)로 주요 페이지 테스트
  5. prefers-reduced-motion 시뮬레이션으로 애니메이션 비활성화 확인
  6. Lighthouse Accessibility 점수 비교

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions