From 0f0e31364e08b57e57b250301d3c5acd1117daf8 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 4 Jul 2026 10:24:18 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improvement]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: Added aria-controls and aria-label to the PPP savings panel accordion trigger and used useId() to link it to the conditionally rendered content branches. 🎯 Why: Improves screen reader accessibility by explicitly associating the toggle button with its content section, maintaining the connection even when the content swaps between summary and full views. 📸 Before/After: Visuals remain unchanged. ♿ Accessibility: Ensures aria-controls resolves to a valid element in all component states. Co-authored-by: brycejohnson1417 <257422776+brycejohnson1417@users.noreply.github.com> --- .Jules/palette.md | 3 +++ components/accounts/ppp-savings-panel.tsx | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 .Jules/palette.md diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..1a64430 --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,3 @@ +## 2024-07-04 - Accessible Accordion Toggles +**Learning:** When React conditional rendering toggles between summary and full views (e.g., `collapsed ? : `), screen readers lose track of the `aria-controls` target if the `id` is only on the expanded content. The `id` must be applied to *both* branch elements to ensure an unbroken association in all states. +**Action:** Use `useId()` to generate a unique ID and assign it to all conditional branches of an accordion's content. diff --git a/components/accounts/ppp-savings-panel.tsx b/components/accounts/ppp-savings-panel.tsx index e6d961b..da576cc 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,8 @@ export function PppSavingsPanel({ orgSlug, accountId }: PppSavingsPanelProps) { type="button" onClick={() => setCollapsed((value) => !value)} aria-expanded={!collapsed} + aria-controls={contentId} + aria-label={collapsed ? "Expand PPP savings details" : "Collapse PPP savings details"} 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 +126,7 @@ export function PppSavingsPanel({ orgSlug, accountId }: PppSavingsPanelProps) { {error ?
{error}
: null} {report && collapsed ? ( -
+
{formatMoney(report.totalSavings)} savings @@ -146,7 +149,7 @@ export function PppSavingsPanel({ orgSlug, accountId }: PppSavingsPanelProps) { ) : null}
) : report ? ( -
+
@@ -238,7 +241,7 @@ export function PppSavingsPanel({ orgSlug, accountId }: PppSavingsPanelProps) {
) : ( -
PPP savings will appear here after calculation.
+
PPP savings will appear here after calculation.
)} );