PySide Qt for Python 접근성 가이드 AccessibleName 스크린리더 Tab 순서 대비 포커스 링 한 번에 정리
🧭 실무에서 바로 쓰는 PySide A11y 체크리스트와 구현 팁을 깔끔하게 담았습니다
데스크톱 앱을 만들다 보면 기능은 잘 동작하는데 사용성이 아쉬운 순간이 분명 찾아옵니다.
시각적 힌트가 부족해 포커스를 놓치거나, 스크린리더가 위젯을 제대로 설명하지 못해 흐름이 끊기는 경험이 대표적이죠.
이럴 때 접근성은 추가 옵션이 아니라 제품의 기본 품질을 결정짓는 요소가 됩니다.
특히 PySide Qt for Python 환경에서는 AccessibleName과 AccessibleDescription, Tab 순서, 대비와 포커스 링 같은 기초만 제대로 정리해도 체감 품질이 눈에 띄게 올라갑니다.
이번 글은 복잡한 이론보다 실제 프로젝트에서 바로 적용할 수 있는 기준과 작성 요령에 집중해, 처음 설정하는 분도 막힘없이 따라올 수 있도록 구성했습니다.
핵심은 화면을 보는 사용자와 보지 못하는 사용자가 같은 정보를 얻고 같은 흐름으로 조작할 수 있어야 한다는 점입니다.
이를 위해서는 스크린리더가 읽을 이름과 설명을 명확히 부여하고, 키보드만으로도 자연스럽게 이동되는 Tab 순서를 설계해야 합니다.
또한 포커스가 어디에 있는지 한눈에 구분되도록 충분한 대비와 선명한 포커스 링을 제공해야 하죠.
각 항목은 서로 연결되어 있기 때문에, 어느 하나만 잘해도 완성되지 않습니다.
아래 목차에 따라 기본 개념부터 실전 설정 포인트까지 차근차근 점검할 수 있도록 정리했습니다.
📋 목차
🔗 PySide 접근성 기본 개념과 설정 흐름
PySide(Qt for Python)에서 접근성(A11y)은 위젯의 의미와 상태를 보조기술에 전달해 누구나 동일한 정보와 흐름으로 사용할 수 있게 하는 체계입니다.
역할(Role), 이름(AccessibleName), 설명(AccessibleDescription), 상태(State), 포커스(Focus), 탐색 순서(Tab 순서)가 핵심 축입니다.
이 값들은 스크린리더와 같은 보조기술이 UI를 이해하고 읽어주는 근거가 됩니다.
기본 개념을 정확히 잡아두면 이후 세부 구현과 테스트가 훨씬 수월해집니다.
Qt는 내부 접근성 프레임워크를 통해 위젯의 메타 정보를 수집하고, 운영체제의 접근성 브리지에 전달합니다.
윈도우는 UIA, 리눅스는 AT-SPI, 맥은 NSAccessibility 계층으로 연결되어 스크린리더가 정보를 소비합니다.
개발자는 PySide에서 올바른 속성과 포커스 정책을 지정하고, 시각적 피드백(대비, 포커스 링)을 보강하는 것으로 충분한 기반을 마련할 수 있습니다.
🧩 접근성 구성 요소 한눈에 이해
| 항목1 | 항목2 |
|---|---|
| AccessibleName | 위젯의 간결한 이름. 스크린리더가 먼저 읽는 라벨로, 동작 목적을 명확히 표현합니다. |
| AccessibleDescription | 추가 설명. 상황, 제약, 입력 형식 등 이름만으로 부족한 보조 정보를 제공합니다. |
| Role · State | 버튼, 체크박스, 입력란 등 의미와 활성/비활성, 선택됨 등의 상태를 전달합니다. |
| Tab 순서 | 키보드로 이동하는 논리적 흐름. 폼 위에서 좌→우, 상→하의 자연스러운 순서를 보장합니다. |
| 대비 · 포커스 링 | 현재 포커스 위치와 조작 가능성을 시각적으로 구분. 저시력 사용자에게 필수입니다. |
🛠️ 최소 설정 흐름
- 🏷️모든 상호작용 위젯에 AccessibleName을 지정합니다.
- 📝필요할 경우 AccessibleDescription으로 추가 맥락을 제공합니다.
- 🔗폼에서는 라벨과 입력 위젯을 setBuddy로 연결해 연관을 명확히 합니다.
- ⌨️Tab 순서를 설계하고 테스트합니다.
- 🎯포커스 링과 대비를 스타일로 보강해 시각적 피드백을 명확히 합니다.
# PySide6 예시: 기본 접근성 속성 설정
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton
from PySide6.QtGui import QPalette, QColor
app = QApplication([])
w = QWidget()
w.setWindowTitle("회원 가입")
layout = QVBoxLayout(w)
label_name = QLabel("&이름")
edit_name = QLineEdit()
label_name.setBuddy(edit_name) # 라벨-입력 연결
# 스크린리더용 이름/설명
edit_name.setAccessibleName("이름 입력란")
edit_name.setAccessibleDescription("실명을 입력하세요. 예: 홍길동")
btn_submit = QPushButton("제출")
btn_submit.setAccessibleName("제출 버튼")
btn_submit.setAccessibleDescription("입력한 정보로 가입을 진행합니다")
layout.addWidget(label_name)
layout.addWidget(edit_name)
layout.addWidget(btn_submit)
# 대비 보강(예시): 다크 버튼 텍스트 대비 향상
pal = btn_submit.palette()
pal.setColor(QPalette.ButtonText, QColor("#ffffff"))
btn_submit.setPalette(pal)
w.setLayout(layout)
w.show()
app.exec()
💡 TIP: 버튼 텍스트가 아이콘만 있는 경우 스크린리더가 의미를 잃습니다.
아이콘 버튼에는 AccessibleName을 반드시 넣고, 상황 설명이 필요한 경우 AccessibleDescription으로 보완합니다.
⚠️ 주의: 플레이스홀더 텍스트를 AccessibleName 대체로 쓰지 마세요.
플레이스홀더는 사용자가 입력을 시작하면 사라져 정보 손실을 유발하고, 스크린리더가 일관되게 읽지 않을 수 있습니다.
💬 좋은 AccessibleName은 ‘동사+대상’ 구조로 간결하게 작성합니다.
예: “파일 열기”, “이미지 업로드”, “이름 입력란”.
구현의 목표는 보조기술 사용자와 시각 사용자에게 동일한 맥락을 제공하는 것입니다.
모든 상호작용 요소에는 이름이 있어야 하며, 연관 라벨을 연결하고, 키보드만으로도 목적지까지 도달할 수 있어야 합니다.
또한 현재 위치를 시각적으로 인지할 수 있도록 충분한 대비와 포커스 표시가 필요합니다.
이 기본 원칙이 이후 세부 설정의 기준점이 됩니다.
🛠️ AccessibleName와 AccessibleDescription 모범 사례
PySide에서 AccessibleName과 AccessibleDescription은 스크린리더가 정보를 전달하는 데 있어 가장 중요한 속성입니다.
이 두 속성은 단순한 텍스트가 아니라 UI의 맥락을 이해시키는 언어적 구조이기 때문에, 올바른 작명과 일관성이 무엇보다 중요합니다.
특히 시각적 요소가 복잡하거나 버튼이 아이콘 형태로만 존재하는 인터페이스에서는 이 속성이 유일한 단서가 되기도 합니다.
🧩 AccessibleName 작성 원칙
- 💬의미를 직관적으로 표현합니다. 예: “파일 열기”, “로그인”, “검색 버튼”.
- ⚙️아이콘만 있는 버튼에는 반드시 AccessibleName을 추가합니다.
- 🔗기능과 대상이 함께 포함되도록 작성합니다. 예: “이미지 업로드”, “주소 입력란”.
- 📏중복된 의미는 피하고, 한 문장으로 간결하게 유지합니다.
# 잘못된 예시
btn = QPushButton()
btn.setIcon(QIcon("trash.svg"))
# 스크린리더는 '버튼'으로만 인식
# 해결 방법:
btn.setAccessibleName("삭제 버튼")
btn.setAccessibleDescription("선택한 항목을 삭제합니다")
📋 AccessibleDescription 작성 요령
AccessibleDescription은 사용자에게 부가적인 정보를 제공할 때 사용됩니다.
예를 들어, 입력 형식, 동작 후 결과, 제약 조건, 접근 방법 등 이름만으로 부족한 맥락을 전달할 때 유용합니다.
단, 모든 위젯에 넣을 필요는 없으며, 정보가 과도하면 오히려 접근성을 떨어뜨릴 수 있습니다.
| 상황 | 예시 문구 |
|---|---|
| 입력 제한 안내 | “8자 이상 비밀번호를 입력하세요” |
| 경고 상황 | “저장하지 않으면 변경 내용이 사라집니다” |
| 진행 상태 안내 | “업로드가 완료될 때까지 기다려 주세요” |
💎 핵심 포인트:
AccessibleDescription은 “필요한 경우에만” 사용해야 합니다.
AccessibleName으로 충분히 설명 가능한 경우 중복을 피하고, 추가 설명이 진짜 필요한 상황에만 보완적으로 작성합니다.
💡 TIP: 동일한 위젯 그룹에 같은 AccessibleName을 부여하지 마세요.
스크린리더는 첫 번째 항목만 인식하거나 잘못된 순서로 읽을 수 있습니다.
각 요소는 고유한 이름을 가져야 하며, 구분이 필요한 경우 ‘번호’나 ‘상태’를 함께 표기하는 것이 좋습니다.
AccessibleName과 Description은 단순히 문구를 채우는 기능이 아니라 사용자와 인터페이스 간의 언어적 다리입니다.
짧고 명확한 이름, 필요 시 간결한 설명, 그리고 불필요한 중복의 배제.
이 세 가지 원칙만 지켜도 PySide 애플리케이션의 접근성 수준은 한 단계 도약할 수 있습니다.
⚙️ 스크린리더 지원과 테스트 방법
AccessibleName과 Description을 잘 지정했더라도 실제 스크린리더에서 제대로 읽히지 않으면 의미가 없습니다.
PySide(Qt for Python)는 운영체제의 접근성 API(UIA, AT-SPI, NSAccessibility)를 통해 정보를 전달하기 때문에, 해당 OS의 스크린리더로 실동작을 검증해야 합니다.
이 단계는 코드 테스트만큼이나 필수입니다.
🧭 주요 스크린리더 종류와 환경별 특징
| 운영체제 | 대표 스크린리더 | 특징 |
|---|---|---|
| Windows | NVDA, JAWS | NVDA는 무료이며 PySide 앱 호환성이 높음. JAWS는 기업용 전문 기능 다수 지원. |
| macOS | VoiceOver | 기본 내장. 트랙패드 탐색과 키보드 조합이 중요. |
| Linux | Orca | AT-SPI 기반. GTK, Qt 앱 모두 테스트 가능. |
PySide 앱은 별도의 접근성 활성화 설정 없이도 기본 위젯 정보를 자동으로 제공합니다.
하지만 커스텀 위젯(QCustomWidget, QGraphicsView 기반 등)은 명시적으로 Accessible 인터페이스를 구현해야 스크린리더가 읽을 수 있습니다.
이를 위해 QAccessibleInterface를 상속하거나 QAccessible::registerAccessibleInterface()를 사용합니다.
# QAccessible 사용 예시 (PySide6)
from PySide6.QtGui import QAccessible, QAccessibleInterface
class MyAccessible(QAccessibleInterface):
def text(self, t):
if t == QAccessible.Name:
return "커스텀 영역"
if t == QAccessible.Description:
return "현재 데이터 시각화 그래프"
return ""
def role(self):
return QAccessible.StaticText
QAccessible.registerAccessibleInterface(MyAccessible())
🔍 테스트 시 확인해야 할 핵심 포인트
- 🗣️스크린리더가 AccessibleName을 정확히 읽는지 확인
- 💬입력 위젯에 대한 라벨 연결(setBuddy) 여부 검증
- ⌨️Tab으로 포커스 이동 시 읽기 순서 일관성 확인
- 🔔버튼, 체크박스, 토글 등은 상태 변화(눌림/해제)가 음성으로 반영되는지 테스트
- 🧩커스텀 위젯은 QAccessibleInterface로 별도 등록 여부 확인
💎 핵심 포인트:
NVDA는 PySide 기반 앱 테스트에 가장 적합한 스크린리더입니다.
한글 출력이 안정적이고, Qt UI 요소의 이름·설명 인식률이 높습니다.
JAWS는 상용 환경에서의 최종 검증에 적합하며, 기업 내부 배포 시 병행 테스트가 권장됩니다.
💬 스크린리더 테스트는 단순히 “읽히는지”가 아니라, 사용자가 “맥락을 이해할 수 있는지”를 기준으로 해야 합니다.
스크린리더 테스트는 개발 과정의 마지막이 아닌, UI를 설계하고 구성하는 단계에서 함께 진행해야 합니다.
특히 대화상자, 탭 전환, 메뉴 포커스 흐름 등은 처음부터 접근성을 고려해 설계해야 수정 비용을 최소화할 수 있습니다.
🔌 Tab 순서 키보드 포커스 FocusPolicy
PySide(Qt for Python)에서 접근성의 또 다른 축은 키보드 내비게이션입니다.
시각장애 사용자는 마우스 대신 키보드 Tab과 Shift+Tab 키를 사용해 위젯 간을 이동합니다.
따라서 Tab 순서가 논리적으로 이어지고, 포커스가 자연스럽게 흐르도록 설계하는 것이 매우 중요합니다.
Qt에서는 setTabOrder() 함수로 이동 순서를 직접 제어할 수 있으며, 각 위젯의 FocusPolicy 속성을 통해 포커스 가능 여부를 지정할 수 있습니다.
기본적으로 QLineEdit, QPushButton 등 입력형 위젯은 StrongFocus 정책을 가지고 있어 Tab으로 이동이 가능하지만, QLabel 같은 표시형 위젯은 포커스를 받을 수 없습니다.
⌨️ 기본 포커스 정책 종류
| FocusPolicy | 설명 |
|---|---|
| Qt.NoFocus | 포커스를 받을 수 없음. |
| Qt.TabFocus | Tab 키로만 포커스 이동 가능. |
| Qt.ClickFocus | 마우스 클릭 시 포커스 가능. |
| Qt.StrongFocus | Tab과 클릭 모두로 포커스 가능(기본 설정). |
# PySide6 예시: Tab 순서 지정
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit, QPushButton
app = QApplication([])
win = QWidget()
layout = QVBoxLayout(win)
edit1 = QLineEdit()
edit2 = QLineEdit()
btn = QPushButton("저장")
layout.addWidget(edit1)
layout.addWidget(edit2)
layout.addWidget(btn)
# Tab 이동 순서 지정
QWidget.setTabOrder(edit1, edit2)
QWidget.setTabOrder(edit2, btn)
# 포커스 정책 설정
btn.setFocusPolicy(Qt.StrongFocus)
win.show()
app.exec()
🧩 폼 구조에서 Tab 순서 설계 요령
- 🔹시각적 순서(좌→우, 상→하)와 논리적 순서를 일치시킵니다.
- 🔹숨겨진 위젯에는 NoFocus를 지정해 불필요한 이동을 막습니다.
- 🔹단계별 폼은 그룹 단위로 포커스를 묶어 관리합니다.
- 🔹포커스 이동 후 스크린리더가 올바른 이름을 읽는지 확인합니다.
💎 핵심 포인트:
포커스 흐름이 ‘자연스럽다’는 것은 시각 디자인이 아닌 논리적 구조에 달려 있습니다.
Tab 순서를 설계할 때는 실제 사용 시나리오를 따라가며 “읽기 순서, 입력 순서, 확인 순서”가 어긋나지 않도록 설계하는 것이 핵심입니다.
💡 TIP: 포커스 이동 테스트 시 Qt Designer에서 Tab 순서를 시각적으로 조정할 수도 있습니다.
‘Form → Tab Order’ 메뉴를 통해 순서를 드래그로 변경하면 자동으로 setTabOrder() 코드가 생성됩니다.
접근성에서 키보드 내비게이션은 ‘기본적인 조작 가능성’을 뜻합니다.
모든 위젯이 올바른 순서로 포커스를 받고, 각 단계에서 명확한 피드백이 제공된다면 그 자체로 완성도 높은 UX를 보장할 수 있습니다.
💡 대비 포커스 링 시각적 피드백 구현
접근성에서 ‘보인다’는 것은 단순히 색상을 보는 것이 아니라, 현재 상태를 즉시 인식할 수 있는가를 의미합니다.
저시력 사용자를 위해서는 충분한 색상 대비와 명확한 포커스 링이 필수입니다.
PySide(Qt for Python)에서는 스타일시트(QSS)와 QPalette를 활용해 손쉽게 대비 및 포커스 피드백을 강화할 수 있습니다.
🎯 대비(Contrast) 조정 원칙
WCAG(Web Content Accessibility Guidelines)에 따르면, 텍스트 대비 비율은 최소 4.5:1 이상이어야 합니다.
버튼, 텍스트, 입력란 등은 배경과 충분히 구분되어야 하며, 상태 변화(활성/비활성, 선택됨/미선택됨)가 시각적으로 드러나야 합니다.
- 🎨텍스트와 배경의 명도 차를 최소 4.5:1 이상 확보합니다.
- 🟦비활성 요소(Disabled)는 흐리게만 하지 말고 아이콘·텍스트 스타일도 함께 조정합니다.
- 💡색상만으로 상태를 구분하지 않고, 아이콘 모양이나 선 굵기 등도 병행합니다.
# PySide6 예시: 대비와 포커스 링 강화
from PySide6.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout
app = QApplication([])
w = QWidget()
layout = QVBoxLayout(w)
btn = QPushButton("확인")
btn.setStyleSheet("""
QPushButton {
background-color: #5a3e2b;
color: #ffffff;
border-radius: 6px;
padding: 8px 14px;
font-weight: bold;
}
QPushButton:focus {
outline: none;
border: 3px solid #ffb703;
box-shadow: 0 0 0 3px rgba(255, 183, 3, 0.4);
}
QPushButton:hover {
background-color: #704c36;
}
""")
layout.addWidget(btn)
w.show()
app.exec()
위 예시는 버튼에 명확한 포커스 링을 추가한 경우입니다.
기본 Qt 포커스 표시보다 시각적으로 뚜렷하며, 배경 대비도 충분히 높아 키보드 사용자나 저시력 사용자 모두에게 유용합니다.
또한 Hover 시 색상 변화로 상호작용 가능성을 직관적으로 보여줍니다.
🌈 포커스 링과 접근성 피드백
포커스 링은 단순한 장식이 아닙니다.
현재 조작 가능한 위치를 시각적으로 알려주는 핵심 피드백이며, 키보드 사용자는 이 표시만으로 앱을 탐색합니다.
PySide에서는 QSS로 제어하거나, 스타일 이벤트를 오버라이드하여 커스텀 포커스 효과를 구현할 수도 있습니다.
💎 핵심 포인트:
포커스 링은 ‘너무 화려하지 않게, 그러나 명확하게’ 표시하는 것이 이상적입니다.
색상 대비뿐 아니라 두께, 외곽 그림자, 모서리 라운드 값 등을 조정해 사용성은 높이고 시각적 부담은 줄일 수 있습니다.
💡 TIP: 다크 모드에서는 포커스 링 색상을 밝은 톤(#FFD54F, #81D4FA 등)으로 변경하고, 라이트 모드에서는 짙은 색(#004D40, #8B6E63 등)을 사용하는 것이 좋습니다.
💬 접근성 디자인의 목표는 “모든 사용자에게 같은 정보, 같은 행동 결과를 제공하는 것”입니다.
대비와 포커스 링은 그 기반을 시각적으로 완성해주는 도구입니다.
PySide에서 접근성은 단지 옵션이 아니라 UI 품질의 필수 요소입니다.
AccessibleName, Description, Tab 순서, 포커스, 대비를 종합적으로 설계할 때, 사용자 경험은 자연스럽게 ‘포용적인 인터페이스’로 발전하게 됩니다.
❓ 자주 묻는 질문 (FAQ)
AccessibleName과 QLabel 텍스트가 같아도 되나요?
PySide에서 스크린리더 테스트는 어떤 순서로 하나요?
커스텀 위젯에도 접근성 속성을 지정할 수 있나요?
Tab 순서가 꼬일 때는 어떻게 해결하나요?
포커스 링 색상을 다크 모드에 자동 대응시키려면?
AccessibleDescription은 모든 위젯에 써야 하나요?
스크린리더 테스트를 자동화할 수 있나요?
접근성 설정을 유지한 채 다국어 UI를 만들 수 있나요?
📘 PySide 접근성 설정 완벽 정리
PySide(Qt for Python)의 접근성은 ‘누구나 사용할 수 있는 앱’을 만드는 출발점입니다.
AccessibleName과 Description을 통해 스크린리더가 올바른 정보를 전달하고,
Tab 순서와 FocusPolicy로 논리적 이동 경로를 확보하며,
대비와 포커스 링을 통해 시각적 구분을 강화하는 것이 핵심입니다.
이 네 가지 축이 잘 조합되면, 보조기술 사용자는 비장애 사용자와 동일한 경험을 누릴 수 있습니다.
접근성을 구현한다는 것은 단순히 표준을 지키는 행위가 아니라,
실제 사용자의 관점에서 불편함을 최소화하고, 정보를 공평하게 전달하려는 노력입니다.
PySide는 이미 Qt 접근성 프레임워크를 그대로 계승하고 있기 때문에,
조금의 설정과 테스트만으로도 충분히 WCAG 기준을 만족하는 앱을 만들 수 있습니다.
결국 중요한 것은 ‘기능을 모두 구현했는가?’가 아니라,
‘누구나 그 기능을 쓸 수 있는가?’라는 관점의 전환입니다.
🏷️ 관련 태그 : PySide, Qt for Python, 접근성, A11y, AccessibleName, 스크린리더, Tab 순서, FocusPolicy, 대비, 포커스 링