From 633237f2622acab00e01d17c8d56ac5a16106b32 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Fri, 30 Oct 2026 22:53:03 +0100 Subject: [PATCH] Make the "Other" dropdown accessible Expose its state with aria-expanded, close it on outside clicks, Escape and when focus leaves, restore the focus outline, and move the inline margins into CSS. Co-Authored-By: Claude Opus 5.5 --- src/components/ui/otherButton.module.css | 11 +++-- src/components/ui/otherButton.tsx | 63 +++++++++++++++++++++--- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/components/ui/otherButton.module.css b/src/components/ui/otherButton.module.css index 5b520f5..eaf4b9d 100644 --- a/src/components/ui/otherButton.module.css +++ b/src/components/ui/otherButton.module.css @@ -39,13 +39,10 @@ .dropdownLink { display: block; width: 100%; + margin-top: 4px; color: black; background-color: #cccccc; - border: none; - border-radius: 999em !important; - cursor: pointer; - outline: none; - border-radius: 10px; + border-radius: 999em; text-align: left; font-size: 1.6rem; box-shadow: var(--shadow); @@ -53,6 +50,10 @@ padding-right: 2em; } +.dropdownLink:first-child { + margin-top: 8px; +} + .dropdownLink:hover { color: black; background-color: #e6e6e6; diff --git a/src/components/ui/otherButton.tsx b/src/components/ui/otherButton.tsx index 9dd13b6..8fc610c 100644 --- a/src/components/ui/otherButton.tsx +++ b/src/components/ui/otherButton.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useEffect, useId, useRef, useState } from "react" import { useLocale, useTranslations } from "next-intl" import * as styles from "./otherButton.module.css" @@ -11,7 +11,37 @@ const OtherButton = () => { const locale = useLocale() const [isOpen, setIsOpen] = useState(false) - const toggleDropdown = () => setIsOpen(!isOpen) + const toggleDropdown = () => setIsOpen(open => !open) + + const containerRef = useRef(null) + const buttonRef = useRef(null) + const dropdownId = useId() + + // Close when clicking outside the dropdown or pressing Escape + useEffect(() => { + if (!isOpen) return + + const handlePointerDown = (event: PointerEvent) => { + if (!containerRef.current?.contains(event.target as Node)) { + setIsOpen(false) + } + } + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setIsOpen(false) + buttonRef.current?.focus() + } + } + + document.addEventListener("pointerdown", handlePointerDown) + document.addEventListener("keydown", handleKeyDown) + + return () => { + document.removeEventListener("pointerdown", handlePointerDown) + document.removeEventListener("keydown", handleKeyDown) + } + }, [isOpen]) const statutesUrl = locale === "en" @@ -28,21 +58,38 @@ const OtherButton = () => { ] return ( -
- {isOpen && ( -
- {links.map((link, index) => ( +
+ {links.map(link => ( {link.label}