diff --git a/packages/core/src/api/exporters/html/internalHTMLSerializer.ts b/packages/core/src/api/exporters/html/internalHTMLSerializer.ts
index 33376b2835..2860426a1d 100644
--- a/packages/core/src/api/exporters/html/internalHTMLSerializer.ts
+++ b/packages/core/src/api/exporters/html/internalHTMLSerializer.ts
@@ -65,6 +65,9 @@ const forceToggleBlocksShow = (element: HTMLElement) => {
);
hiddenToggleWrappers.forEach((toggleWrapper) => {
toggleWrapper.setAttribute("data-show-children", "true");
+ toggleWrapper
+ .querySelector(":scope > .bn-toggle-button")
+ ?.setAttribute("aria-expanded", "true");
});
return element;
diff --git a/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.test.ts b/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.test.ts
new file mode 100644
index 0000000000..01f3188413
--- /dev/null
+++ b/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.test.ts
@@ -0,0 +1,49 @@
+import { afterEach, describe, expect, it } from "vite-plus/test";
+import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
+
+/**
+ * @vitest-environment jsdom
+ */
+
+describe("Toggle button accessibility", () => {
+ let editor: BlockNoteEditor;
+
+ afterEach(() => {
+ editor._tiptapEditor.destroy();
+ window.localStorage.clear();
+ });
+
+ it("has an accessible name and exposes its expanded state", async () => {
+ editor = BlockNoteEditor.create({
+ initialContent: [
+ {
+ id: "toggle",
+ type: "toggleListItem",
+ content: "Toggle",
+ children: [{ type: "paragraph", content: "Child" }],
+ },
+ ],
+ });
+ const div = document.createElement("div");
+ editor.mount(div);
+
+ const button = div.querySelector(".bn-toggle-button")!;
+ expect(button.getAttribute("aria-label")).toBe(
+ editor.dictionary.toggle_blocks.toggle_button_label,
+ );
+ expect(button.querySelector("svg")!.getAttribute("aria-hidden")).toBe(
+ "true",
+ );
+ expect(button.getAttribute("aria-expanded")).toBe("false");
+
+ button.click();
+ expect(button.getAttribute("aria-expanded")).toBe("true");
+
+ // Updating `aria-expanded` must not make ProseMirror re-render the block.
+ await new Promise((resolve) => setTimeout(resolve));
+ expect(div.querySelector(".bn-toggle-button")).toBe(button);
+
+ button.click();
+ expect(button.getAttribute("aria-expanded")).toBe("false");
+ });
+});
diff --git a/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts b/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts
index 257fd7ce6f..c3745b3acf 100644
--- a/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts
+++ b/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts
@@ -42,9 +42,22 @@ export const createToggleWrapper = (
const toggleButton = document.createElement("button");
toggleButton.className = "bn-toggle-button";
toggleButton.type = "button";
+ toggleButton.setAttribute(
+ "aria-label",
+ editor.dictionary.toggle_blocks.toggle_button_label,
+ );
toggleButton.innerHTML =
// https://fonts.google.com/icons?selected=Material+Symbols+Rounded:chevron_right:FILL@0;wght@700;GRAD@0;opsz@24&icon.query=chevron&icon.style=Rounded&icon.size=24&icon.color=%23e8eaed
- '';
+ '';
+ // Keeps the button's expanded state, which screen readers announce, in sync
+ // with whether the child blocks are shown.
+ function setShowChildren(showChildren: boolean) {
+ toggleWrapper.setAttribute(
+ "data-show-children",
+ showChildren ? "true" : "false",
+ );
+ toggleButton.setAttribute("aria-expanded", showChildren ? "true" : "false");
+ }
const toggleButtonMouseDown = (event: MouseEvent) => event.preventDefault();
toggleButton.addEventListener("mousedown", toggleButtonMouseDown);
const toggleButtonOnClick = () => {
@@ -56,14 +69,14 @@ export const createToggleWrapper = (
}
if (toggleWrapper.getAttribute("data-show-children") === "true") {
- toggleWrapper.setAttribute("data-show-children", "false");
+ setShowChildren(false);
toggledState.set(currentBlock, false);
if (dom.contains(toggleAddBlockButton)) {
dom.removeChild(toggleAddBlockButton);
}
} else {
- toggleWrapper.setAttribute("data-show-children", "true");
+ setShowChildren(true);
toggledState.set(currentBlock, true);
if (
@@ -115,7 +128,7 @@ export const createToggleWrapper = (
if (newChildCount > childCount) {
// If a child block is added while children are hidden, show children.
if (toggleWrapper.getAttribute("data-show-children") === "false") {
- toggleWrapper.setAttribute("data-show-children", "true");
+ setShowChildren(true);
const currentBlock = editor.getBlock(block);
if (currentBlock) {
toggledState.set(currentBlock, true);
@@ -131,7 +144,7 @@ export const createToggleWrapper = (
// If the last child block is removed while children are shown, hide
// children.
if (toggleWrapper.getAttribute("data-show-children") === "true") {
- toggleWrapper.setAttribute("data-show-children", "false");
+ setShowChildren(false);
const currentBlock = editor.getBlock(block);
if (currentBlock) {
toggledState.set(currentBlock, false);
@@ -149,7 +162,7 @@ export const createToggleWrapper = (
});
if (toggledState.get(block)) {
- toggleWrapper.setAttribute("data-show-children", "true");
+ setShowChildren(true);
if (editor.isEditable && block.children.length === 0) {
// If the toggle is set to show children, but there are no children,
@@ -157,7 +170,7 @@ export const createToggleWrapper = (
dom.appendChild(toggleAddBlockButton);
}
} else {
- toggleWrapper.setAttribute("data-show-children", "false");
+ setShowChildren(false);
}
return {
@@ -172,6 +185,9 @@ export const createToggleWrapper = (
((mutation.type === "attributes" &&
mutation.target === toggleWrapper &&
mutation.attributeName === "data-show-children") ||
+ (mutation.type === "attributes" &&
+ mutation.target === toggleButton &&
+ mutation.attributeName === "aria-expanded") ||
(mutation.type === "childList" &&
(mutation.addedNodes[0] === toggleAddBlockButton ||
mutation.removedNodes[0] === toggleAddBlockButton)))
diff --git a/packages/core/src/i18n/locales/ar.ts b/packages/core/src/i18n/locales/ar.ts
index 1c19b810dd..b769ec1702 100644
--- a/packages/core/src/i18n/locales/ar.ts
+++ b/packages/core/src/i18n/locales/ar.ts
@@ -184,6 +184,7 @@ export const ar: Dictionary = {
},
toggle_blocks: {
add_block_button: "تبديل فارغ. انقر لإضافة كتلة.",
+ toggle_button_label: "توسيع أو طي",
},
code_block: {
add_source_button_text: "إضافة كود المصدر",
diff --git a/packages/core/src/i18n/locales/de.ts b/packages/core/src/i18n/locales/de.ts
index 45ff9341d8..67284be374 100644
--- a/packages/core/src/i18n/locales/de.ts
+++ b/packages/core/src/i18n/locales/de.ts
@@ -220,6 +220,7 @@ export const de: Dictionary = {
toggle_blocks: {
add_block_button:
"Leerer aufklappbarer Bereich. Klicken, um einen Block hinzuzufügen.",
+ toggle_button_label: "Ein- oder ausklappen",
},
code_block: {
add_source_button_text: "Quellcode hinzufügen",
diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts
index 307ba90c22..ee90f3a3d9 100644
--- a/packages/core/src/i18n/locales/en.ts
+++ b/packages/core/src/i18n/locales/en.ts
@@ -199,6 +199,7 @@ export const en = {
},
toggle_blocks: {
add_block_button: "Empty toggle. Click to add a block.",
+ toggle_button_label: "Expand or collapse",
},
code_block: {
add_source_button_text: "Add source code",
diff --git a/packages/core/src/i18n/locales/es.ts b/packages/core/src/i18n/locales/es.ts
index b2c05ca6b2..8c2102378b 100644
--- a/packages/core/src/i18n/locales/es.ts
+++ b/packages/core/src/i18n/locales/es.ts
@@ -199,6 +199,7 @@ export const es: Dictionary = {
},
toggle_blocks: {
add_block_button: "Toggle vacío. Haz clic para añadir un bloque.",
+ toggle_button_label: "Expandir o contraer",
},
code_block: {
add_source_button_text: "Agregar código fuente",
diff --git a/packages/core/src/i18n/locales/fa.ts b/packages/core/src/i18n/locales/fa.ts
index 405cf87ddf..dd3ce26370 100644
--- a/packages/core/src/i18n/locales/fa.ts
+++ b/packages/core/src/i18n/locales/fa.ts
@@ -167,6 +167,7 @@ export const fa = {
},
toggle_blocks: {
add_block_button: "تاشوی خالی. برای افزودن بلوک کلیک کنید.",
+ toggle_button_label: "باز یا بسته کردن",
},
code_block: {
add_source_button_text: "افزودن کد منبع",
diff --git a/packages/core/src/i18n/locales/fr.ts b/packages/core/src/i18n/locales/fr.ts
index 4807927655..6fc1a3d7b6 100644
--- a/packages/core/src/i18n/locales/fr.ts
+++ b/packages/core/src/i18n/locales/fr.ts
@@ -245,6 +245,7 @@ export const fr: Dictionary = {
},
toggle_blocks: {
add_block_button: "Liste repliable vide. Cliquez pour ajouter un bloc.",
+ toggle_button_label: "Déplier ou replier",
},
code_block: {
add_source_button_text: "Ajouter le code source",
diff --git a/packages/core/src/i18n/locales/he.ts b/packages/core/src/i18n/locales/he.ts
index 1b9338b77b..749aa5412c 100644
--- a/packages/core/src/i18n/locales/he.ts
+++ b/packages/core/src/i18n/locales/he.ts
@@ -201,6 +201,7 @@ export const he: Dictionary = {
},
toggle_blocks: {
add_block_button: "מתג ריק. לחץ כדי להוסיף בלוק.",
+ toggle_button_label: "הרחב או כווץ",
},
code_block: {
add_source_button_text: "הוסף קוד מקור",
diff --git a/packages/core/src/i18n/locales/hr.ts b/packages/core/src/i18n/locales/hr.ts
index 998a245f20..9faccc4040 100644
--- a/packages/core/src/i18n/locales/hr.ts
+++ b/packages/core/src/i18n/locales/hr.ts
@@ -212,6 +212,7 @@ export const hr: Dictionary = {
},
toggle_blocks: {
add_block_button: "Prazan sklopivi blok. Klikni da dodaš sadržaj.",
+ toggle_button_label: "Proširi ili sažmi",
},
code_block: {
add_source_button_text: "Dodaj izvorni kôd",
diff --git a/packages/core/src/i18n/locales/is.ts b/packages/core/src/i18n/locales/is.ts
index e7effe3827..0b95171e03 100644
--- a/packages/core/src/i18n/locales/is.ts
+++ b/packages/core/src/i18n/locales/is.ts
@@ -213,6 +213,7 @@ export const is: Dictionary = {
},
toggle_blocks: {
add_block_button: "Tóm fellilína. Smelltu til að bæta við blokk.",
+ toggle_button_label: "Fella út eða saman",
},
code_block: {
add_source_button_text: "Bæta við frumkóða",
diff --git a/packages/core/src/i18n/locales/it.ts b/packages/core/src/i18n/locales/it.ts
index 782a3c7fc4..2a19a8a224 100644
--- a/packages/core/src/i18n/locales/it.ts
+++ b/packages/core/src/i18n/locales/it.ts
@@ -221,6 +221,7 @@ export const it: Dictionary = {
},
toggle_blocks: {
add_block_button: "Toggle vuoto. Clicca per aggiungere un blocco.",
+ toggle_button_label: "Espandi o comprimi",
},
code_block: {
add_source_button_text: "Aggiungi codice sorgente",
diff --git a/packages/core/src/i18n/locales/ja.ts b/packages/core/src/i18n/locales/ja.ts
index 8bac14021d..0b279d68f0 100644
--- a/packages/core/src/i18n/locales/ja.ts
+++ b/packages/core/src/i18n/locales/ja.ts
@@ -239,6 +239,7 @@ export const ja: Dictionary = {
},
toggle_blocks: {
add_block_button: "空のトグルです。クリックしてブロックを追加。",
+ toggle_button_label: "展開または折りたたみ",
},
code_block: {
add_source_button_text: "ソースコードを追加",
diff --git a/packages/core/src/i18n/locales/ko.ts b/packages/core/src/i18n/locales/ko.ts
index de94329b19..a4f084e12f 100644
--- a/packages/core/src/i18n/locales/ko.ts
+++ b/packages/core/src/i18n/locales/ko.ts
@@ -212,6 +212,7 @@ export const ko: Dictionary = {
},
toggle_blocks: {
add_block_button: "비어 있는 토글입니다. 클릭하여 블록을 추가하세요.",
+ toggle_button_label: "펼치기 또는 접기",
},
code_block: {
add_source_button_text: "소스 코드 추가",
diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts
index a90210b572..6686e4f05e 100644
--- a/packages/core/src/i18n/locales/nl.ts
+++ b/packages/core/src/i18n/locales/nl.ts
@@ -200,6 +200,7 @@ export const nl: Dictionary = {
},
toggle_blocks: {
add_block_button: "Lege uitklapper. Klik om een blok toe te voegen.",
+ toggle_button_label: "Uitklappen of inklappen",
},
code_block: {
add_source_button_text: "Broncode toevoegen",
diff --git a/packages/core/src/i18n/locales/no.ts b/packages/core/src/i18n/locales/no.ts
index 9ed6388dc7..903658a1c3 100644
--- a/packages/core/src/i18n/locales/no.ts
+++ b/packages/core/src/i18n/locales/no.ts
@@ -218,6 +218,7 @@ export const no: Dictionary = {
},
toggle_blocks: {
add_block_button: "Tomt toggle. Klikk for å legge til en blokk.",
+ toggle_button_label: "Utvid eller skjul",
},
code_block: {
add_source_button_text: "Legg til kildekode",
diff --git a/packages/core/src/i18n/locales/pl.ts b/packages/core/src/i18n/locales/pl.ts
index 95751640b9..6fc38d552e 100644
--- a/packages/core/src/i18n/locales/pl.ts
+++ b/packages/core/src/i18n/locales/pl.ts
@@ -191,6 +191,7 @@ export const pl: Dictionary = {
toggle_blocks: {
add_block_button:
"Brak bloków do rozwinięcia. Kliknij, aby dodać pierwszego.",
+ toggle_button_label: "Rozwiń lub zwiń",
},
code_block: {
add_source_button_text: "Dodaj kod źródłowy",
diff --git a/packages/core/src/i18n/locales/pt.ts b/packages/core/src/i18n/locales/pt.ts
index 6914de9d2c..7faee83b77 100644
--- a/packages/core/src/i18n/locales/pt.ts
+++ b/packages/core/src/i18n/locales/pt.ts
@@ -191,6 +191,7 @@ export const pt: Dictionary = {
},
toggle_blocks: {
add_block_button: "Toggle vazio. Clique para adicionar um bloco.",
+ toggle_button_label: "Expandir ou recolher",
},
code_block: {
add_source_button_text: "Adicionar código-fonte",
diff --git a/packages/core/src/i18n/locales/ru.ts b/packages/core/src/i18n/locales/ru.ts
index db116a3c4c..3a0050b00e 100644
--- a/packages/core/src/i18n/locales/ru.ts
+++ b/packages/core/src/i18n/locales/ru.ts
@@ -242,6 +242,7 @@ export const ru: Dictionary = {
},
toggle_blocks: {
add_block_button: "Пустой переключатель. Нажмите, чтобы добавить блок.",
+ toggle_button_label: "Развернуть или свернуть",
},
code_block: {
add_source_button_text: "Добавить исходный код",
diff --git a/packages/core/src/i18n/locales/sk.ts b/packages/core/src/i18n/locales/sk.ts
index f53c4c39d1..e2b9ba3ee9 100644
--- a/packages/core/src/i18n/locales/sk.ts
+++ b/packages/core/src/i18n/locales/sk.ts
@@ -199,6 +199,7 @@ export const sk = {
},
toggle_blocks: {
add_block_button: "Prázdne prepínanie. Kliknite pre pridanie bloku.",
+ toggle_button_label: "Rozbaliť alebo zbaliť",
},
code_block: {
add_source_button_text: "Pridať zdrojový kód",
diff --git a/packages/core/src/i18n/locales/uk.ts b/packages/core/src/i18n/locales/uk.ts
index e6101c8f69..e169bdca25 100644
--- a/packages/core/src/i18n/locales/uk.ts
+++ b/packages/core/src/i18n/locales/uk.ts
@@ -224,6 +224,7 @@ export const uk: Dictionary = {
},
toggle_blocks: {
add_block_button: "Порожній перемикач. Натисніть, щоб додати блок.",
+ toggle_button_label: "Розгорнути або згорнути",
},
code_block: {
add_source_button_text: "Додати вихідний код",
diff --git a/packages/core/src/i18n/locales/uz.ts b/packages/core/src/i18n/locales/uz.ts
index 23b0f4f1a7..0226d3eb97 100644
--- a/packages/core/src/i18n/locales/uz.ts
+++ b/packages/core/src/i18n/locales/uz.ts
@@ -260,6 +260,7 @@ export const uz: Dictionary = {
toggle_blocks: {
add_block_button: "Bo‘sh toggle. Blok qo‘shish uchun bosing.",
+ toggle_button_label: "Yoyish yoki yig‘ish",
},
code_block: {
diff --git a/packages/core/src/i18n/locales/vi.ts b/packages/core/src/i18n/locales/vi.ts
index d52db4d48d..1d8b317019 100644
--- a/packages/core/src/i18n/locales/vi.ts
+++ b/packages/core/src/i18n/locales/vi.ts
@@ -198,6 +198,7 @@ export const vi: Dictionary = {
},
toggle_blocks: {
add_block_button: "Toggle trống. Nhấp để thêm khối.",
+ toggle_button_label: "Mở rộng hoặc thu gọn",
},
code_block: {
add_source_button_text: "Thêm mã nguồn",
diff --git a/packages/core/src/i18n/locales/zh-tw.ts b/packages/core/src/i18n/locales/zh-tw.ts
index 0aba71ead4..53f285bc73 100644
--- a/packages/core/src/i18n/locales/zh-tw.ts
+++ b/packages/core/src/i18n/locales/zh-tw.ts
@@ -240,6 +240,7 @@ export const zhTW: Dictionary = {
},
toggle_blocks: {
add_block_button: "空的切換區。點擊新增區塊。",
+ toggle_button_label: "展開或收合",
},
code_block: {
add_source_button_text: "新增原始碼",
diff --git a/packages/core/src/i18n/locales/zh.ts b/packages/core/src/i18n/locales/zh.ts
index 0017c86672..9fea9191f9 100644
--- a/packages/core/src/i18n/locales/zh.ts
+++ b/packages/core/src/i18n/locales/zh.ts
@@ -240,6 +240,7 @@ export const zh: Dictionary = {
},
toggle_blocks: {
add_block_button: "空的切换区。点击添加区块。",
+ toggle_button_label: "展开或折叠",
},
code_block: {
add_source_button_text: "添加源代码",
diff --git a/packages/react/src/blocks/ToggleWrapper/ToggleWrapper.tsx b/packages/react/src/blocks/ToggleWrapper/ToggleWrapper.tsx
index b35fc8d779..b84bb5b24c 100644
--- a/packages/react/src/blocks/ToggleWrapper/ToggleWrapper.tsx
+++ b/packages/react/src/blocks/ToggleWrapper/ToggleWrapper.tsx
@@ -125,6 +125,8 @@ export const ToggleWrapper = (