diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..20e89bd --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,3 @@ +## 2024-07-07 - Add proper ARIA controls to conditional accordion content +**Learning:** When building an accordion where the content block is conditionally rendered (e.g. `collapsed ? : `), attaching `id={contentId}` to only one branch or hardcoding string IDs can break screen reader association or cause ID collisions. Using `useId()` to generate the ID and applying it to both conditional branches ensures an unbroken `aria-controls` relationship in all states. +**Action:** Applied `useId()` and `aria-controls` to the PPP savings panel toggle, attaching the ID to both the collapsed summary and expanded full view branches. diff --git a/components/accounts/ppp-savings-panel.tsx b/components/accounts/ppp-savings-panel.tsx index e6d961b..75146ac 100644 --- a/components/accounts/ppp-savings-panel.tsx +++ b/components/accounts/ppp-savings-panel.tsx @@ -1,6 +1,6 @@ "use client"; -import { useMemo, useState } from "react"; +import { useId, useMemo, useState } from "react"; import DOMPurify from "isomorphic-dompurify"; import { Calculator, ChevronDown, ChevronUp, Copy, Download, Loader2, Mail } from "lucide-react"; import type { PppSavingsReport } from "@/lib/application/runtime/ppp-savings-service"; @@ -34,6 +34,7 @@ export function PppSavingsPanel({ orgSlug, accountId }: PppSavingsPanelProps) { const [error, setError] = useState(null); const [copied, setCopied] = useState(false); const [collapsed, setCollapsed] = useState(false); + const contentId = useId(); const mailtoHref = useMemo(() => { if (!report?.recipientEmail || !draft) { @@ -102,6 +103,7 @@ export function PppSavingsPanel({ orgSlug, accountId }: PppSavingsPanelProps) { type="button" onClick={() => setCollapsed((value) => !value)} aria-expanded={!collapsed} + aria-controls={contentId} className="inline-flex items-center justify-center gap-2 rounded-lg border border-[var(--border-subtle)] bg-[var(--surface-elevated)] px-4 py-3 text-sm font-semibold text-[var(--text-secondary)] transition hover:border-[var(--border-strong)] hover:text-[var(--text-primary)]" > {collapsed ? : } @@ -123,7 +125,7 @@ export function PppSavingsPanel({ orgSlug, accountId }: PppSavingsPanelProps) { {error ? {error} : null} {report && collapsed ? ( - + {formatMoney(report.totalSavings)} savings @@ -146,7 +148,7 @@ export function PppSavingsPanel({ orgSlug, accountId }: PppSavingsPanelProps) { ) : null} ) : report ? ( - +