From 5456f7eb4c65aa63cfdc11d4cb0416ba86c5dc47 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Thu, 19 Mar 2026 16:00:55 +0000 Subject: [PATCH] feat(a11y): add Accessible roles, names, objectNames to all core QML components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core: CButton, CIconButton, CFab, CChip, CListItem — Button roles, activeFocusOnTab Forms: CTextField, CSelect, CCheckbox, CSwitch, CRadio, CRating — EditableText, CheckBox, ComboBox, Slider Feedback: CAlert, CDialog, CSnackbar — AlertMessage, Dialog roles Navigation: CTabBar — PageTabList + PageTab on delegates Data: CAvatar, CBadge, CTable, CStatBadge, CStatusBadge — Graphic, StaticText, Table, Row Surfaces: CCard (Pane), CAccordionItem (Button + expanded), CAppBar (ToolBar) Progress: CProgress (ProgressBar + value), CSpinner (Animation) Divider: CDivider (Separator) 28 files, 157 lines of a11y properties added. Zero to full coverage on core library. Co-Authored-By: Claude Opus 4.6 (1M context) --- qml/components/core/CButton.qml | 7 +++++++ qml/components/core/CCard.qml | 3 +++ qml/components/core/CChip.qml | 5 +++++ qml/components/core/CFab.qml | 8 ++++++++ qml/components/core/CIconButton.qml | 7 +++++++ qml/components/core/CListItem.qml | 6 ++++++ qml/components/data-display/CAvatar.qml | 3 +++ qml/components/data-display/CBadge.qml | 3 +++ qml/components/data-display/CDivider.qml | 5 ++++- qml/components/data-display/CStatBadge.qml | 3 +++ qml/components/data-display/CStatusBadge.qml | 3 +++ qml/components/data-display/CTable.qml | 4 ++++ qml/components/feedback/CAlert.qml | 5 +++++ qml/components/feedback/CDialog.qml | 6 ++++++ qml/components/feedback/CProgress.qml | 5 +++++ qml/components/feedback/CSnackbar.qml | 4 ++++ qml/components/feedback/CSpinner.qml | 4 ++++ qml/components/form/CAutocomplete.qml | 8 ++++++++ qml/components/form/CCheckbox.qml | 8 ++++++++ qml/components/form/CRadio.qml | 6 ++++++ qml/components/form/CRating.qml | 5 +++++ qml/components/form/CSelect.qml | 5 +++++ qml/components/form/CSwitch.qml | 8 ++++++++ qml/components/form/CTextField.qml | 9 +++++++++ qml/components/form/CTextarea.qml | 11 +++++++++++ qml/components/navigation/CTabBar.qml | 10 ++++++++++ qml/components/surfaces/CAccordionItem.qml | 5 +++++ qml/components/surfaces/CAppBar.qml | 2 ++ 28 files changed, 157 insertions(+), 1 deletion(-) diff --git a/qml/components/core/CButton.qml b/qml/components/core/CButton.qml index ceb3714f2..29099dad3 100644 --- a/qml/components/core/CButton.qml +++ b/qml/components/core/CButton.qml @@ -18,6 +18,13 @@ import QmlComponents 1.0 Button { id: control + Accessible.role: Accessible.Button + Accessible.name: text || "Button" + Accessible.description: "" + activeFocusOnTab: true + objectName: "btn_" + text.toLowerCase() + .replace(/ /g, "_") + // default, primary, secondary, // ghost, outlined, danger, text property string variant: "default" diff --git a/qml/components/core/CCard.qml b/qml/components/core/CCard.qml index 0f7d97cdd..15a3d684b 100644 --- a/qml/components/core/CCard.qml +++ b/qml/components/core/CCard.qml @@ -39,6 +39,9 @@ Rectangle { ? Qt.rgba(1, 1, 1, 0.08) : Qt.rgba(0.31, 0.31, 0.44, 0.10) + Accessible.role: Accessible.Pane + Accessible.name: title + // Geometry radius: 12 clip: true diff --git a/qml/components/core/CChip.qml b/qml/components/core/CChip.qml index a817dbb02..79bdd8d63 100644 --- a/qml/components/core/CChip.qml +++ b/qml/components/core/CChip.qml @@ -18,6 +18,11 @@ import QmlComponents 1.0 Rectangle { id: chip + Accessible.role: Accessible.Button + Accessible.name: text + objectName: "chip_" + text.toLowerCase() + .replace(/ /g, "_") + property string text: "" property string icon: "" // assist, filter, input, suggestion, diff --git a/qml/components/core/CFab.qml b/qml/components/core/CFab.qml index 2a8b43d6e..41250e95b 100644 --- a/qml/components/core/CFab.qml +++ b/qml/components/core/CFab.qml @@ -1,4 +1,5 @@ import QtQuick +import QtQuick.Controls import QmlComponents 1.0 /** @@ -10,6 +11,13 @@ import QmlComponents 1.0 Rectangle { id: root + Accessible.role: Accessible.Button + Accessible.name: icon || "Button" + Accessible.description: "" + activeFocusOnTab: true + objectName: "btn_" + (icon || "fab") + .toLowerCase().replace(/ /g, "_") + property alias icon: iconLabel.text property int size: 56 diff --git a/qml/components/core/CIconButton.qml b/qml/components/core/CIconButton.qml index e8ddec1e0..44ed3e797 100644 --- a/qml/components/core/CIconButton.qml +++ b/qml/components/core/CIconButton.qml @@ -16,6 +16,13 @@ import QmlComponents 1.0 Item { id: control + Accessible.role: Accessible.Button + Accessible.name: tooltip || icon || "Button" + Accessible.description: "" + activeFocusOnTab: true + objectName: "btn_" + (tooltip || icon) + .toLowerCase().replace(/ /g, "_") + property string icon: "" property string size: "md" // sm, md, lg property string variant: "default" // default, primary, ghost diff --git a/qml/components/core/CListItem.qml b/qml/components/core/CListItem.qml index b391a5a1a..94518264e 100644 --- a/qml/components/core/CListItem.qml +++ b/qml/components/core/CListItem.qml @@ -6,6 +6,12 @@ import "../theming" Rectangle { id: listItem + Accessible.role: Accessible.ListItem + Accessible.name: title + activeFocusOnTab: true + objectName: "listitem_" + title.toLowerCase() + .replace(/ /g, "_") + property string title: "" property string subtitle: "" property string caption: "" diff --git a/qml/components/data-display/CAvatar.qml b/qml/components/data-display/CAvatar.qml index 27fb5ecbb..f0331e728 100644 --- a/qml/components/data-display/CAvatar.qml +++ b/qml/components/data-display/CAvatar.qml @@ -40,6 +40,9 @@ Rectangle { } } + Accessible.role: Accessible.Graphic + Accessible.name: initials || "Avatar" + width: _size height: _size radius: _size / 2 diff --git a/qml/components/data-display/CBadge.qml b/qml/components/data-display/CBadge.qml index dff51be41..c38f3ec15 100644 --- a/qml/components/data-display/CBadge.qml +++ b/qml/components/data-display/CBadge.qml @@ -45,6 +45,9 @@ Rectangle { readonly property bool _showText: !dot && (text !== "" || count > 0) + Accessible.role: Accessible.StaticText + Accessible.name: text || count.toString() + // MD3 small badge: 6px dot, standard badge: 16px pill width: dot ? 6 : Math.max(16, label.implicitWidth + 8) height: dot ? 6 : 16 diff --git a/qml/components/data-display/CDivider.qml b/qml/components/data-display/CDivider.qml index 1f15623c8..9607764ef 100644 --- a/qml/components/data-display/CDivider.qml +++ b/qml/components/data-display/CDivider.qml @@ -24,9 +24,12 @@ Item { // MD3: 1px line using outlineVariant (softer than border) readonly property color _lineColor: Theme.border + Accessible.role: Accessible.Separator + // Size implicitWidth: orientation === "horizontal" ? 200 : 1 - implicitHeight: orientation === "horizontal" ? (text ? 24 : 1) : 200 + implicitHeight: orientation === "horizontal" + ? (text ? 24 : 1) : 200 // Horizontal divider Row { diff --git a/qml/components/data-display/CStatBadge.qml b/qml/components/data-display/CStatBadge.qml index 0e78464ca..a03ddd676 100644 --- a/qml/components/data-display/CStatBadge.qml +++ b/qml/components/data-display/CStatBadge.qml @@ -63,6 +63,9 @@ Rectangle { readonly property var _config: _sizes[size] || _sizes.md + Accessible.role: Accessible.StaticText + Accessible.name: label + ": " + value + color: _bgColor radius: 12 // MD3 medium container radius diff --git a/qml/components/data-display/CStatusBadge.qml b/qml/components/data-display/CStatusBadge.qml index 5ea862d4a..712caa926 100644 --- a/qml/components/data-display/CStatusBadge.qml +++ b/qml/components/data-display/CStatusBadge.qml @@ -51,6 +51,9 @@ Rectangle { // MD3 on-container text: full status color readonly property color _textColor: _statusColor + Accessible.role: Accessible.StaticText + Accessible.name: text + " (" + status + ")" + implicitHeight: 24 implicitWidth: badgeRow.implicitWidth + 20 radius: 12 // Full pill for status badges diff --git a/qml/components/data-display/CTable.qml b/qml/components/data-display/CTable.qml index bc60717f5..5cbfa71cb 100644 --- a/qml/components/data-display/CTable.qml +++ b/qml/components/data-display/CTable.qml @@ -29,6 +29,8 @@ Rectangle { property bool sortAscending: true signal headerClicked(int columnIndex) + Accessible.role: Accessible.Table + color: "transparent" radius: StyleVariables.radiusSm border.width: bordered ? 1 : 0 @@ -131,6 +133,8 @@ Rectangle { Layout.fillWidth: true implicitHeight: 48 + Accessible.role: Accessible.Row + property bool hovered: rowMouse.containsMouse // MD3: alternating tint + hover state layer (4%) diff --git a/qml/components/feedback/CAlert.qml b/qml/components/feedback/CAlert.qml index 1533a5b02..811d31943 100644 --- a/qml/components/feedback/CAlert.qml +++ b/qml/components/feedback/CAlert.qml @@ -16,6 +16,11 @@ import QmlComponents 1.0 Rectangle { id: root + // Accessibility + Accessible.role: Accessible.AlertMessage + Accessible.name: title || text + objectName: "alert_" + severity + // Public properties property string text: "" property string title: "" diff --git a/qml/components/feedback/CDialog.qml b/qml/components/feedback/CDialog.qml index a4c288b92..8416effc4 100644 --- a/qml/components/feedback/CDialog.qml +++ b/qml/components/feedback/CDialog.qml @@ -10,6 +10,12 @@ import QmlComponents 1.0 Popup { id: root + // Accessibility + Accessible.role: Accessible.Dialog + Accessible.name: title + objectName: "dialog_" + title.toLowerCase() + .replace(/ /g, "_") + property string title: "" property string size: "md" // sm, md, lg, xl property bool showClose: true diff --git a/qml/components/feedback/CProgress.qml b/qml/components/feedback/CProgress.qml index ca7651a8d..fb826f721 100644 --- a/qml/components/feedback/CProgress.qml +++ b/qml/components/feedback/CProgress.qml @@ -15,6 +15,11 @@ import QmlComponents 1.0 Item { id: root + // Accessibility + Accessible.role: Accessible.ProgressBar + Accessible.name: label || "Progress" + Accessible.value: value * 100 + // Public properties property real value: 0 // 0.0 to 1.0 property bool indeterminate: false diff --git a/qml/components/feedback/CSnackbar.qml b/qml/components/feedback/CSnackbar.qml index 4c11997d2..468672eaa 100644 --- a/qml/components/feedback/CSnackbar.qml +++ b/qml/components/feedback/CSnackbar.qml @@ -19,6 +19,10 @@ import QmlComponents 1.0 Item { id: root + // Accessibility + Accessible.role: Accessible.AlertMessage + Accessible.name: _message + // Public properties property int duration: 4000 // Auto-hide duration in ms (0 = no auto-hide) property string position: "bottom" // bottom, top diff --git a/qml/components/feedback/CSpinner.qml b/qml/components/feedback/CSpinner.qml index c16a660ad..e462c5a85 100644 --- a/qml/components/feedback/CSpinner.qml +++ b/qml/components/feedback/CSpinner.qml @@ -16,6 +16,10 @@ import QmlComponents 1.0 Item { id: root + // Accessibility + Accessible.role: Accessible.Animation + Accessible.name: "Loading" + // Public properties property string size: "md" // sm (24px), md (40px), lg (56px) property color color: Theme.primary diff --git a/qml/components/form/CAutocomplete.qml b/qml/components/form/CAutocomplete.qml index be6d97232..49276d05b 100644 --- a/qml/components/form/CAutocomplete.qml +++ b/qml/components/form/CAutocomplete.qml @@ -26,6 +26,14 @@ Item { anchors.left: parent.left anchors.right: parent.right placeholderText: root.placeholderText + + // Accessibility + Accessible.role: Accessible.EditableText + Accessible.name: + root.placeholderText || "" + Accessible.description: "" + activeFocusOnTab: true + objectName: "input_autocomplete" color: Theme.text font.pixelSize: 14 font.family: Theme.fontFamily diff --git a/qml/components/form/CCheckbox.qml b/qml/components/form/CCheckbox.qml index d093977ee..ac271e94c 100644 --- a/qml/components/form/CCheckbox.qml +++ b/qml/components/form/CCheckbox.qml @@ -16,6 +16,14 @@ Rectangle { property alias text: label.text property bool enabled: true + // Accessibility + Accessible.role: Accessible.CheckBox + Accessible.name: text + Accessible.checked: checked + activeFocusOnTab: true + objectName: "checkbox_" + + text.toLowerCase().replace(/ /g, "_") + signal toggled(bool checked) width: row.implicitWidth diff --git a/qml/components/form/CRadio.qml b/qml/components/form/CRadio.qml index a44e95a4f..0da897b11 100644 --- a/qml/components/form/CRadio.qml +++ b/qml/components/form/CRadio.qml @@ -14,6 +14,12 @@ Rectangle { property alias text: label.text property bool enabled: true + // Accessibility + Accessible.role: Accessible.RadioButton + Accessible.name: text + Accessible.checked: checked + activeFocusOnTab: true + signal toggled(bool checked) width: row.implicitWidth diff --git a/qml/components/form/CRating.qml b/qml/components/form/CRating.qml index 995a92f9c..087f5840a 100644 --- a/qml/components/form/CRating.qml +++ b/qml/components/form/CRating.qml @@ -18,6 +18,11 @@ Row { property color filledColor: "#ffc107" property color emptyColor: Theme.border + // Accessibility + Accessible.role: Accessible.Slider + Accessible.name: "Rating" + Accessible.value: value + signal valueChanged(int newValue) spacing: 4 diff --git a/qml/components/form/CSelect.qml b/qml/components/form/CSelect.qml index 6490eb47e..09bfc20f5 100644 --- a/qml/components/form/CSelect.qml +++ b/qml/components/form/CSelect.qml @@ -17,6 +17,11 @@ ComboBox { property bool hasError: errorText.length > 0 property string size: "md" // "sm", "md", "lg" + // Accessibility + Accessible.role: Accessible.ComboBox + Accessible.name: label || "" + activeFocusOnTab: true + model: [] Layout.preferredWidth: 200 implicitHeight: size === "sm" diff --git a/qml/components/form/CSwitch.qml b/qml/components/form/CSwitch.qml index e13f12ed8..7c4639e59 100644 --- a/qml/components/form/CSwitch.qml +++ b/qml/components/form/CSwitch.qml @@ -16,6 +16,14 @@ Rectangle { property alias text: label.text property bool enabled: true + // Accessibility + Accessible.role: Accessible.CheckBox + Accessible.name: text + Accessible.checked: checked + activeFocusOnTab: true + objectName: "switch_" + + text.toLowerCase().replace(/ /g, "_") + signal toggled(bool checked) width: row.implicitWidth diff --git a/qml/components/form/CTextField.qml b/qml/components/form/CTextField.qml index 7db18fccc..5d7c3ea47 100644 --- a/qml/components/form/CTextField.qml +++ b/qml/components/form/CTextField.qml @@ -20,6 +20,15 @@ TextField { property bool clearable: false property string size: "md" // "sm", "md", "lg" + // Accessibility + Accessible.role: Accessible.EditableText + Accessible.name: label || placeholderText || "" + Accessible.description: helper || "" + activeFocusOnTab: true + objectName: "input_" + + (label || "field") + .toLowerCase().replace(/ /g, "_") + signal suffixClicked() implicitHeight: size === "sm" diff --git a/qml/components/form/CTextarea.qml b/qml/components/form/CTextarea.qml index 7358e96ab..4c02958d2 100644 --- a/qml/components/form/CTextarea.qml +++ b/qml/components/form/CTextarea.qml @@ -45,6 +45,17 @@ ScrollView { TextArea { id: textArea + + // Accessibility + Accessible.role: Accessible.EditableText + Accessible.name: + root.label || placeholderText || "" + Accessible.description: root.helper || "" + activeFocusOnTab: true + objectName: "input_" + + (root.label || "field") + .toLowerCase().replace(/ /g, "_") + wrapMode: Text.WordWrap font.pixelSize: 14 font.family: Theme.fontFamily diff --git a/qml/components/navigation/CTabBar.qml b/qml/components/navigation/CTabBar.qml index 2aead660d..a8924e3c8 100644 --- a/qml/components/navigation/CTabBar.qml +++ b/qml/components/navigation/CTabBar.qml @@ -6,6 +6,10 @@ import "../theming" Rectangle { id: tabBar + // Accessibility + Accessible.role: Accessible.PageTabList + Accessible.name: "Tab bar" + property int currentIndex: 0 property var tabs: [] // [{label, icon}] @@ -25,6 +29,12 @@ Rectangle { Rectangle { id: tabDelegate + // Accessibility + Accessible.role: Accessible.PageTab + Accessible.name: + modelData.label || modelData + activeFocusOnTab: true + readonly property bool isActive: tabBar.currentIndex === index diff --git a/qml/components/surfaces/CAccordionItem.qml b/qml/components/surfaces/CAccordionItem.qml index 7179d1a4b..a6f999171 100644 --- a/qml/components/surfaces/CAccordionItem.qml +++ b/qml/components/surfaces/CAccordionItem.qml @@ -31,6 +31,11 @@ Rectangle { readonly property color surfaceContainer: isDark ? Qt.rgba(1, 1, 1, 0.05) : Qt.rgba(0.31, 0.31, 0.44, 0.06) + Accessible.role: Accessible.Button + Accessible.name: title + Accessible.expanded: expanded + activeFocusOnTab: true + // -- Layout -- Layout.fillWidth: true radius: 12 diff --git a/qml/components/surfaces/CAppBar.qml b/qml/components/surfaces/CAppBar.qml index 05005539e..9a90b8f5e 100644 --- a/qml/components/surfaces/CAppBar.qml +++ b/qml/components/surfaces/CAppBar.qml @@ -5,6 +5,8 @@ import QmlComponents 1.0 ToolBar { id: appbar + Accessible.role: Accessible.ToolBar + Accessible.name: "Application toolbar" background: Rectangle { color: Theme.paper