Skip to content

Commit dae41ae

Browse files
committed
fix(web): resolve DOMParser null element crash in accessibility utils
DOMParser moves <style> elements to <head> during parsing, causing doc.body.firstChild to return null. Replace DOMParser usage with direct document.createElement() calls for announceToScreenReader, createSkipLinks, and injectAccessibilityStyles functions. This was causing the app to crash on load with: "Cannot set properties of null (setting 'textContent')"
1 parent 104586c commit dae41ae

File tree

1 file changed

+18
-19
lines changed

1 file changed

+18
-19
lines changed

apps/web/src/utils/accessibility.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44

55
// Announce messages to screen readers
66
export const announceToScreenReader = (message: string, priority: 'polite' | 'assertive' = 'polite') => {
7-
// Create element using DOMParser to avoid createElement deprecation
8-
const htmlString = `<div aria-live="${priority}" aria-atomic="true" style="position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden;"></div>`;
9-
const parser = new DOMParser();
10-
const doc = parser.parseFromString(htmlString, 'text/html');
11-
const announcement = doc.body.firstChild as HTMLElement;
7+
const announcement = document.createElement('div');
8+
announcement.setAttribute('aria-live', priority);
9+
announcement.setAttribute('aria-atomic', 'true');
10+
announcement.style.position = 'absolute';
11+
announcement.style.left = '-10000px';
12+
announcement.style.width = '1px';
13+
announcement.style.height = '1px';
14+
announcement.style.overflow = 'hidden';
1215

1316
document.body.append(announcement);
1417
announcement.textContent = message;
@@ -60,17 +63,16 @@ export const createSkipLinks = () => {
6063
{ href: '#search', text: 'Skip to search' },
6164
];
6265

63-
// Create container using DOMParser to avoid createElement deprecation
64-
const containerHtml = '<div role="navigation" aria-label="Skip navigation links" class="skip-links"></div>';
65-
const parser = new DOMParser();
66-
const doc = parser.parseFromString(containerHtml, 'text/html');
67-
const skipLinksContainer = doc.body.firstChild as HTMLElement;
66+
const skipLinksContainer = document.createElement('div');
67+
skipLinksContainer.setAttribute('role', 'navigation');
68+
skipLinksContainer.setAttribute('aria-label', 'Skip navigation links');
69+
skipLinksContainer.className = 'skip-links';
6870

6971
skipLinks.forEach((link) => {
70-
// Create anchor elements using DOMParser
71-
const anchorHtml = `<a href="${link.href}" class="skip-link">${link.text}</a>`;
72-
const anchorDoc = parser.parseFromString(anchorHtml, 'text/html');
73-
const anchor = anchorDoc.body.firstChild as HTMLElement;
72+
const anchor = document.createElement('a');
73+
anchor.href = link.href;
74+
anchor.className = 'skip-link';
75+
anchor.textContent = link.text;
7476
skipLinksContainer.append(anchor);
7577
});
7678

@@ -281,11 +283,8 @@ export const injectAccessibilityStyles = () => {
281283
return; // Already injected
282284
}
283285

284-
// Create style element using DOMParser to avoid createElement deprecation
285-
const styleHtml = `<style id="${styleId}"></style>`;
286-
const parser = new DOMParser();
287-
const doc = parser.parseFromString(styleHtml, 'text/html');
288-
const style = doc.body.firstChild as HTMLStyleElement;
286+
const style = document.createElement('style');
287+
style.id = styleId;
289288
style.textContent = `
290289
.skip-links {
291290
position: absolute;

0 commit comments

Comments
 (0)