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-04-13 - Preserving Nested Structural Elements in DOM Updates
**Learning:** When using structural or visual hints like nested `<kbd>` tags within a component, using `textContent` for state changes (e.g., loading states) will strip the HTML structure completely. This breaks accessibility and the visual design when restoring the previous state.
**Action:** Use `const originalNodes = Array.from(element.childNodes)` and `element.replaceChildren()` to save and restore nodes accurately without needing `innerHTML` or re-parsing the DOM, ensuring elements like `<kbd>` remain structurally intact after transient state transitions.
22 changes: 16 additions & 6 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-p0wuvPF87JoIpiHPqz/zPUFM9DlbeRH3Rlpzm5n/wtc='; 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</kbd>/<kbd>Cmd</kbd> + <kbd>Enter</kbd>
</button>

<div id="feedback" class="feedback" aria-live="polite"></div>
Expand All @@ -120,6 +120,16 @@ <h1>Welcome to 5ive</h1>

<script>
const actionBtn = document.getElementById('action-btn');

document.addEventListener('keydown', (event) => {
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
event.preventDefault();
if (!actionBtn.disabled) {
actionBtn.focus();
actionBtn.click();
}
}
});
const feedback = document.getElementById('feedback');
let feedbackTimeout;

Expand All @@ -133,10 +143,10 @@ <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 = '';
actionBtn.replaceChildren();

const spinner = document.createElement('span');
spinner.className = 'spinner';
Expand All @@ -149,7 +159,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