Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2024-05-23 - Accessible Toast Notifications
**Learning:** Toast notifications often disappear too quickly for some users. Implementing a 5000ms minimum duration AND a manual close button ensures compliance with accessibility standards (WCAG 2.2.1 Timing Adjustable) and improves usability for everyone.
**Action:** When implementing temporary feedback messages, always include a visual close button and ensure the timeout is sufficient (>= 5000ms), or allow user preference to extend it.

## 2024-05-24 - Preserving DOM Structure during State Changes
**Learning:** When adding structural visual hints like `<kbd>` tags to buttons, updating button state via `textContent` inadvertently strips out these important nested HTML elements, breaking the UX enhancement.
**Action:** Use `Array.from(element.childNodes)` to save the actual DOM nodes before a state change (like a loading spinner), and restore them using `element.replaceChildren(...originalNodes)` to preserve the structural integrity and accessibility without risking XSS through `innerHTML`.
19 changes: 14 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'sha256-fgxmLOznNmVf4GAd24jy5Eiv/Rer+7sVLOBqnsVx0nY='; script-src 'self' 'sha256-cPRnZP+O4z5KsN+vdFstQwgKExGtoN98I3Gq+Tm1aSA='; object-src 'none'; base-uri 'self'; upgrade-insecure-requests;">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'sha256-fgxmLOznNmVf4GAd24jy5Eiv/Rer+7sVLOBqnsVx0nY='; script-src 'self' 'sha256-5AidD7xn450D0TyzEkOwY5QKedt3yHb5LdsNR95Byos='; object-src 'none'; base-uri 'self'; upgrade-insecure-requests;">
<meta name="referrer" content="no-referrer">
<title>5ive - UX Sample</title>
<style>
Expand Down Expand Up @@ -109,8 +109,8 @@ <h1>Welcome to 5ive</h1>
<p>Sample accessible button component:</p>

<!-- ✅ GOOD UX: Semantic button with proper labeling and focus handling -->
<button type="button" class="btn" id="action-btn">
Click Me (Accessible)
<button type="button" class="btn" id="action-btn" aria-keyshortcuts="Control+Enter Meta+Enter">
Click Me <kbd>Ctrl+Enter</kbd>
</button>

<div id="feedback" class="feedback" aria-live="polite"></div>
Expand All @@ -123,6 +123,15 @@ <h1>Welcome to 5ive</h1>
const feedback = document.getElementById('feedback');
let feedbackTimeout;

// Cross-platform keyboard shortcut for the action button
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
actionBtn.focus();
actionBtn.click();
}
});

actionBtn.addEventListener('click', () => {
// Clear any existing timeout
if (feedbackTimeout) clearTimeout(feedbackTimeout);
Expand All @@ -133,7 +142,7 @@ <h1>Welcome to 5ive</h1>

// Set loading state
// ✅ Sentinel: Avoid innerHTML to prevent XSS
const originalText = actionBtn.textContent;
const originalNodes = Array.from(actionBtn.childNodes);
actionBtn.disabled = true;
actionBtn.setAttribute('aria-busy', 'true');
actionBtn.textContent = '';
Expand All @@ -149,7 +158,7 @@ <h1>Welcome to 5ive</h1>
// Reset state
actionBtn.disabled = false;
actionBtn.removeAttribute('aria-busy');
actionBtn.textContent = originalText;
actionBtn.replaceChildren(...originalNodes);

// Show success with icon and transition
// ✅ Sentinel: Using textContent and DOM methods instead of innerHTML
Expand Down