From 7b69062830b6d85d4a3bd7a5457c7dddf7b72c48 Mon Sep 17 00:00:00 2001 From: abdishakoor-dev <294276791+abdishakoor-dev@users.noreply.github.com> Date: Tue, 30 Jun 2026 20:26:11 +0100 Subject: [PATCH] Add Accessibility and the DOM workshop An interactive demo page that is accessible on load, then injects accessibility defects as the user clicks (low-contrast text, an image with no alt, and a link and button with no accessible name), so trainees can find them with Lighthouse Snapshot mode. Part of CodeYourFuture/curriculum#1920 --- accessibility-snapshot/assets/app.js | 84 ++++++++++ accessibility-snapshot/assets/styles.css | 190 +++++++++++++++++++++++ accessibility-snapshot/index.html | 130 ++++++++++++++++ accessibility-snapshot/readme.md | 54 +++++++ 4 files changed, 458 insertions(+) create mode 100644 accessibility-snapshot/assets/app.js create mode 100644 accessibility-snapshot/assets/styles.css create mode 100644 accessibility-snapshot/index.html create mode 100644 accessibility-snapshot/readme.md diff --git a/accessibility-snapshot/assets/app.js b/accessibility-snapshot/assets/app.js new file mode 100644 index 00000000..9bed1668 --- /dev/null +++ b/accessibility-snapshot/assets/app.js @@ -0,0 +1,84 @@ +const menuToggle = document.getElementById("toggle"); +const menu = document.getElementById("menu"); + +function toggleMenu() { + menu.classList.toggle("is-active"); + menu.toggleAttribute("hidden"); + if (menu.getAttribute("hidden") == null) { + menu.focus(); + } else { + menuToggle.focus(); + } +} +menuToggle.addEventListener("click", toggleMenu); + +window.addEventListener("keydown", (e) => { + if (e.key === "Escape" && menu.classList.contains("is-active")) { + toggleMenu(); + } + if (e.key === "/" && e.metaKey) { + toggleMenu(); + } +}); + +const addButton = document.querySelector("#add"); +const output = document.querySelector("#output"); +const status = document.querySelector("#status"); + +const items = [ + () => { + const p = document.createElement("p"); + p.textContent = + "Thanks for clicking! Here is some extra information for you."; + p.style.color = "#cccccc"; + return p; + }, + () => { + const img = document.createElement("img"); + const svg = + "" + + ""; + img.src = "data:image/svg+xml," + encodeURIComponent(svg); + img.width = 120; + img.height = 80; + return img; + }, + () => { + const link = document.createElement("a"); + link.href = "https://codeyourfuture.io"; + link.innerHTML = + "" + + ""; + return link; + }, + () => { + const button = document.createElement("button"); + button.type = "button"; + button.innerHTML = + "" + + ""; + return button; + }, +]; + +let next = 0; + +addButton.addEventListener("click", () => { + if (next >= items.length) { + return; + } + + const wrapper = document.createElement("div"); + wrapper.className = "added-item"; + wrapper.append(items[next]()); + output.append(wrapper); + + next = next + 1; + + if (next >= items.length) { + addButton.disabled = true; + status.textContent = `All ${items.length} elements added. Now run a Lighthouse audit in Snapshot mode.`; + } else { + status.textContent = `Added element ${next} of ${items.length}.`; + } +}); diff --git a/accessibility-snapshot/assets/styles.css b/accessibility-snapshot/assets/styles.css new file mode 100644 index 00000000..e063ad10 --- /dev/null +++ b/accessibility-snapshot/assets/styles.css @@ -0,0 +1,190 @@ +:root { + --copy: "Lato"; + --paper: hsla(260, 35%, 90%, 0.95); + --ink: hsla(270, 8%, 20%, 0.98); + --brand: hsla(0, 100%, 67%, 0.98); + --shade: hsla(270, 8%, 20%, 0.1); + --space: 2rem; + --box: 18rem; + --container: clamp(var(--box), calc(100vw - calc(var(--space) * 2)), 74rem); + --finger: 48px; + --line-length: 60ch; +} + +* { + box-sizing: border-box; +} + +body, +html { + padding: 0; + margin: 0; +} + +a, +a:any-link, +button { + color: currentColor; + text-decoration: none; + font-weight: bold; + transition: all 0.3s; +} + +:focus { + outline: none; +} + +a:hover, +a:focus, +button:hover, +button:focus { + color: var(--brand); +} + +a:focus, +button:focus { + outline: 2px dashed var(--brand); +} + +button { + appearance: none; + border: none; + background: transparent; +} + +ul, +ul li { + list-style: none; + margin: 0; + padding: 0; +} + +body { + font: 100%/1.5 var(--copy), system-ui, sans-serif; + background-color: var(--paper); + color: var(--ink); + display: grid; + gap: var(--space); + grid-template: + ". ...... ." var(--space) + ". header ." min-content + ". main ." minmax(75vh, 1fr) + ". footer ." min-content + ". ...... ." var(--space) / + minmax(var(--space), 1fr) var(--container) minmax(var(--space), 1fr); + place-content: center; +} + +header { + grid-area: header; + display: grid; + grid-template: + "logo space button" var(--finger) / + var(--finger) 1fr var(--finger); +} + +header h1 { + grid-area: logo; + margin: 0; +} + +main { + grid-area: main; + display: flex; + flex-flow: row wrap; + gap: var(--space); +} + +main h2 { + width: 100%; + text-align: center; + font-size: 2em; +} + +footer { + grid-area: footer; + display: grid; + grid-template: "github space" var(--finger) / var(--finger) 1fr; +} + +footer a { + grid-area: github; +} + +.menu { + background-color: var(--ink); + color: var(--paper); + height: 100vh; + width: max-content; + padding: var(--space); + z-index: 1; + position: absolute; + left: -100vw; +} + +.menu.is-active { + left: 0; +} + +.copy { + max-width: var(--line-length); +} + +.is-invisible { + position: absolute; + left: -100vw; +} + +.is-invisible:focus { + left: 0; +} + +#toggle { + grid-area: button; +} + +.logo, +footer svg { + width: var(--finger); + height: var(--finger); +} + +.copy ol { + list-style: decimal; + padding-left: 1.25rem; +} + +.copy ol li { + margin-bottom: 0.5rem; +} + +#add { + background-color: var(--ink); + color: var(--paper); + padding: 0.6rem 1.2rem; + border-radius: 0.3rem; + cursor: pointer; +} + +#add:disabled { + opacity: 0.55; + cursor: not-allowed; +} + +#status { + font-weight: bold; + min-height: 1.5rem; +} + +#output { + display: flex; + flex-wrap: wrap; + gap: var(--space); + margin-top: var(--space); +} + +.added-item { + background-color: #ffffff; + padding: var(--space); + box-shadow: 2px 4px 16px var(--shade); +} diff --git a/accessibility-snapshot/index.html b/accessibility-snapshot/index.html new file mode 100644 index 00000000..832c5c2b --- /dev/null +++ b/accessibility-snapshot/index.html @@ -0,0 +1,130 @@ + + + + + + Accessibility & the DOM Workshop + + + + + + + +
+

+ + + + +

+ +
+ +
+

♿ Accessibility & the DOM

+ +
+

+ This page is fully accessible when it first loads. Each time you press + the button below, JavaScript adds a new element to the page. Every new + element looks fine, but each one hides a different accessibility + problem. +

+ +

Try it

+
    +
  1. + Run a Lighthouse audit in Navigation mode now, + before clicking anything. Note the Accessibility score. +
  2. +
  3. + Press Add an element until the button is disabled. +
  4. +
  5. + Run a Lighthouse audit in Snapshot mode and compare + what it reports. +
  6. +
  7. Write down every accessibility issue Snapshot mode finds.
  8. +
+ + +

+ +
+
+
+ + + diff --git a/accessibility-snapshot/readme.md b/accessibility-snapshot/readme.md new file mode 100644 index 00000000..e7de92cc --- /dev/null +++ b/accessibility-snapshot/readme.md @@ -0,0 +1,54 @@ +# Accessibility & the DOM + +A short demo of how changing the DOM can change the accessibility of a page, and +how to find those problems with Lighthouse **Snapshot mode**. + +## Learning Objectives + +```objectives +- [ ] Explain how changing the DOM can change the accessibility of a page +- [ ] Find accessibility problems that only appear after user interaction +- [ ] Use Lighthouse Snapshot mode to audit a page after it has changed +``` + +## Requirements + +For this demo you will need: + +- [ ] [Chrome](https://www.google.com/intl/en_uk/chrome/) with Dev Tools +- [ ] This [demo page](https://cyf-workshop.netlify.app/accessibility-snapshot/) + +The page is fully accessible when it first loads. Each time you press **Add an +element**, JavaScript adds a new element to the page. Every new element renders +fine, but each one has a different accessibility problem: + +- text with not enough colour contrast +- an image with no alt text +- a link with no accessible name +- a button with no accessible name + +A normal Lighthouse audit runs in **Navigation mode**: it reloads the page and +checks it as it first appears. At that point the page is empty, so it finds +nothing. The problems only appear _after_ you interact with the page, so a +normal audit misses them. **Snapshot mode** audits the page in its current +state, without reloading it, so it can catch the problems your clicks +introduced. + +### Try it + +1. Open the [demo page](https://cyf-workshop.netlify.app/accessibility-snapshot/) in Chrome. +2. Open Dev Tools and go to the **Lighthouse** panel. +3. With **Mode** set to **Navigation** (the default), click **Analyze page load** and wait for the report. The **Accessibility** section should report no problems, because the page is fine when it first loads. +4. Press the **Add an element** button until it is disabled. The page has now changed, but your Navigation report still shows the page as it was when it loaded. +5. To run another audit, first start a new report by clicking the **+** button at the top of the Lighthouse panel. Then change **Mode** to **Snapshot**, click **Analyze page load**, and wait for the report. +6. Compare the two reports. Write down every accessibility issue Snapshot mode finds. + +## Acceptance Criteria + +- A **Navigation** mode audit of the page on load reports an Accessibility score of 100. The page is accessible to begin with. +- After adding the elements, a **Snapshot** mode audit reports the accessibility problems, and you can list each one and say which element caused it. + +> [!NOTE] +> The accessibility problems on this page are deliberate. They are added by +> JavaScript after you interact with the page, so they only show up in a +> Snapshot audit. That is the whole point of the demo.