diff --git a/config.toml b/config.toml
index 9a09282..90f0f39 100644
--- a/config.toml
+++ b/config.toml
@@ -96,6 +96,9 @@ show_remote_changes = true # Defaults to true.
# Show a link to the repository of the site, right next to the "Powered by Zola & tabi" text.
show_remote_source = true # Defaults to true.
+# Load JavaScript to improve accessibility.
+accessibility_javascript = true
+
# Add a "copy" button to codeblocks (loads ~700 bytes of JavaScript).
# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
copy_button = true
diff --git a/static/js/accessibility.min.js b/static/js/accessibility.min.js
new file mode 100644
index 0000000..2248c2a
--- /dev/null
+++ b/static/js/accessibility.min.js
@@ -0,0 +1,52 @@
+document.addEventListener("DOMContentLoaded", function () {
+ initDropdownMenu();
+});
+
+function initDropdownMenu() {
+ const dropdown = document.querySelector('.dropdown');
+ const menuItems = document.querySelectorAll('[role="menuitem"]');
+ const menuButton = dropdown.querySelector('[role="button"]');
+
+ dropdown.addEventListener("toggle", handleToggle.bind(null, dropdown, menuItems, menuButton));
+ document.addEventListener("keydown", handleKeydown.bind(null, dropdown, menuItems, menuButton));
+}
+
+function handleToggle(dropdown, menuItems, menuButton) {
+ if (dropdown.hasAttribute('open')) {
+ focusMenuItem(menuItems, 0);
+ setAriaExpanded(menuButton, true);
+ } else {
+ menuButton.focus();
+ setAriaExpanded(menuButton, false);
+ }
+}
+
+function handleKeydown(dropdown, menuItems, menuButton, event) {
+ if (!dropdown.hasAttribute('open')) return;
+
+ let focusedItemIndex = Array.from(menuItems).indexOf(document.activeElement);
+
+ switch (event.key) {
+ case "ArrowDown":
+ event.preventDefault();
+ focusMenuItem(menuItems, (focusedItemIndex + 1) % menuItems.length);
+ break;
+ case "ArrowUp":
+ event.preventDefault();
+ focusMenuItem(menuItems, (focusedItemIndex - 1 + menuItems.length) % menuItems.length);
+ break;
+ case "Escape":
+ dropdown.removeAttribute('open');
+ setAriaExpanded(menuButton, false);
+ menuButton.focus();
+ break;
+ }
+}
+
+function focusMenuItem(menuItems, index) {
+ menuItems[index].focus();
+}
+
+function setAriaExpanded(element, state) {
+ element.setAttribute('aria-expanded', state ? 'true' : 'false');
+}
diff --git a/templates/partials/footer.html b/templates/partials/footer.html
index de74f91..ed6c754 100644
--- a/templates/partials/footer.html
+++ b/templates/partials/footer.html
@@ -118,4 +118,9 @@
{%- if email_needs_decoding -%}
{%- endif -%}
+
+ {# Load accessibility JavaScript #}
+ {%- if config.extra.accessibility_javascript -%}
+
+ {%- endif -%}
diff --git a/theme.toml b/theme.toml
index ac90fa1..e34fe78 100644
--- a/theme.toml
+++ b/theme.toml
@@ -75,6 +75,9 @@ show_remote_changes = true # Defaults to true.
# Show a link to the repository of the site, right next to the "Powered by Zola & tabi" text.
show_remote_source = true # Defaults to true.
+# Load JavaScript to improve accessibility.
+accessibility_javascript = true
+
# Add a "copy" button to codeblocks (loads ~700 bytes of JavaScript).
# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
copy_button = true