모바일 애플리케이션 콘텐츠를 구축, 운영, 개선 및 유지 보수할 경우에 적용하는 것으로 모바일 전화기, 태블릿기기 등 모바일 기기에서 실행되는 모든 애플리케이션 및 콘텐츠를
WCAG 2.0(Web Content Accessibility Guidelines 2.0)에서 제시하고 있는 접근성 설계의 4가지 원칙 기준으로,
모바일 애플리케이션 콘텐츠의 설계 및 개발을 위한 지침을 제시하고 있다.
모든 이미지에는 대체 텍스트를 제공하여야 합니다. 이는 시각 장애인 사용자가 스크린 리더를 통해 이미지의 내용을 이해할 수 있도록 제공한다.
WCAG 2.2 Guideline : 1.1.1 비텍스트 콘텐츠(Text Alternatives)
UAAG 2.0 Reference
<!-- 기본 예시 -->
<img src="created_by02_gray.jpg" alt="마이크 앞에서 노래를 부르는 아이">
<!-- 잘못된 예시 : alt 속성과 aria-label을 중복 사용한 경우 -->
<img src="submit_button.png" alt="제출" aria-label="제출">
<!-- 올바른 예시1 : alt 속성만 사용하는 경우 (이미지) -->
<img src="submit_button.png" alt="제출">
<!-- 올바른 예시2 : aria-label 속성만 사용하는 경우 (텍스트가 없는 버튼 등) -->
<button aria-label="제출"><img src="submit_button.png" alt=""></button>
검수 방법
동영상 및 오디오 콘텐츠에는 자막, 대체 텍스트, 오디오 설명 등을 제공해야 한다.
WCAG 2.2 Guideline : 1.2.2 자막 (미디어)(Captions (Prerecorded))
WCAG 2.2 Guideline : 1.2.3 오디오 설명 또는 미디어 대체 (오디오)(Audio Description or Media Alternative (Prerecorded))
<!-- 잘못된 예시 : 영상 콘텐츠에 자막, 원고 또는 수화가 제공되지 않은 경우 -->
<video src="video.mp4" controls></video>
<!-- 올바른 예시1 : 영상 콘텐츠에 자막을 제공하는 경우 -->
<video src="video.mp4" controls>
<track kind="captions" src="captions_en.vtt" srclang="en" label="English">
</video>
<!-- 올바른 예시2 : 영상 콘텐츠에 원고를 제공하는 경우 -->
<video src="video.mp4" controls></video>
<a href="transcript.txt">영상 원고 보기</a>
<!-- 올바른 예시3 : 영상 콘텐츠에 수화 통역을 제공하는 경우 -->
<video src="video_with_sign_language.mp4" controls></video>
검수 방법
콘텐츠는 색에 관계없이 인식될 수 있어야 한다. 이는 색상에 의존하는 정보 전달을 피해야 하며, 색상만으로는 정보를 전달하지 않도록 한다.
WCAG 2.2 Guidelines : 1.4.1 색 사용(Use of Color)
<!-- 네이티브 앱 예시 (Android) - 텍스트와 아이콘을 사용하여 상태를 전달 -->
// xml code
<TextView
android:id="@+id/statusText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="오류 발생"
android:textColor="#FF0000" <!-- 색상 외에 텍스트로 오류 상태를 전달 -->
android:drawableStart="@drawable/ic_error" /> <!-- 오류 아이콘 사용 -->
// java code
Button button = findViewById(R.id.myButton);// 버튼 상태를 텍스트와 색상으로 구분
button.setText("비활성화됨");
button.setTextColor(Color.GRAY); // 비활성화된 상태를 회색 텍스트로 표시
button.setEnabled(false);
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 색상 외의 요소를 사용하여 구분 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Color Independent Recognition Example</title>
<style>
.error-message {
color: #FF0000;
font-weight: bold;
}
.success-message {
color: #008000;
font-weight: bold;
}
</style>
</head>
<body>
<p class="error-message">⚠️ 오류 발생: 입력한 데이터가 유효하지 않습니다.</p> <!-- 텍스트와 아이콘 사용 -->
<p class="success-message">✔️ 성공: 데이터가 정상적으로 저장되었습니다.</p> <!-- 텍스트와 아이콘 사용 -->
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 텍스트 없이 색상만으로 상태를 전달하여 색각 장애 사용자가 정보를 이해하기 어려움 -->
<TextView
android:id="@+id/statusText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0000" /> <!-- 텍스트 없이 색상만 사용 -->
<!-- 잘못된 예시 (하이브리드) - 색상만으로 정보를 구분하여 색각 장애 사용자가 정보를 구별하기 어려움 -->
<p style="color: #FF0000;">오류</p> <!-- 색상만으로 정보 전달 -->
<p style="color: #008000;">성공</p> <!-- 색상만으로 정보 전달 -->
<!-- 올바른 예시 - (네이티브) - 색상과 함께 텍스트 및 아이콘을 사용하여 상태를 명확하게 전달함 -->
<TextView
android:id="@+id/statusText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="오류 발생"
android:textColor="#FF0000"
android:drawableStart="@drawable/ic_error" />
<!-- 올바른 예시 (하이브리드) - 색상 외의 텍스트와 아이콘을 사용하여 정보를 전달함. -->
<p class="error-message">⚠️ 오류 발생: 입력한 데이터가 유효하지 않습니다.</p>
<p class="success-message">✔️ 성공: 데이터가 정상적으로 저장되었습니다.</p>
검수 방법
페이지에서 보이는 텍스트 콘텐츠(텍스트 및 텍스트 이미지)와 배경 간의 충분한 대비를 제공하여, 저시력장애인, 색각장애인, 고령자 등도 콘텐츠를 인식할 수 있도록 제공해야 한다.
다만, 로고, 장식목적의 콘텐츠, 마우스나 키보드를 활용하여 초점을 받았을 때 명도 대비가 커지는 콘텐츠 등은 예외로 한다.
중요 : 작업 시 많이 오류를 범하는 항목 WCAG 2.2 Guidelines : 1.4.3 명도 대비 (Contrast (Minimum))
<!-- 네이티브 앱 예시 (Android) - 명도 대비를 고려한 텍스트와 버튼 디자인 -->
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="로그인"
android:textColor="#FFFFFF" <!-- 전경색: 흰색 -->
android:background="#000000" <!-- 배경색: 검정색 -->
android:textSize="18sp" />
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="제출"
android:textColor="#FFFFFF" <!-- 전경색: 흰색 -->
android:background="#007AFF" <!-- 배경색: 파란색 -->
android:padding="16dp" />
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 명도 대비를 고려한 텍스트와 버튼 스타일 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contrast Example</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0; /* 배경색: 연회색 */
}
.button {
background-color: #007AFF; /* 배경색: 파란색 */
color: #FFFFFF; /* 전경색: 흰색 */
padding: 16px;
border: none;
font-size: 16px;
border-radius: 5px;
}
.text {
color: #000000; /* 전경색: 검정색 */
font-size: 18px;
background-color: #FFFFFF; /* 배경색: 흰색 */
padding: 10px;
}
</style>
</head>
<body>
<p class="text">로그인</p>
<button class="button">제출</button>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 텍스트와 배경색의 대비가 부족하여, 사용자가 내용을 읽기 어려움 -->
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="로그인"
android:textColor="#888888" <!-- 전경색: 회색 -->
android:background="#AAAAAA" <!-- 배경색: 연회색 -->
android:textSize="18sp" />
<!-- 잘못된 예시 (하이브리드) - 명도 대비가 낮아 텍스트가 배경에 묻혀 잘 보이지 않음. -->
<style>
.button {
background-color: #CCCCCC; /* 배경색: 연회색 */
color: #AAAAAA; /* 전경색: 밝은 회색 */
padding: 16px;
border: none;
font-size: 16px;
border-radius: 5px;
}
</style>
<p class="text" style="color: #888888; background-color: #AAAAAA;">로그인</p> <!-- 명도 대비 부족 -->
<button class="button">제출</button>
<!-- 올바른 예시 (네이티브) - 텍스트와 배경의 명도 대비를 충분히 확보하여, 텍스트가 명확하게 보이도록 함 -->
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="로그인"
android:textColor="#FFFFFF" <!-- 전경색: 흰색 -->
android:background="#000000" <!-- 배경색: 검정색 -->
android:textSize="18sp" />
<!-- 올바른 예시 (하이브리드) - 텍스트와 배경색 간의 명도 대비를 충분히 확보하여 가독성을 보장함 -->
<style>
.button {
background-color: #007AFF; /* 배경색: 파란색 */
color: #FFFFFF; /* 전경색: 흰색 */
padding: 16px;
border: none;
font-size: 16px;
border-radius: 5px;
}
</style>
<p class="text" style="color: #000000; background-color: #FFFFFF;">로그인</p>
<button class="button">제출</button>
검수 방법
사용자 인터페이스를 설계할 때, 지시사항이나 안내는 특정 감각적 특성(모양, 크기, 위치, 방향, 색, 소리 등)에 의존하지 않고 명확히 전달되어야 한다.
즉, 색상에만 의존하여 정보를 전달하는 것이 아니라 텍스트나 다른 시각적 단서도 함께 제공해야 한다.
WCAG 2.2 Guidelines : WCAG 2.2 - 레이블 또는 지시 사항 (Labels or Instructions)
<!-- 네이티브 앱 예시 (Android) - 텍스트 기반의 명확한 지시 사항 제공 -->
<TextView
android:id="@+id/instructionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="다음 단계로 진행하려면 '다음' 버튼을 누르세요."
android:textSize="16sp" />
<Button
android:id="@+id/nextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="다음"
android:contentDescription="다음 단계로 진행 버튼" /> <!-- 명확한 레이블과 설명 제공 -->
// 버튼 클릭 이벤트 처리
Button nextButton = findViewById(R.id.nextButton);
nextButton.setOnClickListener(v -> {
// 다음 단계로 진행하는 로직
});
<!-- 하이브리드 앱 예시 - 지시 사항을 텍스트로 명확하게 제공 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clear Instructions Example</title>
<style>
.instruction {
font-size: 16px;
margin-bottom: 10px;
}
.button {
background-color: #007AFF;
color: #FFFFFF;
padding: 10px 20px;
border: none;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<p class="instruction">다음 단계로 진행하려면 아래 '다음' 버튼을 클릭하세요.</p>
<button class="button" aria-label="다음 단계로 진행">다음</button> <!-- 명확한 레이블 제공 -->
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 색상이나 위치에 의존한 지시 사항 제공으로 인해 시각 장애 사용자가 지시 사항을 이해하기 어려움. -->
<TextView
android:id="@+id/instructionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="녹색 버튼을 눌러 진행하세요." /> <!-- 색상에 의존한 지시 사항 -->
<!-- 잘못된 예시 (하이브리드) - 색상이나 모양만으로 지시 사항을 제공하여, 텍스트 없이 정보가 전달되기 어려움. -->
<p style="font-size: 16px;">오른쪽 위 모서리에 있는 빨간 버튼을 누르세요.</p> <!-- 위치에 의존한 지시 사항 -->
<button style="background-color: red;">Proceed</button> <!-- 색상에 의존 -->
<!-- 올바른 예시 (네이티브) - 명확한 텍스트로 지시 사항을 제공하여, 색상이나 위치에 의존하지 않고도 지시 사항을 이해할 수 있도록 함 -->
<TextView
android:id="@+id/instructionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="다음 단계로 진행하려면 '다음' 버튼을 누르세요." />
<Button
android:id="@+id/nextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="다음"
android:contentDescription="다음 단계로 진행 버튼" />
<!-- 올바른 예시 (하이브리드) - 텍스트와 함께 명확한 레이블을 제공하여 모든 사용자가 지시 사항을 이해할 수 있도록 함. -->
<p class="instruction">다음 단계로 진행하려면 아래 '다음' 버튼을 클릭하세요.</p>
<button class="button" aria-label="다음 단계로 진행">다음</button>
검수 방법
알림 정보는 화면 표시, 소리, 진동 등 다양한 방법으로 제공되어야 한다. 이는 사용자가 놓치기 쉬운 중요한 정보를 다양한 감각을 통해 전달함으로써 접근성을 높이는 데 중요하다.
WCAG 2.2 Guidelines : 1.3.3 감각 특성 (Sensory Characteristics)
<!-- 네이티브 앱 예시 (Android) - 화면 표시, 소리, 진동을 통한 알림. 알림을 생성하는 코드 예시 -->
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channelId")
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("새 메시지")
.setContentText("새로운 메시지가 도착했습니다.")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setVibrate(new long[] { 0, 500, 1000 }) // 진동 패턴 설정
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) // 소리 알림 설정
.setAutoCancel(true); // 알림 클릭 시 자동 삭제
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(1001, builder.build()); // 알림 표시
// 버튼 클릭 이벤트 처리
Button nextButton = findViewById(R.id.nextButton);
nextButton.setOnClickListener(v -> {
// 다음 단계로 진행하는 로직
});
<!-- 하이브리드 앱 예시 - 화면 표시와 소리, 진동을 통한 알림 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Notification Example</title>
</head>
<body>
<button onclick="sendNotification()">알림 보내기</button>
<script>
function sendNotification() {
// 화면 표시 (브라우저 알림)
if (Notification.permission === "granted") {
const notification = new Notification("새 메시지", {
body: "새로운 메시지가 도착했습니다.",
icon: "notification_icon.png"
});
}
// 소리 알림
const audio = new Audio('notification_sound.mp3');
audio.play();
// 진동 알림 (모바일 기기에서 작동)
if (navigator.vibrate) {
navigator.vibrate([500, 1000, 500]); // 진동 패턴 설정
}
}
// 알림 권한 요청
if (Notification.permission !== "granted") {
Notification.requestPermission();
}
</script>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 시각적 알림만 제공되어 소리나 진동이 필요한 사용자는 알림을 놓칠 수 있음. -->
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channelId")
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("새 메시지")
.setContentText("새로운 메시지가 도착했습니다.")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true); // 시각적 알림만 제공
<!-- 잘못된 예시 (하이브리드) - 소리 알림만 제공되어 청각 장애를 가진 사용자는 알림을 인식할 수 없음. -->
<script>
function sendNotification() {
// 소리 알림만 제공
const audio = new Audio('notification_sound.mp3');
audio.play();
}
</script>
<!-- 올바른 예시 (네이티브) - 화면 표시, 소리, 진동을 함께 사용하여 모든 사용자가 알림을 인식할 수 있도록 함 -->
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channelId")
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("새 메시지")
.setContentText("새로운 메시지가 도착했습니다.")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setVibrate(new long[] { 0, 500, 1000 }) // 진동 패턴 설정
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) // 소리 알림 설정
.setAutoCancel(true); // 알림 클릭 시 자동 삭제
<!-- 올바른 예시 (하이브리드) - 화면 표시, 소리, 진동을 모두 활용하여 다양한 방법으로 알림을 제공함. -->
<script>
function sendNotification() {
// 화면 표시 (브라우저 알림)
if (Notification.permission === "granted") {
const notification = new Notification("새 메시지", {
body: "새로운 메시지가 도착했습니다.",
icon: "notification_icon.png"
});
}
// 소리 알림
const audio = new Audio('notification_sound.mp3');
audio.play();
// 진동 알림 (모바일 기기에서 작동)
if (navigator.vibrate) {
navigator.vibrate([500, 1000, 500]); // 진동 패턴 설정
}
}
// 알림 권한 요청
if (Notification.permission !== "granted") {
Notification.requestPermission();
}
</script>
검수 방법
의미나 기능을 갖는 모든 사용자 인터페이스 컴포넌트에는 초점(focus)이 적용되고, 초점은 논리적인 순서로 이동되어야 합니다. 이는 터치 스크린을 사용하는 모바일 기기에서 접근성을 높이기 위해 중요한 원칙.
중요 : 작업 시 가장 많이 오류를 범하는 항목 WCAG 2.2 Guidelines : 2.4.3 초점 순서 (Focus Order) WCAG 2.2 Guidelines : 2.4.7 초점 표시 (Focus Visible)
<!-- 네이티브 방식의 예시 -->
<!-- 초점 가능한 요소 설정 -->
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="제출"
android:contentDescription="제출 버튼" />
<!-- 명확한 초점 스타일 제공 -->
<EditText
android:id="@+id/nameInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="이름 입력"
android:background="@drawable/focused_background" />
<!-- 논리적인 초점 이동 순서 설정 -->
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/nameInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="이름 입력"
android:nextFocusDown="@id/emailInput" />
<EditText
android:id="@+id/emailInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="이메일 입력"
android:nextFocusDown="@id/submitButton" />
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="제출" />
</LinearLayout>
<!-- 하이브리드 방식의 예시 -->
<!-- 잘못된 예시 1 : 초점이 적용되지 않은 경우 -->
<div>클릭하세요</div>
<!-- 잘못된 예시 2 : 초점 스타일이 명확하지 않은 경우 -->
<a href="page.html" style="outline: none;">링크</a>
<!-- 올바른 예시 1 : 초점이 적용된 경우 -->
<button>클릭하세요</button>
<!-- 올바른 예시 2 : 명확한 초점 스타일을 제공한 경우 -->
<a href="page.html" style="outline: 2px solid #00f;">링크</a>
<!-- 올바른 예시 3 : 논리적인 초점 이동 순서를 설정한 경우 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<nav>
<a href="#content">본문으로 이동</a>
<a href="#menu">메뉴로 이동</a>
</nav>
<main id="content">
<h1>메인 콘텐츠</h1>
<p>여기에 주요 내용이 있습니다.</p>
</main>
<aside id="menu">
<h2>메뉴</h2>
<ul>
<li><a href="page1.html">페이지 1</a></li>
<li><a href="page2.html">페이지 2</a></li>
</ul>
</aside>
</body>
</html>
검수 방법
터치(touch) 기반 모바일 기기의 모든 컨트롤은 누르기 동작으로 제어할 수 있어야 한다. 이는 모바일 앱의 접근성을 높이기 위한 기본적인 원칙으로, 다양한 사용자들이 직관적이고 쉽게 인터페이스를 조작할 수 있도록 보장한다.
WCAG 2.2 Guidelines : 2.5.1 터치 조작 (Pointer Gestures))
<!-- 터치 가능한 버튼 (네이티브) -->
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="제출"
android:contentDescription="제출 버튼" />
<!-- 시각적 피드백 적용 (네이티브) -->
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="제출"
android:background="?android:attr/selectableItemBackground" />
<!-- 터치 영역 확대 (네이티브) -->
<Button
android:id="@+id/smallButton"
android:layout_width="64dp"
android:layout_height="64dp"
android:text="작은 버튼"
android:padding="16dp" />
<!-- 하이브리드 방식의 예시 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Touch Button Example</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
}
.button {
background-color: #007AFF;
color: white;
padding: 16px 32px;
text-align: center;
font-size: 16px;
border-radius: 5px;
margin: 16px;
cursor: pointer;
border: none;
transition: background-color 0.3s ease;
}
.button:active {
background-color: #005BB5; /* 시각적 피드백 */
transform: scale(0.98); /* 버튼이 눌린 효과 */
}
</style>
</head>
<body>
<button class="button" aria-label="제출 버튼">제출</button>
</body>
</html>
검수 방법
시간 제한이 있는 콘텐츠(예: 자동 로그아웃, 타임아웃 폼 제출 등)는 사용자가 응답 시간을 조절할 수 있어야 한다. 이는 모바일 앱에서 장애를 가진 사용자를 포함한 모든 사용자가 충분한 시간을 가지고 콘텐츠를 이용할 수 있도록 보장하는 중요한 접근성 원칙이다.
WCAG 2.2 Guidelines : 2.2.1 타이밍 조정(Timing Adjustable)
<!-- 네이티브 앱 예시 (Android) - 시간 연장 알림 다이얼로그 -->
new AlertDialog.Builder(this)
.setTitle("시간 연장")
.setMessage("세션이 만료됩니다. 시간을 연장하시겠습니까?")
.setPositiveButton("예", (dialog, which) -> {
// 세션 연장 로직
})
.setNegativeButton("아니요", (dialog, which) -> {
// 세션 종료 로직
})
.show();
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 시간 연장 알림 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>세션 연장 예시</title>
<style>
.dialog {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
z-index: 1000;
}
.dialog.active {
display: block;
}
</style>
</head>
<body>
<div id="timeoutDialog" class="dialog">
<p>세션이 만료됩니다. 시간을 연장하시겠습니까?</p>
<button id="extendTime">예</button>
<button id="endSession">아니요</button>
</div>
<script>
// 세션 타임아웃 시 다이얼로그 표시
setTimeout(function() {
document.getElementById('timeoutDialog').classList.add('active');
}, 5000); // 5초 후에 타임아웃 다이얼로그 표시
document.getElementById('extendTime').addEventListener('click', function() {
// 세션 연장 로직
document.getElementById('timeoutDialog').classList.remove('active');
});
document.getElementById('endSession').addEventListener('click', function() {
// 세션 종료 로직
});
</script>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 자동 로그아웃 로직만 있고, 사용자에게 시간 연장 선택권을 제공하지 않음 -->
handler.postDelayed(() -> logout(), 5000);
<!-- 잘못된 예시 (하이브리드) - 사용자에게 알림 없이 폼이 자동 제출됨 -->
<script>
setTimeout(function() {
document.getElementById('myForm').submit();
}, 5000);
</script>
<!-- 올바른 예시 (네이티브) -->
new AlertDialog.Builder(this)
.setTitle("시간 연장")
.setMessage("세션이 만료됩니다. 시간을 연장하시겠습니까?")
.setPositiveButton("예", (dialog, which) -> {
// 세션 연장 로직
})
.setNegativeButton("아니요", (dialog, which) -> {
// 세션 종료 로직
})
.show();
<!-- 올바른 예시 (하이브리드) - 타임아웃 전에 사용자에게 연장 옵션 제공 -->
<script>
setTimeout(function() {
document.getElementById('timeoutDialog').classList.add('active');
}, 5000);
</script>
검수 방법
자동으로 움직이거나 갱신되는 콘텐츠(예: 슬라이드쇼, 애니메이션, 자동 갱신 광고 등)는 사용자가 이를 제어하거나 정지할 수 있는 기능을 제공해야 한다.
이는 사용자가 콘텐츠를 충분히 읽고 이해할 수 있도록 도우며, 특정 사용자들에게 불편을 줄 수 있는 요소들을 제어할 수 있게 한다.
WCAG 2.2 Guidelines : 2.2.2 일시 정지, 정지, 숨기기(Pause, Stop, Hide)
<!-- 네이티브 앱 예시 (Android) - 자동 슬라이드쇼 제어 버튼 -->
ImageView imageView = findViewById(R.id.imageView);
Button pauseButton = findViewById(R.id.pauseButton);
Button playButton = findViewById(R.id.playButton);
pauseButton.setOnClickListener(v -> {
// 슬라이드쇼 일시 정지 로직
imageView.clearAnimation();
});
playButton.setOnClickListener(v -> {
// 슬라이드쇼 재생 로직
Animation anim = new AlphaAnimation(0.0f, 1.0f);
anim.setDuration(2000); // 2초 간격으로 슬라이드 변경
anim.setRepeatCount(Animation.INFINITE);
imageView.startAnimation(anim);
});
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 자동 슬라이드쇼 제어 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>슬라이드쇼 제어 예시</title>
<style>
.controls {
margin-top: 20px;
}
.controls button {
padding: 10px 20px;
margin-right: 10px;
}
</style>
</head>
<body>
<div id="slideshow">
<img id="slideImage" src="image1.jpg" alt="슬라이드 이미지">
</div>
<div class="controls">
<button id="pauseButton">일시 정지</button>
<button id="playButton">재생</button>
</div>
<script>
let slideshowInterval;
function startSlideshow() {
slideshowInterval = setInterval(function() {
// 슬라이드 변경 로직
const slideImage = document.getElementById('slideImage');
slideImage.src = slideImage.src === 'image1.jpg' ? 'image2.jpg' : 'image1.jpg';
}, 2000); // 2초 간격으로 슬라이드 변경
}
document.getElementById('pauseButton').addEventListener('click', function() {
clearInterval(slideshowInterval);
});
document.getElementById('playButton').addEventListener('click', function() {
startSlideshow();
});
// 초기 슬라이드쇼 시작
startSlideshow();
</script>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 슬라이드쇼가 자동으로 변경되지만 정지 또는 제어할 방법이 제공되지 않음 -->
Animation anim = new AlphaAnimation(0.0f, 1.0f);
anim.setDuration(2000); // 2초 간격으로 슬라이드 변경
anim.setRepeatCount(Animation.INFINITE);
imageView.startAnimation(anim);
<!-- 잘못된 예시 (하이브리드) - 사용자가 정지할 수 없는 자동 슬라이드쇼 -->
<!-- 하이브리드 앱 - 잘못된 예시 -->
<script>
setInterval(function() {
const slideImage = document.getElementById('slideImage');
slideImage.src = slideImage.src === 'image1.jpg' ? 'image2.jpg' : 'image1.jpg';
}, 2000);
</script>
<!-- 올바른 예시 - (네이티브) - 슬라이드쇼를 일시 정지하거나 재생할 수 있는 제어 버튼을 제공 -->
pauseButton.setOnClickListener(v -> {
// 슬라이드쇼 일시 정지 로직
imageView.clearAnimation();
});
playButton.setOnClickListener(v -> {
// 슬라이드쇼 재생 로직
Animation anim = new AlphaAnimation(0.0f, 1.0f);
anim.setDuration(2000); // 2초 간격으로 슬라이드 변경
anim.setRepeatCount(Animation.INFINITE);
imageView.startAnimation(anim);
});
<!-- 올바른 예시 (하이브리드) - 사용자가 슬라이드쇼를 정지하거나 다시 시작할 수 있도록 제어 버튼을 제공 -->
<!-- 하이브리드 앱 - 올바른 예시 -->
<script>
document.getElementById('pauseButton').addEventListener('click', function() {
clearInterval(slideshowInterval);
});
document.getElementById('playButton').addEventListener('click', function() {
startSlideshow();
});
</script>
검수 방법
모바일 기기의 터치스크린에서 컨트롤(예: 버튼, 링크, 입력 필드)은 충분한 크기와 간격을 가져야 한다.
이는 사용자가 실수로 잘못된 버튼을 누르거나 여러 컨트롤을 동시에 터치하는 것을 방지하고, 인터페이스를 쉽게 조작할 수 있도록 한다.
WCAG 2.2 Guidelines : 2.5.5 크기와 간격(Target Size)
<!-- 네이티브 앱 예시 (Android) - 충분한 크기와 간격을 가진 버튼 -->
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48dp"
android:minHeight="48dp"
android:layout_margin="16dp"
android:text="제출" />
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 충분한 크기와 간격을 가진 버튼 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Button Size Example</title>
<style>
.button {
padding: 16px 24px; /* 터치 영역을 확보 */
font-size: 18px;
margin: 16px; /* 다른 컨트롤과의 간격 유지 */
background-color: #007AFF;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<button class="button">제출</button>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 버튼의 크기가 너무 작고, 인접한 다른 컨트롤과 너무 가까이 배치되어 있어 터치하기 어려움 -->
<Button
android:id="@+id/submitButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="4dp"
android:text="제출" />
<!-- 잘못된 예시 (네이티브) - 작은 크기의 버튼이 촘촘하게 배치되어 있어 실수로 다른 버튼을 터치할 위험이 크다 -->
<button style="padding: 8px 12px; margin: 4px; font-size: 12px;">제출</button>
<!-- 올바른 예시 (네이티브) - 버튼의 크기를 충분히 키우고, 인접한 다른 컨트롤과의 간격을 확보 -->
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48dp"
android:minHeight="48dp"
android:layout_margin="16dp"
android:text="제출" />
<!-- 올바른 예시 (하이브리드) - 버튼의 크기와 간격을 충분히 확보하여 사용자들이 쉽게 터치할 수 있도록 제공 -->
<button style="padding: 16px 24px; margin: 16px; font-size: 18px;">제출</button>
검수 방법
입력 서식을 이용할 때 사용자가 잘못된 정보를 입력하지 않도록 도와주는 방법을 제공하거나, 오류 발생 시 이를 쉽게 정정할 수 있도록 안내해야 한다.
이러한 기능은 사용자들이 올바른 데이터를 입력하도록 유도하며, 특히 인지 장애가 있는 사용자들에게 매우 중요.
중요 : 작업 시 많이 오류를 범하는 항목
WCAG 2.2 Guidelines : 3.3.1 오류 식별(Error Identification) WCAG 2.2 Guidelines : 3.3.3 오류 제안(Error Suggestion)
<!-- 네이티브 앱 예시 (Android) - 실시간 입력 검증과 오류 피드백 -->
// xml code
<EditText
android:id="@+id/emailInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="이메일 입력"
android:inputType="textEmailAddress" />
<TextView
android:id="@+id/emailError"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="올바른 이메일 주소를 입력하세요."
android:textColor="#FF0000"
android:visibility="gone" />
// java code
EditText emailInput = findViewById(R.id.emailInput);
TextView emailError = findViewById(R.id.emailError);
emailInput.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!Patterns.EMAIL_ADDRESS.matcher(s).matches()) {
emailError.setVisibility(View.VISIBLE);
} else {
emailError.setVisibility(View.GONE);
}
}
@Override
public void afterTextChanged(Editable s) {}
});
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 실시간 입력 검증과 오류 피드백 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form Validation Example</title>
<style>
.error {
color: red;
display: none;
margin-top: 8px;
}
input:invalid {
border-color: red;
}
</style>
</head>
<body>
<form id="emailForm">
<label for="emailInput">이메일:</label>
<input type="email" id="emailInput" placeholder="example@domain.com" required>
<div id="emailError" class="error">올바른 이메일 주소를 입력하세요.</div>
</form>
<script>
const emailInput = document.getElementById('emailInput');
const emailError = document.getElementById('emailError');
emailInput.addEventListener('input', function() {
if (emailInput.validity.typeMismatch) {
emailError.style.display = 'block';
} else {
emailError.style.display = 'none';
}
});
</script>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 이메일 형식이 잘못되었을 때 아무런 피드백을 제공하지 않음 -->
<EditText
android:id="@+id/emailInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="이메일 입력" />
<!-- 잘못된 예시 (네이티브) - 사용자가 입력한 내용이 잘못되었을 때에도 명확한 오류 메시지를 제공하지 않음 -->
<input type="email" id="emailInput" placeholder="example@domain.com">
<!-- 올바른 예시 (네이티브) - 실시간으로 이메일 형식을 검증하고, 오류 시 사용자에게 명확한 피드백을 제공 -->
<EditText
android:id="@+id/emailInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="이메일 입력"
android:inputType="textEmailAddress" />
<TextView
android:id="@+id/emailError"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="올바른 이메일 주소를 입력하세요."
android:textColor="#FF0000"
android:visibility="gone" />
<!-- 올바른 예시 (하이브리드) - 입력된 내용이 잘못되었을 때 즉시 오류 메시지를 표시하여 사용자가 쉽게 오류를 수정할 수 있도록 제공 -->
<input type="email" id="emailInput" placeholder="example@domain.com" required>
<div id="emailError" class="error">올바른 이메일 주소를 입력하세요.</div>
검수 방법
사용자 인터페이스(UI) 컴포넌트들이 일관성 있게 배치되면 사용자는 앱을 보다 쉽게 탐색하고 이해할 수 있다.
일관성 있는 UI는 사용자가 각 요소의 위치와 동작을 예측할 수 있게 해주며, 특히 장애를 가진 사용자들에게 더욱 중요한 역할을 한다.
WCAG 2.2 Guidelines : 3.2.3 일관된 내비게이션(Consistent Navigation) WCAG 2.2 Guidelines : 3.2.4 일관된 식별자(Consistent Identification)
<!-- 네이티브 앱 예시 (Android) - 일관된 버튼 배치 -->
// xml code
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- 뒤로 가기 버튼: 모든 화면에서 왼쪽 상단에 일관되게 배치 -->
<Button
android:id="@+id/backButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="뒤로 가기"
android:layout_gravity="start" />
<!-- 주요 작업 버튼: 항상 동일한 스타일과 위치 유지 -->
<Button
android:id="@+id/mainActionButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="저장"
android:layout_marginTop="16dp" />
</LinearLayout>
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 일관된 내비게이션과 버튼 스타일-->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>일관성 있는 UI 예시</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
}
nav {
width: 100%;
background-color: #007AFF;
padding: 16px;
color: white;
text-align: center;
}
button {
padding: 16px;
margin: 16px;
background-color: #007AFF;
color: white;
border: none;
border-radius: 5px;
font-size: 18px;
cursor: pointer;
}
/* 모든 버튼에 동일한 스타일 적용 */
.primary-button {
background-color: #007AFF;
}
.secondary-button {
background-color: #005BB5;
}
</style>
</head>
<body>
<!-- 일관된 내비게이션 -->
<nav>내비게이션 바</nav>
<!-- 일관된 버튼 스타일 -->
<button class="primary-button">저장</button>
<button class="secondary-button">취소</button>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 동일한 작업을 수행하는 버튼이 각 화면마다 다른 위치에 배치되거나 스타일이 일관되지 않음 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- 동일한 기능을 하는 버튼이 다른 위치에 배치됨 -->
<Button
android:id="@+id/backButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="뒤로 가기"
android:layout_gravity="end" />
<Button
android:id="@+id/saveButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="저장"
android:layout_gravity="center" />
</LinearLayout>
<!-- 잘못된 예시 (네이티브) - 동일한 동작을 수행하는 버튼이 다른 페이지에서 서로 다른 색상과 크기로 제공됨 -->
<button style="padding: 12px; background-color: green;">저장</button>
<button style="padding: 20px; background-color: red;">저장</button>
<!-- 올바른 예시 (네이티브) - 동일한 기능을 수행하는 버튼이 모든 화면에서 일관된 위치에 배치되고, 일관된 스타일이 적용됨 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/backButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="뒤로 가기"
android:layout_gravity="start" />
<Button
android:id="@+id/saveButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="저장"
android:layout_marginTop="16dp" />
</LinearLayout>
<!-- 올바른 예시 (하이브리드) - 동일한 기능을 수행하는 버튼이 모든 페이지에서 동일한 스타일과 위치를 유지함 -->
<button class="primary-button">저장</button>
검수 방법
깜박이거나 번쩍이는 콘텐츠는 특정 사용자들에게 심각한 건강 문제를 일으킬 수 있다. 특히, 이러한 콘텐츠는 광과민성 발작을 유발할 위험이 있으며, 시각적으로 불편함을 줄 수 있다. 따라서, 깜박임의 빈도를 제한하거나, 깜박임이 없는 대체 콘텐츠를 제공해야 한다.
WCAG 2.2 Guidelines : 2.3.1 3초 이상의 깜박임 (Three Flashes or Below Threshold)
<!-- 네이티브 앱 예시 (Android) - 깜박임을 피하는 애니메이션 설정 -->
// xml code
<ImageView
android:id="@+id/flashingImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/static_image" /> <!-- 깜박이는 이미지 대신 정적 이미지 사용 -->
// java code
Animation animation = new AlphaAnimation(0.0f, 1.0f);// 깜박임을 제한하는 애니메이션 설정
animation.setDuration(1000); // 1초간의 깜박임 주기
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.REVERSE);
if (animation.getDuration() > 333) { // 깜박임 빈도가 초당 3회 이하인지 확인
imageView.startAnimation(animation);
} else {
// 깜박임이 과도한 경우 애니메이션을 사용하지 않음
imageView.setImageResource(R.drawable.static_image);
}
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 깜박임 대신 CSS 애니메이션 사용 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>깜박임 제한 예시</title>
<style>
.fading {
animation: fadeInOut 3s infinite;
}
@keyframes fadeInOut {
0%, 100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
</style>
</head>
<body>
<div class="fading">이 텍스트는 깜박이지 않고 천천히 사라집니다.</div>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 깜박임 빈도가 초당 3회 이상인 애니메이션이 사용됨. -->
// java code
Animation animation = new AlphaAnimation(0.0f, 1.0f);
animation.setDuration(100); // 0.1초간의 깜박임 주기 - 초당 10회 이상 깜박임
animation.setRepeatCount(Animation.INFINITE);
imageView.startAnimation(animation);
<!-- 잘못된 예시 (하이브리드) - 깜박임 빈도가 높은 애니메이션이 포함되어 있어 사용자가 불편을 느낄 수 있음. -->
<style>
@keyframes blink {
0%, 100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
.blinking {
animation: blink 0.5s infinite; /* 초당 2회 깜박임 */
}
</style>
<div class="blinking">이 텍스트는 깜박입니다.</div>
<!-- 올바른 예시 (네이티브) - 깜박임 대신 정적인 이미지를 사용하거나, 깜박임 빈도가 초당 3회 이하인 애니메이션을 사용 -->
Animation animation = new AlphaAnimation(0.0f, 1.0f);
animation.setDuration(1000); // 1초간의 깜박임 주기 - 초당 1회 깜박임
animation.setRepeatCount(Animation.INFINITE);
imageView.startAnimation(animation);
<!-- 올바른 예시 (하이브리드) - 깜박임 대신 천천히 사라지는 애니메이션을 사용하거나, 깜박임 빈도가 낮은 애니메이션을 적용 -->
<style>
@keyframes fadeInOut {
0%, 100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
.fading {
animation: fadeInOut 3s infinite; /* 초당 0.33회 깜박임 */
}
</style>
<div class="fading">이 텍스트는 천천히 사라집니다.</div>
검수 방법
자동으로 재생되는 배경음악이나 소리는 사용자가 원치 않는 상황에서 재생될 수 있어 불편함을 초래할 수 있으며, 특히 보조 기술을 사용하는 사용자들에게는 중요한 정보를 방해할 수 있다. 따라서, 배경음악이나 소리가 자동으로 재생되지 않도록 해야 하며, 재생을 원할 경우 사용자가 직접 제어할 수 있도록 해야 한다.
WCAG 2.2 Guidelines : 1.4.2 오디오 제어(Audio Control)
<!-- 네이티브 앱 예시 (Android) - 배경음악을 자동으로 재생하지 않도록 설정 -->
// java code
MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.background_music);// 음악 재생 컨트롤
Button playButton = findViewById(R.id.playButton);
playButton.setOnClickListener(v -> {
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
});
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 배경음악 재생을 사용자 선택으로 설정 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audio Control Example</title>
</head>
<body>
<button id="playButton">음악 재생</button>
<audio id="backgroundAudio" src="background_music.mp3"></audio>
<script>
const playButton = document.getElementById('playButton');
const backgroundAudio = document.getElementById('backgroundAudio');
playButton.addEventListener('click', () => {
if (backgroundAudio.paused) {
backgroundAudio.play();
}
});
</script>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 앱이 시작되면 사용자가 원하지 않는 상황에서도 자동으로 배경음악이 재생됨 -->
// java code
MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.background_music);// 앱이 시작되면 자동으로 배경음악 재생
mediaPlayer.start(); // 사용자의 동의 없이 자동 재생
<!-- 잘못된 예시 (하이브리드) - 페이지가 로드되자마자 자동으로 소리가 재생되며, 사용자가 이를 제어할 방법이 제공되지 않음. -->
<audio id="backgroundAudio" src="background_music.mp3" autoplay></audio>
<!-- 올바른 예시 (네이티브) - 배경음악 재생은 사용자의 클릭으로만 시작되며, 자동으로 재생되지 않음 -->
MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.background_music);
Button playButton = findViewById(R.id.playButton);
playButton.setOnClickListener(v -> {
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
});
<!-- 올바른 예시 (하이브리드) - 사용자가 클릭 후 일어날 이벤트에 대해 명확히 이해하고 동의하도록 피드백을 제공한 후 화면을 전환 -->
<button id="playButton">음악 재생</button>
<audio id="backgroundAudio" src="background_music.mp3"></audio>
검수 방법
사용자 인터페이스(UI)에서 예상치 못한 화면 전환이나 이벤트는 사용자에게 혼란을 줄 수 있다. 이러한 상황이 발생할 경우, 사용자는 이 동작의 이유와 결과를 이해할 수 있어야 하며, 예측 가능한 방식으로 제공되어야 한다.
예측 가능한 UI는 특히 시각, 청각, 인지 장애를 가진 사용자들에게 매우 중요하다.
WCAG 2.2 Guidelines : 3.2.1 온포커스(On Focus) WCAG 2.2 Guidelines : 3.2.2 온입력(On Input)
<!-- 네이티브 앱 예시 (Android) - 예상치 못한 화면 전환 시 피드백 제공 -->
// java code
Button deleteButton = findViewById(R.id.deleteButton);// 예를 들어, 사용자가 데이터 삭제 버튼을 클릭했을 때
deleteButton.setOnClickListener(v -> {
new AlertDialog.Builder(this)
.setTitle("데이터 삭제")
.setMessage("이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?")
.setPositiveButton("예", (dialog, which) -> {
// 데이터를 삭제하고 화면을 전환
deleteData();
startActivity(new Intent(this, MainActivity.class));
})
.setNegativeButton("아니요", null)
.show();
});
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 예상치 못한 페이지 전환에 대한 피드백 제공 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Predictability Example</title>
</head>
<body>
<button id="deleteButton">데이터 삭제</button>
<script>
document.getElementById('deleteButton').addEventListener('click', function() {
const userConfirmed = confirm("이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?");
if (userConfirmed) {
// 데이터 삭제 후 페이지 이동
deleteData(); // 가상의 데이터 삭제 함수
window.location.href = 'main.html';
}
});
function deleteData() {
// 데이터 삭제 로직
}
</script>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 사용자가 버튼을 클릭하자마자 별다른 경고 없이 화면이 전환되어 사용자가 혼란스러워 함 -->
// java code
Button deleteButton = findViewById(R.id.deleteButton);
deleteButton.setOnClickListener(v -> {
// 사용자의 동의 없이 데이터를 삭제하고 화면 전환
deleteData();
startActivity(new Intent(this, MainActivity.class));
});
<!-- 잘못된 예시 (하이브리드) - 페이지가 자동으로 리로드되거나 이동되어 사용자가 당황스러워 함 -->
<!-- 하이브리드 앱 - 잘못된 예시 -->
<script>
document.getElementById('deleteButton').addEventListener('click', function() {
// 사용자 확인 없이 페이지 이동
deleteData();
window.location.href = 'main.html';
});
</script>
<!-- 올바른 예시 (네이티브) - 화면 전환 전 사용자가 예상할 수 있도록 명확한 경고 메시지를 제공하여 사용자가 확인할 수 있도록 한다 -->
Button deleteButton = findViewById(R.id.deleteButton);
deleteButton.setOnClickListener(v -> {
new AlertDialog.Builder(this)
.setTitle("데이터 삭제")
.setMessage("이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?")
.setPositiveButton("예", (dialog, which) -> {
deleteData();
startActivity(new Intent(this, MainActivity.class));
})
.setNegativeButton("아니요", null)
.show();
});
<!-- 올바른 예시 (하이브리드) - 사용자가 클릭 후 일어날 이벤트에 대해 명확히 이해하고 동의하도록 피드백을 제공한 후 화면을 전환 -->
<script>
document.getElementById('deleteButton').addEventListener('click', function() {
const userConfirmed = confirm("이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?");
if (userConfirmed) {
deleteData();
window.location.href = 'main.html';
}
});
</script>
검수 방법
모바일 운영체제는 사용자가 텍스트 크기, 글꼴, 대비 등을 조정할 수 있는 기능을 제공하며, 이러한 기능을 앱 내에서 지원함으로써 사용자가 자신에게 적합한 텍스트 환경을 설정할 수 있게 해야 한다.
이를 통해 접근성을 높이고, 다양한 사용자들이 앱을 편리하게 사용할 수 있도록 도와준다.
일관성 있는 UI는 사용자가 각 요소의 위치와 동작을 예측할 수 있게 해주며, 특히 장애를 가진 사용자들에게 더욱 중요한 역할을 한다.
WCAG 2.2 Guidelines : 1.4.3 명도 대비(Contrast) WCAG 2.2 Guidelines : 1.4.4 텍스트 크기 조정(Resize Text)
<!-- 네이티브 앱 예시 (Android) - 운영체제의 폰트 설정을 따르는 텍스트 -->
// xml code
<TextView
android:id="@+id/sampleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="여기에 텍스트를 입력하세요"
android:textSize="18sp" <!-- 운영체제의 텍스트 크기 설정에 따라 크기가 조정됨 -->
android:fontFamily="sans-serif" />
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - 운영체제의 폰트 설정을 따르는 텍스트 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Font Settings Example</title>
<style>
body {
font-size: calc(1em + 1vw); /* 텍스트 크기를 반응형으로 설정하여 운영체제 설정을 따름 */
font-family: system-ui, sans-serif; /* 시스템 폰트를 사용하여 운영체제의 폰트 설정을 따름 */
}
</style>
</head>
<body>
<p>운영체제의 폰트 설정을 따르는 텍스트 예시입니다.</p>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 텍스트 크기를 고정값으로 설정하여 운영체제의 텍스트 크기 설정을 따르지 않음 -->
// xml code
<TextView
android:id="@+id/sampleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="여기에 텍스트를 입력하세요"
android:textSize="14dp" <!-- 고정된 텍스트 크기 -->
android:fontFamily="sans-serif" />
<!-- 잘못된 예시 (하이브리드) - 폰트 크기가 고정되어 있으며, 사용자가 텍스트 크기를 조절할 수 있는 옵션이 제공되지 않음 -->
<style>
body {
font-size: 14px; /* 고정된 텍스트 크기 */
font-family: Arial, sans-serif; /* 시스템 폰트가 아닌 고정 폰트 사용 */
}
</style>
<p>이 텍스트는 고정된 크기와 폰트로 설정되어 있습니다.</p>
<!-- 올바른 예시 (네이티브) - 운영체제의 텍스트 크기 설정을 따르고, 폰트 크기가 조정 가능하도록 설정함 -->
<TextView
android:id="@+id/sampleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="여기에 텍스트를 입력하세요"
android:textSize="18sp" <!-- 운영체제의 설정을 따름 -->
android:fontFamily="sans-serif" />
<!-- 올바른 예시 (하이브리드) - 반응형 텍스트 크기와 시스템 폰트를 사용하여 운영체제의 폰트 설정을 따름 -->
<style>
body {
font-size: calc(1em + 1vw); /* 반응형 텍스트 크기 */
font-family: system-ui, sans-serif; /* 시스템 폰트 사용 */
}
</style>
<p>운영체제의 폰트 설정을 따르는 텍스트 예시입니다.</p>
검수 방법
보조 기술은 시각, 청각, 신체적 장애를 가진 사용자들이 컴퓨터와 모바일 기기를 사용할 수 있도록 도와주는 장치나 소프트웨어이며, UI 컴포넌트가 이러한 보조 기술과 호환되도록 설계되면, 모든 사용자가 앱을 접근 가능하게 이용할 수 있다. 이 호환성을 보장하기 위해 UI 컴포넌트에는 적절한 접근성 속성이 적용되어야 한다.
WCAG 2.2 Guidelines : 2.1.1 키보드(Keyboard) WCAG 2.2 Guidelines : 4.1.2 이름, 역할, 값(Name, Role, Value)
<!-- 네이티브 앱 예시 (Android) - 보조 기술을 위한 접근성 속성 설정 -->
// xml code
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="제출"
android:contentDescription="폼 제출 버튼" <!-- 스크린 리더에 의해 읽혀질 텍스트 -->
android:importantForAccessibility="yes" />
// java code
Button submitButton = findViewById(R.id.submitButton);// 특정 동작에 대한 피드백 제공
submitButton.setOnClickListener(v -> {
submitButton.setContentDescription("폼이 제출되었습니다.");// 버튼 클릭 시 스크린 리더에 읽힐 메시지 설정
submitForm();// 폼 제출 로직 수행
});
<!-- 하이브리드 앱 예시 (HTML/JavaScript) - ARIA 속성을 사용하여 보조 기술 호환성 보장 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accessible Button Example</title>
</head>
<body>
<button id="submitButton" aria-label="폼 제출">제출</button>
<script>
document.getElementById('submitButton').addEventListener('click', function() {
this.setAttribute('aria-label', '폼이 제출되었습니다');// 폼 제출 후 상태를 업데이트하여 스크린 리더에 읽히도록 함
});
</script>
</body>
</html>
<!-- 잘못된 예시 (네이티브) - 버튼에 접근성 속성이 없어서 스크린 리더가 버튼의 역할을 알 수 없음 -->
// xml code
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="제출" />
<!-- 잘못된 예시 (하이브리드) - 버튼에 aria-label이 없어서 스크린 리더가 버튼의 기능을 읽을 수 없음 -->
<button id="submitButton">제출</button>
<!-- 올바른 예시 (네이티브) - 버튼에 contentDescription을 설정하여 스크린 리더가 버튼의 역할을 읽을 수 있도록 함 -->
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="제출"
android:contentDescription="폼 제출 버튼"
android:importantForAccessibility="yes" />
<!-- 올바른 예시 (하이브리드) - aria-label 속성을 사용하여 보조 기술이 버튼의 기능을 인식할 수 있도록 설정 -->
<button id="submitButton" aria-label="폼 제출">제출</button>
검수 방법