-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
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>oraria-label - 현재: 검색 input에 placeholder만 있고
aria-label없음.<section>에aria-label없음. 검색 버튼에aria-label없음 - 개선:
- input에
aria-label="세션 검색" - section에
aria-label="검색 및 필터" - 검색 버튼에
aria-label="검색"
- input에
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: containin modals/drawers/sheets - 현재: 모달에서 스크롤 시 배경 페이지까지 스크롤 전파 가능
- 개선: 모달 컨테이너에
overscroll-behavior: containCSS 추가
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-numsfor 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 |
검증 방법
pnpm build— 빌드 성공 확인pnpm lint— lint 통과 확인- 키보드만으로 전체 플로우 탐색 (Tab, Enter, Escape)
- 스크린 리더(VoiceOver)로 주요 페이지 테스트
prefers-reduced-motion시뮬레이션으로 애니메이션 비활성화 확인- Lighthouse Accessibility 점수 비교
Reactions are currently unavailable