@@ -103,7 +108,7 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma
tabIndex={0}
aria-labelledby={sectionRoadmapTitleId}
>
- {song.sections.map((section) => (
+ {song.sections.map((section, sectionIndex) => (
{section.roles
.filter(role => !activeRole || role.id === activeRole)
- .map(role => (
+ .map((role, roleIndex) => (
handleChordEdit(section.id, role)}
- title={onSongUpdate ? t("chordEditTitle") : undefined}
- disabled={!onSongUpdate}
+ onClick={(e) => {
+ if (!onSongUpdate) {
+ e.preventDefault();
+ return;
+ }
+ handleChordEdit(section.id, role);
+ }}
+ title={onSongUpdate ? t("chordEditTitle") : t("chordEditUnavailableTitle")}
+ aria-disabled={!onSongUpdate}
>
{role.harmony.chord}
+ {!onSongUpdate && (
+
+ {t("chordEditUnavailableTitle")}
+
+ )}
{role.harmony.source === "user" && (
{t("harmonySourceUserBadge")}
diff --git a/apps/desktop/src/locales/en/common.json b/apps/desktop/src/locales/en/common.json
index 39f716d50..3d92e5324 100644
--- a/apps/desktop/src/locales/en/common.json
+++ b/apps/desktop/src/locales/en/common.json
@@ -59,6 +59,7 @@
"chordEditAriaLabel": "Edit chord for {roleName} in {sectionLabel}, current {chord}",
"chordEditPrompt": "Enter new chord:",
"chordEditTitle": "Click to edit chord",
+ "chordEditUnavailableTitle": "Open an editable song to change this chord",
"harmonySourceUserBadge": "User",
"roleSwitcherTitle": "Role-specific View",
"allRoles": "All Roles",
@@ -149,4 +150,4 @@
"practiceProgressLabel": "Practice Progress",
"decreasePracticeProgressLabel": "Decrease progress",
"increasePracticeProgressLabel": "Increase progress"
-}
+}
\ No newline at end of file
diff --git a/apps/desktop/src/locales/ko/common.json b/apps/desktop/src/locales/ko/common.json
index 371884abb..07cacaed7 100644
--- a/apps/desktop/src/locales/ko/common.json
+++ b/apps/desktop/src/locales/ko/common.json
@@ -59,6 +59,7 @@
"chordEditAriaLabel": "{roleName}의 {sectionLabel} 코드 수정, 현재 {chord}",
"chordEditPrompt": "새 코드 입력:",
"chordEditTitle": "코드 수정",
+ "chordEditUnavailableTitle": "코드를 바꾸려면 편집 가능한 곡을 여세요",
"harmonySourceUserBadge": "사용자",
"roleSwitcherTitle": "악기/보컬 역할",
"allRoles": "전체 보기",
@@ -149,4 +150,4 @@
"practiceProgressLabel": "연습 진척도",
"decreasePracticeProgressLabel": "진척도 감소",
"increasePracticeProgressLabel": "진척도 증가"
-}
+}
\ No newline at end of file
diff --git a/docs/doctoring/accessible-disabled-chord-controls.md b/docs/doctoring/accessible-disabled-chord-controls.md
new file mode 100644
index 000000000..b8f37fde8
--- /dev/null
+++ b/docs/doctoring/accessible-disabled-chord-controls.md
@@ -0,0 +1,42 @@
+# Discoverable unavailable chord controls
+
+## Decision
+
+BandScope keeps the chord-edit button focusable when editing is unavailable because the action remains useful to discover during rehearsal. The control uses `aria-disabled="true"` rather than native `disabled`, blocks activation before the edit prompt, preserves its stable localized accessible name, and associates a localized recovery instruction with `aria-describedby`.
+
+The recovery copy is action-oriented: users are told to open an editable song to change the chord. The HTML `title` mirrors that recovery copy for pointer users, but BandScope does not treat `title` alone as sufficient assistive-technology evidence. The referenced description is the semantic explanation exposed for assistive technologies.
+
+## Conformance rationale
+
+WAI-ARIA 1.2 defines `aria-disabled` for elements that are perceivable but not operable, while `aria-describedby` identifies content that describes an object. The W3C ARIA Authoring Practices keyboard-interface guidance notes that unavailable controls can remain in the focus order when discoverability is important, using `aria-disabled` rather than native disabling where appropriate. The APG button pattern uses `aria-disabled="true"` for unavailable buttons, and the W3C ARIA1 technique documents `aria-describedby` as a programmatic association through an ID reference list whose referenced element IDs are unique.
+
+The HTML Standard additionally requires an element `id` to be unique in its tree, non-empty, and free of ASCII whitespace. BandScope therefore generates the description IDs from renderer-owned section and visible-role positions rather than from analysis-domain identifiers. This keeps the ARIA references unique and whitespace-free even when otherwise valid section or role IDs contain whitespace or punctuation, while leaving those domain identifiers unchanged for business logic.
+
+BandScope therefore verifies exact rendered values rather than inferring accessibility from CSS or source intent:
+
+- the unavailable chord control remains a button and remains keyboard-focusable;
+- `aria-disabled` renders exactly as `true`;
+- the accessible name remains the localized chord-edit action;
+- `aria-describedby` resolves to the exact localized recovery instruction;
+- description IDs remain unique and whitespace-free even when validated domain section or role IDs contain arbitrary string content;
+- the pointer `title` exposes the same recovery instruction;
+- activation does not open the edit prompt when the action is unavailable; and
+- enabled chord editing retains the existing localized action label, tooltip, prompt, and update behavior.
+
+## Scope boundary
+
+This change does not claim that a native `title` attribute implements the WAI-ARIA tooltip pattern. The APG tooltip pattern describes a distinct popup that appears on hover or keyboard focus and is referenced from the triggering element. If BandScope later requires a visible keyboard-focus popup, it should use the shared tooltip primitive and validate focus, dismissal, and description behavior explicitly.
+
+## References
+
+World Wide Web Consortium. (2023). *Accessible Rich Internet Applications (WAI-ARIA) 1.2*. https://www.w3.org/TR/wai-aria/
+
+World Wide Web Consortium, Web Accessibility Initiative. (2026, January 12). *ARIA1: Using the aria-describedby property to provide a descriptive label for user interface controls*. https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA1
+
+World Wide Web Consortium, Web Accessibility Initiative. (n.d.). *Button pattern*. ARIA Authoring Practices Guide. Retrieved August 15, 2026, from https://www.w3.org/WAI/ARIA/apg/patterns/button/
+
+World Wide Web Consortium, Web Accessibility Initiative. (n.d.). *Developing a keyboard interface*. ARIA Authoring Practices Guide. Retrieved August 15, 2026, from https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/
+
+World Wide Web Consortium, Web Accessibility Initiative. (n.d.). *Tooltip pattern*. ARIA Authoring Practices Guide. Retrieved August 15, 2026, from https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
+
+WHATWG. (2026). *HTML Standard: The global attributes*. Retrieved August 15, 2026, from https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute