WCAG 성공 기준 · Level A

WCAG 3.3.1: 오류 식별

WCAG 3.3.1은 입력 오류가 자동으로 감지될 때, 오류가 있는 항목을 식별하고 그 오류를 사용자에게 텍스트로 설명할 것을 요구합니다. 이는 장애가 있는 사용자가 양식을 작성할 때 실수를 인지하고 이해하며 수정할 수 있도록 보장합니다.

이 규칙의 의미

WCAG 3.3.1 오류 식별(Error Identification)은 이해 가능(Understandable) 원칙 아래의 레벨 A 성공 기준이다. 이 기준은 다음과 같이 규정한다. "입력 오류가 자동으로 감지되는 경우, 오류가 있는 항목이 식별되고 그 오류가 사용자에게 텍스트로 설명되어야 한다." 실무적으로 말하면, 웹사이트나 애플리케이션이 사용자 입력을 자동으로 검증할 때 — 폼 제출 시든, 필드 포커스 아웃(blur) 시든, 실시간이든 — 오류가 감지되면 두 가지가 반드시 이루어져야 한다. 오류가 포함된 구체적인 필드나 컨트롤이 명확히 식별되어야 하고, 오류의 성격이 실제 텍스트 콘텐츠(색, 아이콘, 소리만이 아니라)를 사용해 전달되어야 한다.

이 기준은 사용자로부터 입력을 수집하고 검증이 자동으로 이루어지는 모든 상황에 적용된다. 여기에는 <input>, <select>, <textarea> 같은 HTML 폼 요소뿐 아니라 ARIA로 구축된 커스텀 인터랙티브 위젯도 포함된다. JavaScript로 트리거되는 클라이언트 측 검증, required, pattern, minlength, type 같은 속성을 사용하는 브라우저 기본 제약 조건 검증, 페이지 리로드 후 렌더링되거나 DOM에 동적으로 삽입되는 서버 측 검증 결과 모두를 포괄한다.

준수(pass)를 위해서는 다음이 필요하다. (1) 오류가 있는 각 필드가 오류 메시지와 프로그래밍적으로 연관되어 있어야 한다 — 일반적으로 aria-describedby 또는 aria-errormessage를 통해 — 그래야 보조 기술이 이를 읽어 줄 수 있다. (2) 오류 메시지는 UI에서 눈에 보이는 일반 텍스트여야 하며(시각 사용자에게 숨겨져 있으면 안 된다). (3) 텍스트는 단순히 문제가 있다는 것만이 아니라, 무엇이 잘못되었는지를 명확히 설명해야 한다. 예를 들어, "Email address is required"는 유효한 오류 설명이지만, 빨간 테두리나 느낌표 아이콘만 표시하고 텍스트 대체가 전혀 없는 경우는 실패이다.

실패(fail)는 다음과 같은 경우에 발생한다. 오류가 색상 변화(텍스트 없이 빨간 테두리)로만 표시되는 경우, role="alert"로만 오류가 알림되고 어떤 필드에 영향을 미치는지 식별하지 않는 경우, 오류 메시지 텍스트가 비어 있거나 지나치게 일반적인 경우(예: "Invalid input"), 또는 오류 메시지가 DOM 안에 존재하지만 해당 필드와 프로그래밍적으로 연결되어 있지 않아 스크린 리더가 둘을 연관 지을 수 없는 경우이다.

WCAG 3.3.1은 자동 감지가 전혀 이루어지지 않는 경우에는 적용되지 않는다. 예를 들어, 폼이 제출되었을 때 서버가 단순히 페이지를 다시 로드하기만 하고 무엇이 잘못되었는지에 대한 어떤 표시도 제공하지 않는다면, 이는 별도의 사용성 문제이지만 이 성공 기준의 엄밀한 범위 밖에 해당한다. 그러나 시스템이 자동 감지를 수행한다면(사실상 거의 모든 현대 폼이 그렇다), 이 기준은 완전히 적용된다. 특정 입력 유형이나 사용 사례에 대한 예외는 이 기준 자체에 정의되어 있지 않다.

왜 중요한가

오류 식별은 여러 장애 유형에 직접적이고 중대한 영향을 미친다. NVDA, JAWS, VoiceOver 같은 스크린 리더에 의존하는 시각장애 및 저시력 사용자에게 빨간 테두리나 경고 아이콘은 아무 정보도 전달하지 못한다. 오류 메시지가 실제 텍스트로 존재하지 않거나 오류가 있는 필드와 프로그래밍적으로 연결되어 있지 않으면, 스크린 리더는 이를 읽어 주지 못하고 사용자는 왜 폼 제출이 계속 실패하는지 이해하지 못한 채 반복해서 제출할 수 있다. 세계보건기구(WHO)에 따르면 전 세계적으로 약 22억 명이 어떤 형태로든 시각 장애를 가지고 있으며, 이들 중 수백만 명이 매일 웹 콘텐츠와 상호작용하기 위해 보조 기술에 의존한다.

인지 장애 — 난독증, 주의력 결핍 장애, 기억력 장애 등을 포함 — 가 있는 사용자에게 "Something went wrong" 같은 모호한 오류 메시지는 큰 장벽이 된다. 이들은 어떤 필드를 어떻게 고쳐야 하는지 정확히 알려 주는 구체적이고 설명적인 오류 텍스트로부터 큰 도움을 받는다. 예를 들어, "Invalid date" 대신 "Date of birth must be in DD/MM/YYYY format"와 같은 메시지가 훨씬 더 실행 가능하다.

운동 장애가 있어 키보드나 스위치 장치로 탐색하는 사용자는 폼을 이동하는 데 상당한 노력을 들인다. 오류가 불분명하거나 식별되지 않으면, 문제를 찾기 위해 폼 전체를 다시 돌아다녀야 하며, 이는 폼 작성의 인지적·신체적 부담을 크게 증가시킨다. 명확한 오류 식별은 이러한 사용자가 문제 있는 필드로 바로 이동할 수 있게 해 준다.

다음과 같은 실제 상황을 생각해 보자. 장애 급여를 신청하기 위해 정부 전자 서비스 포털에 회원가입을 시도하는 한 터키 시민이 있다. 이 폼은 특정 형식의 주민등록번호 입력을 요구한다. 시각장애가 있는 이 사용자는 자신의 주민등록번호를 입력한다. 제출 후 폼은 다시 로드되며, 일부 필드가 빨간색으로 강조 표시되지만 연결된 텍스트 오류는 없다. 스크린 리더는 필드 레이블만 다시 읽어 줄 뿐이며, 사용자는 무엇이 잘못되었는지, 어떤 필드에 문제가 있는지 전혀 알 수 없다. 결국 이 사용자는 과정을 완전히 포기하고, 중요한 공공 서비스에 대한 접근을 잃게 된다. WCAG 3.3.1이 바로 이런 상황을 방지하기 위해 설계된 것이다.

접근성을 넘어, 명확한 오류 식별은 모든 사용자의 전환율과 사용성을 향상시킨다. UX 연구에서는 설명적인 인라인 오류 메시지가 폼 이탈을 줄인다는 결과가 일관되게 나타난다. SEO 관점에서도, 사이트 체류 시간 증가와 성공적인 폼 제출 등 개선된 사용자 참여 신호는 시간이 지남에 따라 검색 순위에 긍정적인 영향을 미친다.

관련 Axe-core 규칙

WCAG 3.3.1을 완전히 검증하려면 수동 테스트가 필요하다. 자동화 도구는 aria-describedbyaria-errormessage 같은 구조적 패턴의 존재 여부는 감지할 수 있지만, 오류 메시지의 텍스트 콘텐츠가 의미 있고 정확하며 사용자가 오류를 이해하고 수정하는 데 충분한지 평가할 수는 없다. 자동화 도구는 role="alert" 요소가 존재한다는 사실은 알 수 있지만, "Error on line 4"라는 메시지가 스크린 리더 사용자에게 검증 실패를 충분히 설명하는지 판단할 수는 없다.

  • aria-required-attr: 이 axe-core 규칙은 특정 ARIA role을 가진 요소에 필요한 ARIA 속성이 모두 존재하는지 확인한다. 오류 식별 규칙에만 국한되지는 않지만, 오류 상태의 폼 필드가 role="textbox" 등과 함께 필요한 속성 없이 사용될 경우 보조 기술에 상태 정보를 제대로 전달하지 못해 오류 커뮤니케이션을 저해할 수 있다는 점에서 관련성이 있다.
  • aria-valid-attr-value: 이 규칙은 ARIA 속성이 DOM에 존재하지 않는 ID를 참조하는 경우를 표시한다. 예를 들어, 어떤 필드가 aria-describedby="email-error"를 사용하지만 id="email-error"를 가진 요소가 존재하지 않는다면, 연관 관계가 끊어져 스크린 리더가 오류 메시지를 읽지 못한다. 이는 3.3.1에서 흔히 발생하는 패턴 실패다.
  • 수동 평가 요구 사항: 테스터는 검증 오류를 수동으로 트리거한 뒤 스크린 리더를 사용해 다음을 확인해야 한다. 오류가 있는 필드가 이름이나 레이블로 식별되는지, 오류 설명이 읽히는지, 메시지가 의미 있고 실행 가능하며, 오류가 비텍스트 수단만으로 전달되지 않는지. 자동 스캔은 사용자 상호작용 시퀀스를 시뮬레이션하거나 메시지 텍스트의 의미적 품질을 평가할 수 없으므로, 이 성공 기준에서는 인간의 판단이 필수적이다.

테스트 방법

  1. axe DevTools 또는 Lighthouse를 사용한 자동 스캔: 검증 오류를 트리거하기 전과 후에 폼이 포함된 페이지에서 axe DevTools 스캔을 실행한다. 깨진 aria-describedby 또는 aria-errormessage 참조, 동적 오류 삽입에 사용되는 role="alert" 또는 aria-live 영역의 누락, 레이블이 없는 폼 필드(올바른 오류 연관을 방해함)와 관련된 위반 사항을 특히 확인한다. Lighthouse에서는 접근성(A11y) 감사에서 폼 관련 위반 사항을 확인한다. 자동 보고서가 깨끗하다고 해서 3.3.1 준수가 보장되는 것은 아니며, 특정 구조적 실패만 배제해 줄 뿐이라는 점에 유의한다.
  2. 수동 키보드 내비게이션 테스트: 키보드만 사용(Tab, Shift+Tab, Enter, Space)하여 의도적으로 잘못되었거나 누락된 값을 입력한 상태로 폼 제출을 시도한다. 포커스가 첫 번째 오류 필드로 또는 그 근처로 이동하는지, 각 오류 메시지가 뷰포트 안에서 보이는지, 오류 메시지가 특정 필드를 이름으로 식별하고 문제를 일반 텍스트로 설명하는지 확인한다.
  3. NVDA + Firefox 스크린 리더 테스트: Firefox에서 NVDA를 실행한 상태로 폼을 연다. 오류가 있는 상태로 폼을 제출한다. NVDA가 어느 필드에 오류가 있는지 발표하는지, 오류 설명을 읽는지 주의 깊게 듣는다. 오류가 있는 필드로 Tab 키로 이동했을 때, NVDA가 해당 필드의 접근 가능한 설명의 일부로 연관된 오류 메시지를 읽는지 확인한다. aria-live 영역을 사용하는 경우, 알림이 지연되거나 무시되지 않는지도 확인한다.
  4. VoiceOver + Safari(macOS/iOS) 스크린 리더 테스트: Safari에서 VoiceOver를 사용해 동일한 과정을 반복한다. 폼 제출 후 VO+오른쪽 화살표를 사용해 폼을 읽어 내려간다. 오류가 있는 각 필드에 오류 텍스트가 접근 가능한 이름 또는 설명에 포함되어 있는지 확인한다. iOS에서는 터치 내비게이션과 스와이프 제스처로 테스트한다.
  5. JAWS + Chrome 스크린 리더 테스트: Chrome에서 JAWS를 실행한 상태로 오류가 있는 폼을 제출한다. JAWS 가상 커서를 사용해 오류가 있는 각 폼 필드로 이동한다. JAWS가 필드를 읽을 때 오류 메시지 텍스트가 포함되는지 확인한다. 또한 폼 모드에서 일부 라이브 영역 알림이 억제될 수 있으므로 JAWS 폼 모드 동작도 함께 확인한다.
  6. 색상 및 비텍스트 단서 감사: 각 오류 상태를 시각적으로 점검한다. 브라우저 개발자 도구나 북마클릿을 사용해 CSS를 일시적으로 제거하거나 비활성화하고, 오류 식별이 여전히 DOM 내 텍스트로 존재하는지 확인한다. 이를 통해 오류 전달이 색상이나 아이콘에만 의존하지 않는다는 점을 검증할 수 있다.
  7. 동적 삽입 점검: 단일 페이지 애플리케이션이나 실시간 검증이 있는 폼의 경우, 오류가 트리거된 후 브라우저 개발자 도구를 사용해 DOM을 검사한다. 오류 메시지 요소가 DOM에 존재하는지, 비어 있지 않은 텍스트를 포함하는지, 해당 입력이 aria-describedby 또는 aria-errormessage를 통해 이를 참조하는지 확인한다.

수정 방법

시나리오 1: 색상으로만 표시된 오류 — 잘못된 예

<!-- Fail: red border added via class, no error text associated -->
<label for='email'>Email Address</label>
<input type='email' id='email' name='email' class='input-error'>
<!-- CSS sets border: 2px solid red on .input-error -->
<!-- No error message text is rendered in the DOM -->

시나리오 1: 색상으로만 표시된 오류 — 올바른 예

<!-- Pass: error text is present, visible, and linked to the input -->
<label for='email'>Email Address</label>
<input
  type='email'
  id='email'
  name='email'
  class='input-error'
  aria-describedby='email-error'
  aria-invalid='true'
>
<!-- aria-invalid signals the error state to assistive technologies -->
<!-- aria-describedby links the input to its error message by ID -->
<p id='email-error' role='alert'>
  Please enter a valid email address, for example: [email protected]
</p>

시나리오 2: 필드 식별 없는 일반 오류 요약 — 잘못된 예

<!-- Fail: a summary alert exists but does not identify which fields failed -->
<div role='alert'>
  <p>There are errors in the form. Please correct them and try again.</p>
</div>
<label for='phone'>Phone Number</label>
<input type='tel' id='phone' name='phone' class='is-invalid'>
<!-- No per-field error message; user cannot determine which field failed -->

시나리오 2: 필드 식별 없는 일반 오류 요약 — 올바른 예

<!-- Pass: summary lists specific fields in error, and each field has inline error text -->
<div role='alert' aria-labelledby='error-heading'>
  <h2 id='error-heading'>2 errors found. Please fix the following:</h2>
  <ul>
    <li><a href='#phone'>Phone Number: must contain only digits and be 10 characters long</a></li>
    <li><a href='#dob'>Date of Birth: required field — please enter your date of birth</a></li>
  </ul>
</div>
<label for='phone'>Phone Number</label>
<input
  type='tel'
  id='phone'
  name='phone'
  aria-describedby='phone-error'
  aria-invalid='true'
>
<!-- Inline error linked via aria-describedby -->
<p id='phone-error'>Phone Number must contain only digits and be 10 characters long.</p>

시나리오 3: 깨진 aria-describedby 참조 — 잘못된 예

<!-- Fail: aria-describedby references an ID that does not exist in the DOM -->
<label for='username'>Username</label>
<input
  type='text'
  id='username'
  name='username'
  aria-describedby='username-msg'
  aria-invalid='true'
>
<!-- The element with id='username-msg' was never rendered or was removed -->
<!-- Screen readers find no target and announce nothing for the description -->

시나리오 3: 깨진 aria-describedby 참조 — 올바른 예

<!-- Pass: the referenced element exists in the DOM with matching ID -->
<label for='username'>Username</label>
<input
  type='text'
  id='username'
  name='username'
  aria-describedby='username-msg'
  aria-invalid='true'
>
<!-- Element with matching id is present and contains descriptive text -->
<span id='username-msg'>
  Username must be between 4 and 20 characters and contain only letters and numbers.
</span>

시나리오 4: 동적으로 주입된 실시간 검증 오류 — 잘못된 예

<!-- Fail: error injected into DOM but not associated with input; no live region -->
<label for='password'>Password</label>
<input type='password' id='password' name='password'>
<!-- After blur, JavaScript appends this element, but no aria-describedby on input -->
<div class='error-text'>Password is too short.</div>

시나리오 4: 동적으로 주입된 실시간 검증 오류 — 올바른 예

<!-- Pass: error container pre-exists in DOM (empty), input references it; aria-live announces changes -->
<label for='password'>Password</label>
<input
  type='password'
  id='password'
  name='password'
  aria-describedby='password-error'
  aria-invalid='false'
>
<!-- Empty span present at page load; JavaScript populates text and sets aria-invalid='true' on error -->
<span id='password-error' aria-live='polite'></span>
<!-- JavaScript on blur: -->
<!-- document.getElementById('password-error').textContent = 'Password must be at least 8 characters.'; -->
<!-- document.getElementById('password').setAttribute('aria-invalid', 'true'); -->

자주 발생하는 실수

  • 오류를 표시하기 위해 border-color: red 추가와 같은 CSS 클래스 변경만 사용하고, DOM에 해당하는 텍스트를 전혀 제공하지 않는 경우. 색상만으로는 시각장애 사용자나 색각 이상 사용자가 오류를 인지할 수 없으며, 3.3.1과 1.4.1 모두를 위반한다.
  • 입력 요소에 aria-describedby를 작성했지만, 검증 리셋 시 조건부로 렌더링되거나 DOM에서 제거되는 ID를 가리키는 경우. 참조가 깨지면 스크린 리더는 연관된 콘텐츠를 찾지 못하고, 오류는 사실상 보조 기술 사용자에게 보이지 않게 된다.
  • placeholder 텍스트를 유일한 오류 메시지로 사용하는 경우. placeholder 텍스트는 사용자가 입력을 시작하면 사라지고, 대비가 낮은 경우가 많으며, 오류 상태에서 필드 설명의 일부로 모든 스크린 리더가 안정적으로 읽어 주는 것도 아니다.
  • 오류 메시지를 DOM에 동적으로 삽입하면서, 이를 aria-describedby를 통해 상위 필드와 연관시키지 않는 경우. 시각적으로 인접한 오류 메시지라고 해서 입력과 자동으로 연관되는 것은 아니며, 프로그래밍적 연결이 명시적으로 이루어져야 한다.
  • 페이지 상단 수준의 오류 요약만 표시하고, 각 요약 항목을 오류가 있는 특정 필드와 연결하지 않는 경우. 스크린 리더 사용자나 키보드 내비게이션 사용자는 오류 설명에서 바로 수정이 필요한 필드로 이동할 수 있어야 한다.
  • 필드에 aria-invalid='true'를 설정하면서 오류 메시지 텍스트는 제공하지 않는 경우. aria-invalid 속성은 필드가 오류 상태임을 알릴 뿐 오류 내용을 설명하지는 않으므로, 반드시 설명적인 메시지와 함께 사용해야 한다.
  • "Invalid input"이나 "Error in field"처럼 지나치게 일반적인 오류 메시지를 제공하는 경우. 이러한 설명은 무엇이 잘못되었는지, 어떻게 고쳐야 하는지 알려 주지 못해 3.3.1의 설명 요구 사항을 충족하지 못하며, 모든 사용자에게 오류 수정 과정을 불필요하게 어렵게 만든다.
  • 폼을 리셋할 때 오류 컨테이너에서 aria-live 영역이나 role='alert'를 제거해, 스크린 리더가 내용을 다 읽기도 전에 컨테이너가 사라지게 만드는 경우. 오류 알림이 중간에 끊겨 사용자가 검증 결과를 인지하지 못할 수 있다.
  • 커스터마이징 없이 브라우저 기본 검증 버블(기본 툴팁 팝업)에만 의존하는 경우. 브라우저 기본 검증 UI는 브라우저와 보조 기술 조합에 따라 일관성이 없으며, 복잡한 폼 시나리오에서 식별과 설명에 대한 WCAG 요구 사항을 충족하지 못하는 경우가 많다.
  • 오류 메시지를 관련 필드와 시각적으로 멀리 떨어진 위치 — 예를 들어 헤더 경고 상자에만 — 배치하고, 각 필드 근처에 인라인 메시지를 제공하지 않는 경우. 화면을 확대하는 저시력 사용자는 입력 영역에 포커스를 두고 있을 때 헤더 수준의 경고를 보지 못할 수 있으며, 키보드 사용자는 오류를 읽기 위해 상당한 거리를 이동해야 한다.

터키 접근성 규정과의 관계

터키의 대통령령 2025/10은 2025년 6월 21일 관보 제32933호에 게재되었으며, 터키에서 운영되는 광범위한 주체에 대해 의무적인 웹 접근성 요구 사항을 수립한다. 이 대통령령은 WCAG 2.2를 기술 표준으로 채택하고 있으며, 모든 레벨 A 성공 기준 — WCAG 3.3.1 오류 식별을 포함 — 을 적용 대상 조직에 법적으로 의무화한다.

대통령령이 정한 준수 기한은 공공 기관에 대해 1년, 민간 부문에 대해 2년이며, 기준일은 공포일이다. 이는 공공 기관이 2026년 6월까지 WCAG 2.2 레벨 A 적합성을 달성해야 하고, 적용 대상 민간 부문은 2027년 6월까지 달성해야 함을 의미한다.

대통령령의 적용 대상에는 터키의 디지털 서비스 전반이 폭넓게 포함된다. 공공 기관 — 중앙 정부 부처, 지방자치단체, 국립 대학, 공공 기관 모두 — 은 디지털 서비스의 접근성을 보장해야 한다. 민간 부문에서는 전자상거래 플랫폼, 은행 및 금융 기관, 병원 및 민간 의료 제공자, 가입자 200,000명 이상인 통신사, 여행사, 민간 운송 회사, 그리고 교육부(MoNE)의 인가를 받은 사립학교가 대통령령의 적용을 받는다.

이들 모든 주체에게 WCAG 3.3.1은 온라인 폼이 사용되는 모든 곳에서 직접적으로 관련된다. 실제로는 거의 모든 디지털 접점이 여기에 해당한다. 전자상거래 결제 폼, 은행 계좌 개설 흐름, 병원 환자 등록 포털, 정부 복지 신청 폼, 항공·버스 예약 시스템, 학교 입학 신청 페이지 등은 모두 폼 검증에 의존한다. 이러한 시스템이 입력 오류를 자동으로 감지하면서도 필드를 식별하지 않거나 오류를 텍스트로 설명하지 못한다면, 이는 대통령령 요구 사항을 직접적으로 위반하는 것이다.

대통령령을 준수하지 않을 경우, 적용 대상 조직은 규제 기관의 조사, 평판 리스크, 그리고 터키의 디지털 접근성 집행 체계가 성숙해짐에 따라 잠재적 제재에 노출될 수 있다. 법적 준수를 넘어, 3.3.1을 준수하는 것은 포용적 서비스 제공에 대한 의지를 보여 주는 것이기도 하다. 이는 TÜİK 자료 기준 약 850만 명에 이르는 터키의 장애인들이 온라인에서 필수 서비스를 장벽 없이 이용할 수 있도록 보장하는 일이다. Accsible의 SDK 프레임워크 하에서 운영되는 조직은 자동화된 구조적 준수뿐 아니라 철저한 수동 테스트를 우선시하여, 오류 처리 방식이 이 기본적인 성공 기준을 완전히 충족하도록 해야 한다.