Training project
What This Site Is
My first full site, and the project where I actually learned JavaScript by using it — a mobile hamburger nav, client-side form validation, and a project filter that reads each project's category data and builds its own filter buttons automatically.
How I Worked Through This
The project filter was built data-driven on purpose: each project card carries its own category tags, and the JavaScript reads whatever categories exist on the page and generates the filter buttons from them. That means adding a new project later means touching one attribute, not the JavaScript itself — a small upfront decision that pays off every time a new project gets added.
Two real bugs surfaced after the project was already marked
"finished," both the sneaky kind — nothing looked broken until you
tried the exact right interaction. The submit handler was listening
for a click on the submit button specifically, so pressing Enter in a
text field — a completely normal way to submit a form — bypassed
validation entirely and reloaded the page, wiping whatever someone had
typed. Separately, the form itself was missing
novalidate, so the browser's own built-in validation was
silently intercepting submission before my JavaScript ever ran — same
symptom, a browser message instead of mine, for a completely different
reason.
Getting the success message to announce correctly for screen reader
users took two attempts, not one. The first pass added
aria-live="polite" to a success message that was appended
to the DOM after submission — and testing with VoiceOver showed it
silently failed to announce automatically. The real issue: a live
region only reliably announces changes to content already present in
the accessibility tree; a brand-new appended element isn't
consistently treated as a "change." The fix was a permanent, empty
live region present from page load, with JavaScript only ever updating
its content, never creating or removing it.
A smaller one, caught during the filter's desktop layout: filtered items stopped hiding correctly once the desktop media query kicked in. Traced it to a CSS specificity conflict — a more specific rule inside the media query was quietly overriding the hide rule — and fixed it by adding the same override inside that query, rather than assuming it was a JavaScript problem and debugging the wrong layer.
Technical Details
| Component | Role |
|---|---|
| HTML | Semantic structure — articles for project cards, per-field error spans |
| CSS | Custom properties, mobile-first responsive layout |
| Vanilla JavaScript | Nav toggle, form validation, data-driven project filter |
| Fetch API / async-await | Real form submission and success/failure handling via Formspree |
| ARIA (aria-live, aria-expanded) | Accessible form errors and submission status, verified with VoiceOver |