diff --git a/.Jules/palette.md b/.Jules/palette.md
index a997ba7e..f1d04f1e 100644
--- a/.Jules/palette.md
+++ b/.Jules/palette.md
@@ -25,3 +25,7 @@
## 2026-06-08 - Badge Snippet Accessible Names
**Learning:** アイコンやコピーなどの汎用的なアクションを実行するボタンが複数リストされている場合、スクリーンリーダーユーザーがどの項目のアクションかを識別できるように、`aria-label` に具体的なコンテキスト(例: `Copy ${snippet.label}`)を含めることが重要です。同時に、表示されているテキスト(この場合は 'Copy')を `aria-label` に含めることで WCAG 2.5.3(Label in Name)に準拠します。
**Action:** 今後、リスト内やコンテキストが不明瞭なボタン(特に同じテキストラベルを持つ複数のボタン)を実装・改善する際は、必ず `aria-label` に詳細な説明を含めるようにします。また、以前試みた「文字数カウンターの aria-live 属性の削除」は、スクリーンリーダーユーザーへのフィードバックを完全に奪うことになりアクセシビリティを低下させる(リグレッション)ため、行わないよう注意します。
+
+## 2025-06-19 - Markdown Editor Tab Accessible Names
+**Learning:** タブUI(例:Markdownエディタの「Write」と「Preview」)において、短い表示テキスト(「Write」や「Preview」)だけではスクリーンリーダーユーザーにとってコンテキストが不足する場合があります。しかし、`aria-label`を追加してコンテキストを補足する際(例:「Write: Edit Markdown」)、WCAG 2.5.3(Label in Name)に準拠するためには、画面に表示されているテキスト(「Write」)を`aria-label`の先頭に正確に含める必要があります。これにより、音声認識ユーザーも視覚的なテキストを使って正しく要素を操作できるようになります。
+**Action:** 今後、短いテキストラベルを持つタブやボタンに対して、より詳細なコンテキストを提供するために`aria-label`を追加する際は、必ず表示されているテキストを`aria-label`の文字列(理想的には先頭)に含めるようにします。また、アクセシブルな名前を変更した場合は、関連するユニットテスト(`getByRole`での名前の指定など)も必ず更新してテストの失敗を防ぎます。
diff --git a/apps/web/src/components/__tests__/markdown-editor.test.tsx b/apps/web/src/components/__tests__/markdown-editor.test.tsx
index a8a56615..1f168d3d 100644
--- a/apps/web/src/components/__tests__/markdown-editor.test.tsx
+++ b/apps/web/src/components/__tests__/markdown-editor.test.tsx
@@ -32,7 +32,7 @@ describe("MarkdownEditor", () => {
render();
// Write tab is selected
- const writeTab = screen.getByRole("tab", { name: "Write" });
+ const writeTab = screen.getByRole("tab", { name: "Write: Edit Markdown" });
expect(writeTab).toHaveAttribute("aria-selected", "true");
// Textarea is displayed
@@ -50,7 +50,9 @@ describe("MarkdownEditor", () => {
render();
// Preview tab is selected
- const previewTab = screen.getByRole("tab", { name: "Preview" });
+ const previewTab = screen.getByRole("tab", {
+ name: "Preview: View Output",
+ });
expect(previewTab).toHaveAttribute("aria-selected", "true");
// Textarea is not displayed
@@ -75,7 +77,9 @@ describe("MarkdownEditor", () => {
it("calls onModeChange when clicking tabs", () => {
const { rerender } = render();
- const previewTab = screen.getByRole("tab", { name: "Preview" });
+ const previewTab = screen.getByRole("tab", {
+ name: "Preview: View Output",
+ });
fireEvent.click(previewTab);
expect(mockOnModeChange).toHaveBeenCalledTimes(1);
@@ -84,7 +88,7 @@ describe("MarkdownEditor", () => {
// Simulate re-render with new mode using rerender
rerender();
- const writeTab = screen.getByRole("tab", { name: "Write" });
+ const writeTab = screen.getByRole("tab", { name: "Write: Edit Markdown" });
fireEvent.click(writeTab);
expect(mockOnModeChange).toHaveBeenCalledTimes(2);
diff --git a/apps/web/src/components/markdown-editor.tsx b/apps/web/src/components/markdown-editor.tsx
index e0a8d0b0..a1c26d31 100644
--- a/apps/web/src/components/markdown-editor.tsx
+++ b/apps/web/src/components/markdown-editor.tsx
@@ -64,6 +64,7 @@ export function MarkdownEditor({
id={`${id}-tab-write`}
type="button"
role="tab"
+ aria-label="Write: Edit Markdown"
aria-selected={mode === "write"}
aria-controls={`${id}-panel-write`}
tabIndex={mode === "write" ? 0 : -1}
@@ -77,6 +78,7 @@ export function MarkdownEditor({
id={`${id}-tab-preview`}
type="button"
role="tab"
+ aria-label="Preview: View Output"
aria-selected={mode === "preview"}
aria-controls={`${id}-panel-preview`}
tabIndex={mode === "preview" ? 0 : -1}