Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2025-04-30 - Accessible Interactive Widgets with useId
**Learning:** In the `opencitation` Next.js codebase, when building or enhancing accessible React interactive widgets like collapsibles, tabs, or dropdowns, it's critical to use React's `useId` hook to generate unique IDs for ARIA mappings (e.g., `aria-controls` to the content container). This prevents ID collisions across multiple component instances on the same page, ensuring correct screen reader context.
**Action:** Always check `aria-controls` and `id` mappings for dynamic components and standardize on using `useId()` when creating or improving interactive UI patterns in this project.
9 changes: 6 additions & 3 deletions src/components/wiki/wiki-collapsible.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState } from "react";
import { useState, useId } from "react";

interface WikiCollapsibleProps {
title: string;
Expand All @@ -14,19 +14,22 @@ export function WikiCollapsible({
defaultOpen = true,
}: WikiCollapsibleProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
const contentId = useId();

return (
<div className="border border-wiki-border-light bg-wiki-offwhite">
<div className="flex items-center justify-between px-4 py-2 border-b border-wiki-border-light bg-wiki-tab-bg">
<span className="font-medium text-sm">{title}</span>
<button
onClick={() => setIsOpen(!isOpen)}
className="text-wiki-link text-sm hover:underline"
aria-expanded={isOpen}
aria-controls={isOpen ? contentId : undefined}
className="text-wiki-link text-sm hover:underline focus-visible:outline-dotted focus-visible:outline-1 focus-visible:outline-wiki-text focus-visible:outline-offset-2"
Comment on lines +25 to +27
>
[{isOpen ? "hide" : "show"}]
</button>
</div>
{isOpen && <div className="px-4 py-3">{children}</div>}
{isOpen && <div id={contentId} className="px-4 py-3">{children}</div>}
</div>
);
}
Loading