From 7c51c48869a13deaa056fb5c25f057a6d33b2945 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 12 Jul 2026 08:46:53 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20dynamic=20a?= =?UTF-8?q?ccessibility=20context=20to=20list=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: NSEvent <44446865+NSEvent@users.noreply.github.com> --- .Jules/palette.md | 4 ++++ .../Views/Macros/MacroListView.swift | 12 ++++++------ .../MainWindow/ChordSequenceListViews.swift | 16 ++++++++-------- .../Views/MainWindow/GestureListViews.swift | 8 ++++---- .../Views/Scripts/ScriptListView.swift | 8 ++++---- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index f61603c8..58909748 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -25,3 +25,7 @@ ## 2026-06-23 - [SwiftUI Button Accessibility] **Learning:** Found a pattern where SwiftUI icon-only buttons or minimal UI elements were given `.help()` modifiers for hover tooltips but lacked `.accessibilityLabel()` modifiers for screen readers. **Action:** When adding `.help()` to buttons, always pair it with a corresponding `.accessibilityLabel()` to ensure full accessibility. + +## 2024-07-12 - [Dynamic Accessibility Labels for List Item Actions] +**Learning:** Icon-only buttons (like Edit/Delete) inside lists pose a major accessibility challenge for VoiceOver users when identical labels ("Edit") are repeated without context, making it impossible to know which row is being acted on. +**Action:** Always interpolate the dynamic item context (e.g., `item.name`) into both `.help()` tooltips and `.accessibilityLabel()` modifiers in repeated SwiftUI lists. Include fallback text for empty states (e.g., `"Unnamed Item"`). diff --git a/XboxControllerMapper/XboxControllerMapper/Views/Macros/MacroListView.swift b/XboxControllerMapper/XboxControllerMapper/Views/Macros/MacroListView.swift index 211ecb1e..8e249337 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/Macros/MacroListView.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/Macros/MacroListView.swift @@ -155,8 +155,8 @@ struct SharedMacroRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit in shared library") - .accessibilityLabel("Edit in shared library") + .help("Edit \(macro.name.isEmpty ? "Unnamed Macro" : macro.name) in shared library") + .accessibilityLabel("Edit \(macro.name.isEmpty ? "Unnamed Macro" : macro.name) in shared library") } .padding(.horizontal, 12) .padding(.vertical, 8) @@ -206,16 +206,16 @@ struct MacroRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit") - .accessibilityLabel("Edit") + .help("Edit \(macro.name.isEmpty ? "Unnamed Macro" : macro.name)") + .accessibilityLabel("Edit \(macro.name.isEmpty ? "Unnamed Macro" : macro.name)") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Delete") - .accessibilityLabel("Delete") + .help("Delete \(macro.name.isEmpty ? "Unnamed Macro" : macro.name)") + .accessibilityLabel("Delete \(macro.name.isEmpty ? "Unnamed Macro" : macro.name)") } } .padding(.horizontal, 12) diff --git a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift index 169053d5..f5a2975e 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift @@ -115,16 +115,16 @@ struct ChordRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit") - .accessibilityLabel("Edit") + .help("Edit \(chord.name)") + .accessibilityLabel("Edit \(chord.name)") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Delete") - .accessibilityLabel("Delete") + .help("Delete \(chord.name)") + .accessibilityLabel("Delete \(chord.name)") } } .padding(.horizontal, 12) @@ -261,16 +261,16 @@ struct SequenceRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit") - .accessibilityLabel("Edit") + .help("Edit \(sequence.name)") + .accessibilityLabel("Edit \(sequence.name)") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Delete") - .accessibilityLabel("Delete") + .help("Delete \(sequence.name)") + .accessibilityLabel("Delete \(sequence.name)") } } .padding(.horizontal, 12) diff --git a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/GestureListViews.swift b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/GestureListViews.swift index a77232ea..ed85a992 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/GestureListViews.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/GestureListViews.swift @@ -78,8 +78,8 @@ struct GestureRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit") - .accessibilityLabel("Edit") + .help("Edit \(gestureType.displayName)") + .accessibilityLabel("Edit \(gestureType.displayName)") if mapping?.hasAction == true { Button(action: onClear) { @@ -87,8 +87,8 @@ struct GestureRow: View { .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Clear") - .accessibilityLabel("Clear") + .help("Clear \(gestureType.displayName)") + .accessibilityLabel("Clear \(gestureType.displayName)") } } } diff --git a/XboxControllerMapper/XboxControllerMapper/Views/Scripts/ScriptListView.swift b/XboxControllerMapper/XboxControllerMapper/Views/Scripts/ScriptListView.swift index 40c6f5ff..11c8dd28 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/Scripts/ScriptListView.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/Scripts/ScriptListView.swift @@ -222,16 +222,16 @@ struct ScriptRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit") - .accessibilityLabel("Edit") + .help("Edit \(script.name.isEmpty ? "Untitled Script" : script.name)") + .accessibilityLabel("Edit \(script.name.isEmpty ? "Untitled Script" : script.name)") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Delete") - .accessibilityLabel("Delete") + .help("Delete \(script.name.isEmpty ? "Untitled Script" : script.name)") + .accessibilityLabel("Delete \(script.name.isEmpty ? "Untitled Script" : script.name)") } } .padding(.horizontal, 12) From 1eb5dca51c0c7fbf650572d3bf372db222846748 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 12 Jul 2026 08:54:12 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20dynamic=20a?= =?UTF-8?q?ccessibility=20context=20to=20list=20actions=20(Fix=20compilati?= =?UTF-8?q?on)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: NSEvent <44446865+NSEvent@users.noreply.github.com> --- .../MainWindow/ChordSequenceListViews.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift index f5a2975e..bcd944d8 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift @@ -115,16 +115,16 @@ struct ChordRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit \(chord.name)") - .accessibilityLabel("Edit \(chord.name)") + .help("Edit \(chord.buttonsDisplayString)") + .accessibilityLabel("Edit \(chord.buttonsDisplayString)") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Delete \(chord.name)") - .accessibilityLabel("Delete \(chord.name)") + .help("Delete \(chord.buttonsDisplayString)") + .accessibilityLabel("Delete \(chord.buttonsDisplayString)") } } .padding(.horizontal, 12) @@ -261,16 +261,16 @@ struct SequenceRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit \(sequence.name)") - .accessibilityLabel("Edit \(sequence.name)") + .help("Edit \(sequence.stepsDisplayString)") + .accessibilityLabel("Edit \(sequence.stepsDisplayString)") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Delete \(sequence.name)") - .accessibilityLabel("Delete \(sequence.name)") + .help("Delete \(sequence.stepsDisplayString)") + .accessibilityLabel("Delete \(sequence.stepsDisplayString)") } } .padding(.horizontal, 12)