├── .eslintrc
├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── README.md
├── completed-components
├── exercise-3-html
│ ├── ProductBuyBoxVariantSelector.tsx
│ ├── ProductDetails.tsx
│ ├── ProductHeader.tsx
│ ├── ProductImageGallery.tsx
│ └── ProductPage.tsx
├── exercise-4-ARIA
│ ├── ProductBuyBoxVariantSelector.tsx
│ ├── ProductDetails.tsx
│ ├── ProductHeader.tsx
│ ├── ProductImageGallery.tsx
│ └── ProductPage.tsx
├── exercise-5-focus
│ ├── ProductBuyBoxVariantSelector.tsx
│ ├── ProductDetails.tsx
│ ├── ProductHeader.tsx
│ ├── ProductImageGallery.tsx
│ └── ProductPage.tsx
└── exercise-6-motion
│ ├── ProductBuyBoxVariantSelector.tsx
│ ├── ProductDetails.tsx
│ ├── ProductHeader.tsx
│ ├── ProductImageGallery.tsx
│ └── ProductPage.tsx
├── components
├── BookATripForm.module.scss
├── BookATripForm.tsx
├── Icons.tsx
├── ProductBuyBoxVariantSelector.tsx
├── ProductDetails.tsx
├── ProductHeader.tsx
├── ProductImageGallery.tsx
├── ProductPage.tsx
├── ScreenReaderImageGallery.tsx
└── slides
│ ├── DropCap.tsx
│ ├── Links.tsx
│ └── StackBlitz.tsx
├── custom.css
├── data
└── index.tsx
├── declarations.d.ts
├── hooks
└── use-reduced-motion.ts
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
├── .DS_Store
├── _app.tsx
├── _document.tsx
├── _meta.json
├── index.tsx
├── product-page-1.tsx
├── product-page-exercise-3.tsx
├── product-page-exercise-4.tsx
├── product-page-exercise-5.tsx
├── product-page-exercise-6.tsx
└── topics
│ ├── ARIA
│ ├── _meta.json
│ ├── accessible-names-descriptions.mdx
│ ├── exercise-4.mdx
│ ├── introduction.mdx
│ ├── live-regions.mdx
│ └── roles-states-properties.mdx
│ ├── _meta.json
│ ├── accessibility-debugging
│ ├── _meta.json
│ ├── exercise-2.mdx
│ ├── getting-started.mdx
│ ├── going-beyond-compliance.mdx
│ ├── linters-and-devtools.mdx
│ └── working-with-teams.mdx
│ ├── accessible-HTML
│ ├── _meta.json
│ ├── exercise-3.mdx
│ ├── lang-markup.mdx
│ ├── overview.mdx
│ └── semantic-html.mdx
│ ├── focus-management
│ ├── _meta.json
│ ├── active-element.mdx
│ ├── exercise-5.mdx
│ ├── keyboard-only.mdx
│ ├── keyboard-trapping.mdx
│ ├── shortcuts.mdx
│ ├── skip-links.mdx
│ └── tab-key-navigation.mdx
│ ├── index.mdx
│ ├── introduction
│ ├── _meta.json
│ ├── overlapping-areas.mdx
│ ├── overview.mdx
│ ├── standards-definitions.mdx
│ ├── ways-people-use-the-web.mdx
│ └── what-is-accessibility.mdx
│ ├── screen-readers
│ ├── _meta.json
│ ├── alternative-text.mdx
│ ├── exercise-1.mdx
│ ├── how-SRs-work.mdx
│ ├── how-to-enable.mdx
│ ├── introduction.mdx
│ └── other-ATs.mdx
│ ├── visual-considerations
│ ├── _meta.json
│ ├── color-contrast.mdx
│ ├── exercise-6.mdx
│ ├── performance-considerations.mdx
│ ├── prefers-color-scheme.mdx
│ ├── reducing-motion.mdx
│ └── reflow.mdx
│ └── wrap-up.mdx
├── postcss.config.js
├── public
├── .DS_Store
├── accessibility-color-contrast-examples.png
├── accessible-naming.png
├── activeElement.jpg
├── adapt-action.jpg
├── animated-scene.png
├── aria-toast.jpg
├── bio-banner-black.jpg
├── bio-banner-white.jpg
├── capitol-crawl.jpg
├── chrome.png
├── compliant.jpg
├── costco-nav.png
├── demo-pages
│ ├── Fido Pro Panza Harness + Deployable Emergency Dog Rescue Sling - Hike & Camp.html
│ ├── Fido Pro Panza Harness + Deployable Emergency Dog Rescue Sling - Hike & Camp_files
│ │ ├── 0
│ │ ├── 440
│ │ ├── 67723
│ │ ├── 5438854
│ │ ├── 0402a4497000c5d37a35fc42cfeb6f18c4e96c75.a220bbcd6c48990964fd.2.115.7.js
│ │ ├── 102.535071e982bde64c64d4.2.115.7.js
│ │ ├── 104.e57d28ad33e4db6cf22c.2.115.7.js
│ │ ├── 1070187334.html
│ │ ├── 114380.ct.js
│ │ ├── 134.80dd0f31b479096c665e.2.115.7.js
│ │ ├── 135.20f541e7f655e32e73d0.2.115.7.js
│ │ ├── 136.572f017b34120c79b947.2.115.7.js
│ │ ├── 139.379c7db5ddc6ec61eeac.2.115.7.js
│ │ ├── 1676409410248_Feedback_Tab.png
│ │ ├── 20557481387
│ │ ├── 2843_c7924a1eb38cdbc866e2fc72c6d86e86be6343fd6dc8435e35ba157952bc2405_edge_helper.js
│ │ ├── 30.cdeb9c0785e44ad8909d.2.115.7.js
│ │ ├── 3a78db81bceb32d68c532e13623f25759705315c.6764a782f55d485da73f.2.115.7.js
│ │ ├── 3cc687d78f0a822cb32f11c152611119bfbb5e98.c19b0971c1a97e5bfc83.2.115.7.js
│ │ ├── 4069bbf9c7f74fc76b083f89bdc75e46bf10d0a2.b7a95ec527c2f6f5b425.2.115.7.js
│ │ ├── 4c38c1e13b1255a27ba4891347f2c472283e17fd.e9ca7c4ee2291b158beb.2.115.7.js
│ │ ├── 4d9f93bc734e54364c2416fbd7a54640eb153e85.e7cd41a3608b9b246fb5.2.115.7.js
│ │ ├── 5438854.js
│ │ ├── 610185875725240
│ │ ├── 6443dc27873f66400a297ce0dea0f260e04b080b.ca9423c83050bd7f918f.2.115.7.js
│ │ ├── 6db6164da0a02f4d3bf4aa99299e98d72b8f158c.9b4167ec08e1225880b8.2.115.7.js
│ │ ├── 9799b222b7fc5bd0f6e887f7075b4d136bdd60f4.2d5fb2ce02e90db5744b.2.115.7.js
│ │ ├── 9dfe2cde3f220999092422b236aca7f96e252a82.86b7f29c938ebe51e21c.2.115.7.js
│ │ ├── A372572-d6b3-4464-8175-43afcdedc6dd1.js
│ │ ├── BLA(1).jpg
│ │ ├── BLA.jpg
│ │ ├── ContentWall.8d71d5242956c0bd59db.2.115.7.js
│ │ ├── Details.c2b37b8f169299b41ed5.2.115.7.js
│ │ ├── Flyout(1).jpg
│ │ ├── Flyout(2).jpg
│ │ ├── Flyout(3).jpg
│ │ ├── Flyout(4).jpg
│ │ ├── Flyout.jpg
│ │ ├── FlyoutM.jpg
│ │ ├── FlyoutSki.jpg
│ │ ├── FlyoutSnowboard.jpg
│ │ ├── FlyoutW.jpg
│ │ ├── Flyout_StockingStuffers.jpg
│ │ ├── Flyout_kids.jpg
│ │ ├── Klarna.a9455bf32fa3bad78e69.2.115.7.js
│ │ ├── OfferDrawerPrompt.dba5345f5c04de21ae23.2.115.7.js
│ │ ├── RED.jpg
│ │ ├── RED_D5.jpg
│ │ ├── RED_D6.jpg
│ │ ├── RED_D7.jpg
│ │ ├── RED_D8.jpg
│ │ ├── Ski_Flyout.jpg
│ │ ├── Snowboard_Flyout.jpg
│ │ ├── StickyBuyBoxSection.0294d0f0cc72341c5204.2.115.7.js
│ │ ├── Travel_Flyout.jpg
│ │ ├── X1ECiEB
│ │ ├── [id]-1d7954067f243968428a.js
│ │ ├── _app-8716f5d1fd5dcdb48842.js
│ │ ├── _buildManifest.js
│ │ ├── _ssgManifest.js
│ │ ├── a44221fddf9b0e4f806a28f0b2d03af174140151.76305ae13c8b15854220.2.115.7.js
│ │ ├── a5269526b9a3807b8d45aded35007c28acd3ec3d.f80c13eba958a4a16d1c.2.115.7.js
│ │ ├── analytics.js
│ │ ├── b6dfbdc868889737690cfb52f785e35befe67ce6.c50c59fce6c18066a8ac.2.115.7.js
│ │ ├── backcountry-chat-module.js.gz
│ │ ├── backcountry-us.attn.tv.js
│ │ ├── backcountry_vT67AH.js
│ │ ├── bat.js
│ │ ├── bv.js
│ │ ├── c8f7fe3b0e41be846d5687592cf2018ff6e22687.e64ad0d896d1d88c3cfa.2.115.7.js
│ │ ├── clarity.js
│ │ ├── commons.66471e926b26ea7fe0bd.2.115.7.js
│ │ ├── connect.html
│ │ ├── css
│ │ ├── css(1)
│ │ ├── cssReset-bbce9172.css
│ │ ├── datadog-logs.js
│ │ ├── di.js
│ │ ├── dtag.js
│ │ ├── ec.js
│ │ ├── ede1f4077b6380d4c3d5fbb0287b65bf846cf415.bbbca8cfbceb95287165.2.115.7.js
│ │ ├── embed.js
│ │ ├── f.txt
│ │ ├── fbevents.js
│ │ ├── fonts.css
│ │ ├── framework.162f627c77c9dacbdb20.2.115.7.js
│ │ ├── generic1699892387979.js
│ │ ├── gtm.js
│ │ ├── index-569e91c6.js
│ │ ├── index.html
│ │ ├── js
│ │ ├── julian.jpg
│ │ ├── lib.js
│ │ ├── load-chat.js
│ │ ├── lp.js
│ │ ├── main-2bf509ede96b7cf0a4aa.js
│ │ ├── messaging.js
│ │ ├── nsjs
│ │ ├── polyfills-9877e4e8f0a2a3974035.js
│ │ ├── retargeting.js
│ │ ├── saved_resource
│ │ ├── saved_resource(1)
│ │ ├── saved_resource(2).html
│ │ ├── saved_resource.html
│ │ ├── serenovawebchat.2.2.3.min.js
│ │ ├── skilength.jpg
│ │ ├── track.v2.js
│ │ ├── unified-tag.js
│ │ ├── universal_pixel.1.1.0.js
│ │ ├── up(1).html
│ │ ├── up.html
│ │ ├── up_loader.1.1.0.js
│ │ ├── venturebeyond.jpg
│ │ └── webpack-e31cc4f3930205349c00.js
│ ├── backcountry-x-petco-the-dog-sleeping-bag.html
│ └── backcountry-x-petco-the-dog-sleeping-bag_files
│ │ ├── 0
│ │ ├── 440
│ │ ├── 67723
│ │ ├── 5438854
│ │ ├── 0402a4497000c5d37a35fc42cfeb6f18c4e96c75.a220bbcd6c48990964fd.2.115.7.js
│ │ ├── 101.1b01bdfb3c9d60c5471d.2.115.7.js
│ │ ├── 102.535071e982bde64c64d4.2.115.7.js
│ │ ├── 104.e57d28ad33e4db6cf22c.2.115.7.js
│ │ ├── 1070187334.html
│ │ ├── 108.c8fd74adfd8217c9214e.2.115.7.js
│ │ ├── 114380.ct.js
│ │ ├── 134.80dd0f31b479096c665e.2.115.7.js
│ │ ├── 135.20f541e7f655e32e73d0.2.115.7.js
│ │ ├── 136.572f017b34120c79b947.2.115.7.js
│ │ ├── 139.379c7db5ddc6ec61eeac.2.115.7.js
│ │ ├── 1676409410248_Feedback_Tab.png
│ │ ├── 20557481387
│ │ ├── 2843_c7924a1eb38cdbc866e2fc72c6d86e86be6343fd6dc8435e35ba157952bc2405_edge_helper.js
│ │ ├── 30.cdeb9c0785e44ad8909d.2.115.7.js
│ │ ├── 3a78db81bceb32d68c532e13623f25759705315c.6764a782f55d485da73f.2.115.7.js
│ │ ├── 3cc687d78f0a822cb32f11c152611119bfbb5e98.c19b0971c1a97e5bfc83.2.115.7.js
│ │ ├── 4069bbf9c7f74fc76b083f89bdc75e46bf10d0a2.b7a95ec527c2f6f5b425.2.115.7.js
│ │ ├── 43ac8cbe-c340-5e59-a319-d54c0a7aeb01
│ │ ├── 4c38c1e13b1255a27ba4891347f2c472283e17fd.e9ca7c4ee2291b158beb.2.115.7.js
│ │ ├── 4d9f93bc734e54364c2416fbd7a54640eb153e85.e7cd41a3608b9b246fb5.2.115.7.js
│ │ ├── 5438854.js
│ │ ├── 610185875725240
│ │ ├── 6443dc27873f66400a297ce0dea0f260e04b080b.ca9423c83050bd7f918f.2.115.7.js
│ │ ├── 6db6164da0a02f4d3bf4aa99299e98d72b8f158c.9b4167ec08e1225880b8.2.115.7.js
│ │ ├── 9799b222b7fc5bd0f6e887f7075b4d136bdd60f4.2d5fb2ce02e90db5744b.2.115.7.js
│ │ ├── 9dfe2cde3f220999092422b236aca7f96e252a82.86b7f29c938ebe51e21c.2.115.7.js
│ │ ├── A372572-d6b3-4464-8175-43afcdedc6dd1.js
│ │ ├── AARFoKQsC
│ │ ├── ADOLUN.jpg
│ │ ├── BLA(1).jpg
│ │ ├── BLA(2).jpg
│ │ ├── BLA(3).jpg
│ │ ├── BLA(4).jpg
│ │ ├── BLA(5).jpg
│ │ ├── BLA(6).jpg
│ │ ├── BLA.jpg
│ │ ├── BLHALAGR.jpg
│ │ ├── BON(1).jpg
│ │ ├── BON.jpg
│ │ ├── CALGRE(1).jpg
│ │ ├── CALGRE.jpg
│ │ ├── CHA(1).jpg
│ │ ├── CHA.jpg
│ │ ├── ContentWall.8d71d5242956c0bd59db.2.115.7.js
│ │ ├── DARBL.jpg
│ │ ├── DARSHA(1).jpg
│ │ ├── DARSHA.jpg
│ │ ├── DUC.jpg
│ │ ├── Details.c2b37b8f169299b41ed5.2.115.7.js
│ │ ├── ELGRTOPR(1).jpg
│ │ ├── ELGRTOPR.jpg
│ │ ├── EVESULSPR(1).jpg
│ │ ├── EVESULSPR.jpg
│ │ ├── EVESULSPR_D1.jpg
│ │ ├── EVESULSPR_D2.jpg
│ │ ├── EVESULSPR_D3.jpg
│ │ ├── EVESULSPR_D4.jpg
│ │ ├── Flyout(1).jpg
│ │ ├── Flyout(2).jpg
│ │ ├── Flyout(3).jpg
│ │ ├── Flyout.jpg
│ │ ├── FlyoutM.jpg
│ │ ├── FlyoutSki.jpg
│ │ ├── FlyoutSnowboard.jpg
│ │ ├── FlyoutW.jpg
│ │ ├── Flyout_StockingStuffers.jpg
│ │ ├── Flyout_kids.jpg
│ │ ├── Flyoutv2.jpg
│ │ ├── GRAJAY.jpg
│ │ ├── GY.jpg
│ │ ├── Klarna.a9455bf32fa3bad78e69.2.115.7.js
│ │ ├── LAVGRA(1).jpg
│ │ ├── LAVGRA.jpg
│ │ ├── OLIGRE.jpg
│ │ ├── ONECOL(1).jpg
│ │ ├── ONECOL.jpg
│ │ ├── OfferDrawerPrompt.dba5345f5c04de21ae23.2.115.7.js
│ │ ├── PEW.jpg
│ │ ├── PHA(1).jpg
│ │ ├── PHA.jpg
│ │ ├── PURSAG(1).jpg
│ │ ├── PURSAG(2).jpg
│ │ ├── PURSAG.jpg
│ │ ├── RD(1).jpg
│ │ ├── RD.jpg
│ │ ├── RED(1).jpg
│ │ ├── RED.jpg
│ │ ├── SHA.jpg
│ │ ├── STA(1).jpg
│ │ ├── STA(2).jpg
│ │ ├── STA(3).jpg
│ │ ├── STA.jpg
│ │ ├── Ski_Flyout.jpg
│ │ ├── Snowboard_Flyout.jpg
│ │ ├── StickyBuyBoxSection.0294d0f0cc72341c5204.2.115.7.js
│ │ ├── Travel_Flyout.jpg
│ │ ├── YL.jpg
│ │ ├── [id]-1d7954067f243968428a.js
│ │ ├── _app-8716f5d1fd5dcdb48842.js
│ │ ├── _buildManifest.js
│ │ ├── _ssgManifest.js
│ │ ├── a44221fddf9b0e4f806a28f0b2d03af174140151.76305ae13c8b15854220.2.115.7.js
│ │ ├── a5269526b9a3807b8d45aded35007c28acd3ec3d.f80c13eba958a4a16d1c.2.115.7.js
│ │ ├── analytics.js
│ │ ├── b6dfbdc868889737690cfb52f785e35befe67ce6.c50c59fce6c18066a8ac.2.115.7.js
│ │ ├── backcountry-chat-module.js.gz
│ │ ├── backcountry-us.attn.tv.js
│ │ ├── backcountry_vT67AH.js
│ │ ├── bat.js
│ │ ├── bv.js
│ │ ├── c8f7fe3b0e41be846d5687592cf2018ff6e22687.e64ad0d896d1d88c3cfa.2.115.7.js
│ │ ├── clarity.js
│ │ ├── commons.66471e926b26ea7fe0bd.2.115.7.js
│ │ ├── connect.html
│ │ ├── css
│ │ ├── css(1)
│ │ ├── cssReset-bbce9172.css
│ │ ├── d9bdbc2d-bf5b-5b1c-9679-a85610202b18
│ │ ├── datadog-logs.js
│ │ ├── di.js
│ │ ├── dtag.js
│ │ ├── ec.js
│ │ ├── ede1f4077b6380d4c3d5fbb0287b65bf846cf415.bbbca8cfbceb95287165.2.115.7.js
│ │ ├── embed.js
│ │ ├── f.txt
│ │ ├── fbevents.js
│ │ ├── fonts.css
│ │ ├── form1699892137519.html
│ │ ├── framework.162f627c77c9dacbdb20.2.115.7.js
│ │ ├── generic1699892387979.js
│ │ ├── gtm.js
│ │ ├── index-569e91c6.js
│ │ ├── index.html
│ │ ├── js
│ │ ├── julian.jpg
│ │ ├── lib.js
│ │ ├── liveform-web-style-79a7d26a8c.css
│ │ ├── liveform-web-vendor-7a445f15ef.css
│ │ ├── load-chat.js
│ │ ├── lp.js
│ │ ├── main-2bf509ede96b7cf0a4aa.js
│ │ ├── messaging.js
│ │ ├── nsjs
│ │ ├── polyfills-9877e4e8f0a2a3974035.js
│ │ ├── retargeting.js
│ │ ├── saved_resource
│ │ ├── saved_resource(1)
│ │ ├── saved_resource(2).html
│ │ ├── saved_resource.html
│ │ ├── serenovawebchat.2.2.3.min.js
│ │ ├── skilength.jpg
│ │ ├── track.v2.js
│ │ ├── unified-tag.js
│ │ ├── universal_pixel.1.1.0.js
│ │ ├── up(1).html
│ │ ├── up.html
│ │ ├── up_loader.1.1.0.js
│ │ ├── venturebeyond.jpg
│ │ └── webpack-e31cc4f3930205349c00.js
├── div-soup.png
├── exercises
│ ├── IMG_0920.JPG
│ ├── hawaii.jpg
│ ├── header.png
│ ├── north-cascades.jpg
│ ├── product-dog-coat
│ │ ├── BLUDUS.jpg
│ │ ├── BLUDUS_D1.jpg
│ │ ├── BLUDUS_D2.jpg
│ │ ├── BLUDUS_D3.jpg
│ │ ├── BLUDUS_D5.jpg
│ │ ├── BLUDUS_D6.jpg
│ │ ├── BLUDUS_D7.jpg
│ │ ├── REDSUM.jpg
│ │ └── WAVORA.jpg
│ ├── product-dog-harness
│ │ ├── BLA(1).jpg
│ │ ├── BLA.jpg
│ │ ├── RED.jpg
│ │ ├── RED_D5.jpg
│ │ ├── RED_D6.jpg
│ │ ├── RED_D7.jpg
│ │ └── RED_D8.jpg
│ ├── product-dog-sleeping-bag
│ │ ├── BLA(1).jpg
│ │ ├── BLA(2).jpg
│ │ ├── BLA(3).jpg
│ │ ├── BLA(4).jpg
│ │ ├── BLA(5).jpg
│ │ ├── BLA(6).jpg
│ │ ├── BLA.jpg
│ │ ├── BLHALAGR.jpg
│ │ ├── BON(1).jpg
│ │ ├── BON.jpg
│ │ ├── CALGRE(1).jpg
│ │ ├── CALGRE.jpg
│ │ ├── CHA(1).jpg
│ │ ├── CHA.jpg
│ │ ├── DARBL.jpg
│ │ ├── DARSHA(1).jpg
│ │ ├── DARSHA.jpg
│ │ ├── ELGRTOPR(1).jpg
│ │ ├── ELGRTOPR.jpg
│ │ ├── EVESULSPR(1).jpg
│ │ ├── EVESULSPR.jpg
│ │ ├── EVESULSPR_D1.jpg
│ │ ├── EVESULSPR_D2.jpg
│ │ ├── EVESULSPR_D3.jpg
│ │ ├── EVESULSPR_D4.jpg
│ │ ├── Flyout(1).jpg
│ │ ├── Flyout(2).jpg
│ │ ├── Flyout(3).jpg
│ │ ├── Flyout.jpg
│ │ ├── FlyoutM.jpg
│ │ ├── FlyoutSki.jpg
│ │ ├── FlyoutSnowboard.jpg
│ │ ├── FlyoutW.jpg
│ │ ├── Flyout_StockingStuffers.jpg
│ │ ├── Flyout_kids.jpg
│ │ ├── Flyoutv2.jpg
│ │ ├── GRAJAY.jpg
│ │ ├── GY.jpg
│ │ ├── LAVGRA(1).jpg
│ │ ├── LAVGRA.jpg
│ │ ├── OLIGRE.jpg
│ │ ├── ONECOL(1).jpg
│ │ ├── ONECOL.jpg
│ │ ├── PEW.jpg
│ │ ├── PHA(1).jpg
│ │ ├── PHA.jpg
│ │ ├── PURSAG(1).jpg
│ │ ├── PURSAG(2).jpg
│ │ ├── PURSAG.jpg
│ │ ├── RD(1).jpg
│ │ ├── RD.jpg
│ │ ├── RED(1).jpg
│ │ ├── RED.jpg
│ │ ├── SHA.jpg
│ │ ├── STA(1).jpg
│ │ ├── STA(2).jpg
│ │ ├── STA(3).jpg
│ │ ├── STA.jpg
│ │ ├── Ski_Flyout.jpg
│ │ ├── Snowboard_Flyout.jpg
│ │ ├── Travel_Flyout.jpg
│ │ ├── YL.jpg
│ │ ├── julian.jpg
│ │ ├── skilength.jpg
│ │ └── venturebeyond.jpg
│ └── soundcloud-axe.jpg
├── firefox.png
├── fm-bio-banner.jpg
├── fm-logo.png
├── html.jpg
├── html.webp
├── javascript.svg
├── js.png
├── lighthouse.svg
├── logo.svg
├── marcy-sutton-todd.jpg
├── marklar.jpg
├── og-image2.png
├── ogimage.png
├── ogimage1.png
├── react.svg
├── reduced-motion.png
├── rwd.jpg
├── safari.png
├── skeleton-loader.jpg
├── the-big-one.jpg
├── tootsie-pop-owl-pic.jpg
├── universal-access-icon.png
└── voiceover-cat-image-links.jpg
├── robots.txt
├── styles.css
├── tailwind.config.js
├── theme.config.js
├── tsconfig.json
├── types
└── index.ts
└── yarn.lock
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "prettier"],
3 | "rules": {
4 | // Allows the use of the `inert` attribute until https://github.com/facebook/react/pull/24730 is merged.
5 | "react/no-unknown-property": ["error", { "ignore": ["inert"] }]
6 | }
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vercel
2 | .next
3 | node_modules
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSameLine": true,
3 | "printWidth": 120,
4 | "semi": true,
5 | "singleQuote": true,
6 | "tabWidth": 2,
7 | "useTabs": true,
8 | "trailingComma": "es5"
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "grammarly.selectors": [
3 | {
4 | "language": "mdx",
5 | "scheme": "file"
6 | }
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Web App Accessibility - Frontend Masters
3 |
4 | This repo includes the public source code for the [course website](https://web-accessibility-v3.vercel.app/) related to the [Frontend Masters course on Web App Accessibility](https://frontendmasters.com/courses/react-accessibility/).
5 |
6 | Topics:
7 |
8 | - Introduction
9 | - Screen Readers and Assistive Technologies
10 | - Accessibility Debugging
11 | - Accessible HTML
12 | - ARIA (Accessible Rich Internet Applications)
13 | - Focus Management
14 | - Visual Considerations
15 | - Wrap-Up
16 |
17 | The base components used in class are in the `components` directory: https://github.com/marcysutton/frontend-masters-web-accessibility-v3/tree/main/components
18 |
19 | More-complete components are in the `completed-components` directory: https://github.com/marcysutton/frontend-masters-web-accessibility-v3/tree/main/completed-components
20 |
21 | ## Setup
22 |
23 | This site uses Next.js and React. To run the project, clone (or download it) from GitHub:
24 |
25 | ```sh
26 | git clone git@github.com:marcysutton/frontend-masters-web-accessibility-v3.git
27 | ```
28 |
29 | Install dependencies with yarn or npm:
30 |
31 | ```sh
32 | yarn
33 | ```
34 |
35 | Run the project locally:
36 |
37 | ```sh
38 | yarn run dev
39 | ```
40 |
41 | Note: These slides are based on the [JavaScript Patterns repo](https://github.com/lydiahallie/javascript-react-patterns) from Lydia Hallie.
42 |
--------------------------------------------------------------------------------
/completed-components/exercise-3-html/ProductImageGallery.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Image } from '@chakra-ui/react';
3 | import type { ImageData } from '../../types';
4 |
5 | type ProductImageGalleryProps = {
6 | imageData: ImageData;
7 | onFullscreenOpen: () => void;
8 | onFullscreenClose: () => void;
9 | };
10 | const ProductImageGallery = ({ imageData, onFullscreenOpen, onFullscreenClose }: ProductImageGalleryProps) => {
11 | const [fullscreenImage, setFullscreenImage] = useState(null);
12 |
13 | const openFullscreen = () => {
14 | onFullscreenOpen();
15 | };
16 |
17 | const closeFullscreen = () => {
18 | onFullscreenClose();
19 | setFullscreenImage(null);
20 | };
21 | useEffect(() => {
22 | if (fullscreenImage) {
23 | onFullscreenOpen();
24 | } else {
25 | onFullscreenClose();
26 | }
27 | }, [fullscreenImage]);
28 | return (
29 | <>
30 |
33 |
34 |
37 |
38 | {imageData.galleryImages.map((image, index) => (
39 |
42 | ))}
43 |
44 | {!!fullscreenImage && (
45 |
46 |
52 |

53 |
54 | )}
55 | >
56 | );
57 | };
58 | export default ProductImageGallery;
59 |
--------------------------------------------------------------------------------
/completed-components/exercise-3-html/ProductPage.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import Head from 'next/head';
3 | import { ChakraProvider, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, HStack, Text } from '@chakra-ui/react';
4 |
5 | import ProductHeader from './ProductHeader';
6 | import ProductDetails from './ProductDetails';
7 | import ProductImageGallery from './ProductImageGallery';
8 | import { IconBack } from '../../components/Icons';
9 |
10 | import type { Product } from '../../types';
11 |
12 | type ProductPageProps = {
13 | productData: Product;
14 | };
15 |
16 | const ProductPage = ({ productData }: ProductPageProps) => {
17 | const [shoppingCartItems, updateShoppingCartItems] = useState([]);
18 | const [isFullscreenShowing, setFullscreenShowing] = useState(false);
19 |
20 | const onFullscreen = () => {
21 | setFullscreenShowing(true);
22 | };
23 | const onFullscreenClose = () => {
24 | setFullscreenShowing(false);
25 | };
26 | return (
27 |
28 |
29 |
30 | {productData.productTitle} by {productData.companyName} - Background
31 |
32 |
33 |
37 |
38 |
39 |
40 | }>
41 | < Back
42 |
43 | /
44 |
45 |
46 |
47 | Hike & Camp
48 |
49 |
50 |
51 |
52 | {productData.breadcrumb.title}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
onFullscreen()}
62 | onFullscreenClose={() => onFullscreenClose()}
63 | />
64 |
65 |
68 |
69 |
70 |
71 |
72 | );
73 | };
74 | export default ProductPage;
75 |
--------------------------------------------------------------------------------
/completed-components/exercise-4-ARIA/ProductImageGallery.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Image } from '@chakra-ui/react';
3 | import type { ImageData } from '../../types';
4 |
5 | type ProductImageGalleryProps = {
6 | imageData: ImageData;
7 | onFullscreenOpen: () => void;
8 | onFullscreenClose: () => void;
9 | };
10 | const ProductImageGallery = ({ imageData, onFullscreenOpen, onFullscreenClose }: ProductImageGalleryProps) => {
11 | const [fullscreenImage, setFullscreenImage] = useState(null);
12 |
13 | const openFullscreen = () => {
14 | onFullscreenOpen();
15 | };
16 |
17 | const closeFullscreen = () => {
18 | onFullscreenClose();
19 | setFullscreenImage(null);
20 | };
21 | useEffect(() => {
22 | if (fullscreenImage) {
23 | onFullscreenOpen();
24 | } else {
25 | onFullscreenClose();
26 | }
27 | }, [fullscreenImage]);
28 | return (
29 | <>
30 |
33 |
34 |
37 |
38 | {imageData.galleryImages.map((image, index) => (
39 |
42 | ))}
43 |
44 | {!!fullscreenImage && (
45 |
46 |
closeFullscreen()}
50 | role="button">
51 | X
52 |
53 |

54 |
55 | )}
56 | >
57 | );
58 | };
59 | export default ProductImageGallery;
60 |
--------------------------------------------------------------------------------
/completed-components/exercise-4-ARIA/ProductPage.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { ChakraProvider, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, HStack, Text } from '@chakra-ui/react';
3 |
4 | import ProductHeader from './ProductHeader';
5 | import ProductDetails from './ProductDetails';
6 | import ProductImageGallery from './ProductImageGallery';
7 | import { IconBack } from '../../components/Icons';
8 |
9 | import type { Product } from '../../types';
10 |
11 | type ProductPageProps = {
12 | productData: Product;
13 | };
14 |
15 | const ProductPage = ({ productData }: ProductPageProps) => {
16 | const [shoppingCartItems, updateShoppingCartItems] = useState([]);
17 | const [isFullscreenShowing, setFullscreenShowing] = useState(false);
18 |
19 | const onAddToCart = (product) => {
20 | const items = [...shoppingCartItems, product];
21 | updateShoppingCartItems(items);
22 | };
23 |
24 | const onFullscreen = () => {
25 | setFullscreenShowing(true);
26 | };
27 | const onFullscreenClose = () => {
28 | setFullscreenShowing(false);
29 | };
30 | return (
31 |
32 |
36 |
37 |
38 |
39 | }>
40 | < Back
41 |
42 | /
43 |
44 |
45 |
46 | Hike & Camp
47 |
48 |
49 |
50 |
51 | {productData.breadcrumb.title}
52 |
53 |
54 |
55 |
56 |
57 |
58 |
onFullscreen()}
61 | onFullscreenClose={() => onFullscreenClose()}
62 | />
63 |
64 |
67 |
68 |
69 |
70 |
71 | );
72 | };
73 | export default ProductPage;
74 |
--------------------------------------------------------------------------------
/completed-components/exercise-5-focus/ProductImageGallery.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Image, Modal, ModalOverlay, ModalContent, ModalBody, ModalCloseButton } from '@chakra-ui/react';
3 | import type { ImageData } from '../../types';
4 |
5 | type ProductImageGalleryProps = {
6 | imageData: ImageData;
7 | onFullscreenOpen: () => void;
8 | onFullscreenClose: () => void;
9 | };
10 | const ProductImageGallery = ({ imageData, onFullscreenOpen, onFullscreenClose }: ProductImageGalleryProps) => {
11 | const [fullscreenImage, setFullscreenImage] = useState(null);
12 |
13 | const openFullscreen = () => {
14 | onFullscreenOpen();
15 | };
16 |
17 | const closeFullscreen = () => {
18 | onFullscreenClose();
19 | setFullscreenImage(null);
20 | };
21 | useEffect(() => {
22 | if (fullscreenImage) {
23 | onFullscreenOpen();
24 | } else {
25 | onFullscreenClose();
26 | }
27 | }, [fullscreenImage]);
28 | return (
29 | <>
30 |
33 |
34 |
37 |
38 | {imageData.galleryImages.map((image, index) => (
39 |
42 | ))}
43 |
44 | {!!fullscreenImage && (
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | )}
55 | >
56 | );
57 | };
58 | export default ProductImageGallery;
59 |
--------------------------------------------------------------------------------
/completed-components/exercise-5-focus/ProductPage.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { ChakraProvider, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, HStack, Text } from '@chakra-ui/react';
3 |
4 | import ProductHeader from './ProductHeader';
5 | import ProductDetails from './ProductDetails';
6 | import ProductImageGallery from './ProductImageGallery';
7 | import { IconBack } from '../../components/Icons';
8 |
9 | import type { Product } from '../../types';
10 |
11 | type ProductPageProps = {
12 | productData: Product;
13 | };
14 |
15 | const ProductPage = ({ productData }: ProductPageProps) => {
16 | const [shoppingCartItems, updateShoppingCartItems] = useState(null);
17 | const [isFullscreenShowing, setFullscreenShowing] = useState(false);
18 |
19 | const onAddToCart = ({ product }) => {};
20 |
21 | const onFullscreen = () => {
22 | setFullscreenShowing(true);
23 | };
24 | const onFullscreenClose = () => {
25 | setFullscreenShowing(false);
26 | };
27 | return (
28 |
29 |
33 |
34 |
35 |
36 | }>
37 | < Back
38 |
39 | /
40 |
41 |
42 |
43 | Hike & Camp
44 |
45 |
46 |
47 |
48 | {productData.breadcrumb.title}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
onFullscreen()}
58 | onFullscreenClose={() => onFullscreenClose()}
59 | />
60 |
61 |
64 |
65 |
66 |
67 |
68 | );
69 | };
70 | export default ProductPage;
71 |
--------------------------------------------------------------------------------
/completed-components/exercise-6-motion/ProductImageGallery.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Image } from '@chakra-ui/react';
3 | import type { ImageData } from '../../types';
4 |
5 | type ProductImageGalleryProps = {
6 | imageData: ImageData;
7 | onFullscreenOpen: () => void;
8 | onFullscreenClose: () => void;
9 | };
10 | const ProductImageGallery = ({ imageData, onFullscreenOpen, onFullscreenClose }: ProductImageGalleryProps) => {
11 | const [fullscreenImage, setFullscreenImage] = useState(null);
12 |
13 | const openFullscreen = () => {
14 | onFullscreenOpen();
15 | };
16 |
17 | const closeFullscreen = () => {
18 | onFullscreenClose();
19 | setFullscreenImage(null);
20 | };
21 | useEffect(() => {
22 | if (fullscreenImage) {
23 | onFullscreenOpen();
24 | } else {
25 | onFullscreenClose();
26 | }
27 | }, [fullscreenImage]);
28 | return (
29 | <>
30 |
33 |
34 |
37 |
38 | {imageData.galleryImages.map((image, index) => (
39 |
42 | ))}
43 |
44 | {!!fullscreenImage && (
45 |
46 |
closeFullscreen()}
50 | role="button">
51 | X
52 |
53 |

54 |
55 | )}
56 | >
57 | );
58 | };
59 | export default ProductImageGallery;
60 |
--------------------------------------------------------------------------------
/completed-components/exercise-6-motion/ProductPage.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { ChakraProvider, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, HStack, Text } from '@chakra-ui/react';
3 |
4 | import ProductHeader from './ProductHeader';
5 | import ProductDetails from './ProductDetails';
6 | import ProductImageGallery from './ProductImageGallery';
7 | import { IconBack } from '../../components/Icons';
8 |
9 | import type { Product } from '../../types';
10 |
11 | type ProductPageProps = {
12 | productData: Product;
13 | shouldAnimate?: boolean;
14 | };
15 |
16 | const ProductPage = ({ productData, shouldAnimate = false }: ProductPageProps) => {
17 | const [shoppingCartItems, updateShoppingCartItems] = useState(null);
18 | const [isFullscreenShowing, setFullscreenShowing] = useState(false);
19 |
20 | const onFullscreen = () => {
21 | setFullscreenShowing(true);
22 | };
23 | const onFullscreenClose = () => {
24 | setFullscreenShowing(false);
25 | };
26 | return (
27 |
28 |
32 |
33 |
34 |
35 | }>
36 | < Back
37 |
38 | /
39 |
40 |
41 |
42 | Hike & Camp
43 |
44 |
45 |
46 |
47 | {productData.breadcrumb.title}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
onFullscreen()}
57 | onFullscreenClose={() => onFullscreenClose()}
58 | />
59 |
60 |
63 |
64 |
65 |
66 |
67 | );
68 | };
69 | export default ProductPage;
70 |
--------------------------------------------------------------------------------
/components/ProductImageGallery.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Image } from '@chakra-ui/react';
3 | import type { ImageData } from '../types';
4 |
5 | type ProductImageGalleryProps = {
6 | imageData: ImageData;
7 | onFullscreenOpen: () => void;
8 | onFullscreenClose: () => void;
9 | };
10 | const ProductImageGallery = ({ imageData, onFullscreenOpen, onFullscreenClose }: ProductImageGalleryProps) => {
11 | const [fullscreenImage, setFullscreenImage] = useState(null);
12 |
13 | const openFullscreen = () => {
14 | onFullscreenOpen();
15 | };
16 |
17 | const closeFullscreen = () => {
18 | onFullscreenClose();
19 | setFullscreenImage(null);
20 | };
21 | useEffect(() => {
22 | if (fullscreenImage) {
23 | onFullscreenOpen();
24 | } else {
25 | onFullscreenClose();
26 | }
27 | }, [fullscreenImage]);
28 | return (
29 | <>
30 |
33 |
34 |
setFullscreenImage(imageData.mainImage)}>
35 |
36 |
37 |
38 | {imageData.galleryImages.map((image, index) => (
39 |
40 |

41 |
42 | ))}
43 |
44 | {!!fullscreenImage && (
45 |
46 |
closeFullscreen()}>
50 | X
51 |
52 |

53 |
54 | )}
55 | >
56 | );
57 | };
58 | export default ProductImageGallery;
59 |
--------------------------------------------------------------------------------
/components/ProductPage.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { ChakraProvider, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, HStack, Text } from '@chakra-ui/react';
3 |
4 | import ProductHeader from './ProductHeader';
5 | import ProductDetails from './ProductDetails';
6 | import ProductImageGallery from './ProductImageGallery';
7 | import { IconBack } from './Icons';
8 |
9 | import type { Product } from '../types';
10 |
11 | type ProductPageProps = {
12 | productData: Product;
13 | shouldAnimate?: boolean;
14 | };
15 |
16 | const ProductPage = ({ productData, shouldAnimate = false }: ProductPageProps) => {
17 | const [shoppingCartItems, updateShoppingCartItems] = useState(null);
18 | const [isFullscreenShowing, setFullscreenShowing] = useState(false);
19 |
20 | const onFullscreen = () => {
21 | setFullscreenShowing(true);
22 | };
23 | const onFullscreenClose = () => {
24 | setFullscreenShowing(false);
25 | };
26 | return (
27 |
28 |
32 |
33 |
34 |
35 | }>
36 | < Back
37 |
38 | /
39 |
40 |
41 |
42 | Hike & Camp
43 |
44 |
45 |
46 |
47 | {productData.breadcrumb.title}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
onFullscreen()}
57 | onFullscreenClose={() => onFullscreenClose()}
58 | />
59 |
60 |
63 |
64 |
65 |
66 |
67 | );
68 | };
69 | export default ProductPage;
70 |
--------------------------------------------------------------------------------
/components/ScreenReaderImageGallery.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const ScreenReaderImageGallery = () => {
4 | return (
5 |
6 |
7 |
12 |
13 | Make your escape to the North Cascades.
14 |
15 |
16 |
17 |

18 |
Hawaii calls!
19 |
20 |
21 | );
22 | }
23 |
24 | export default ScreenReaderImageGallery;
25 |
--------------------------------------------------------------------------------
/components/slides/DropCap.tsx:
--------------------------------------------------------------------------------
1 | export const DropCapQuote = ({children}) => {
2 | return (
3 |
5 | {children}
6 |
7 | )
8 | }
9 |
10 | export const DropCapGrid = ({children}) => (
11 |
12 | {children}
13 |
14 | )
15 |
16 | export const DropCapEmoji = ({children}) => {
17 | return (
18 |
19 | {children}
20 |
21 | )
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/components/slides/Links.tsx:
--------------------------------------------------------------------------------
1 | const Links = ({children}) => {
2 | return (
3 |
4 |
Links
5 | {children}
6 |
7 | )
8 | }
9 |
10 | export default Links
--------------------------------------------------------------------------------
/components/slides/StackBlitz.tsx:
--------------------------------------------------------------------------------
1 | export default function StackBlitz(props: {
2 | name: string;
3 | height?: number;
4 | view?: "preview" | "editor",
5 | openFile?: string;
6 | hideExplorer?: boolean;
7 | showDevtools?: boolean;
8 | }) {
9 |
10 | return (
11 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/custom.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --field-bg-color: field;
3 | --ol-item-bg-color: white;
4 | --ol-item-text-color: black;
5 | }
6 | .light:root {
7 | --field-bg-color: #edecec;
8 | --ol-item-bg-color: #333;
9 | --ol-item-text-color: white;
10 | }
11 |
12 | html {
13 | font-size: 18px;
14 | }
15 |
16 | .nextra-body.full {
17 | margin: 0 auto;
18 | }
19 |
20 | code {
21 | @apply text-sm;
22 | }
23 |
24 | .dark .invert-on-dark {
25 | filter: invert(1) brightness(1.8);
26 | }
27 |
28 | .nextra-toc-meta {
29 | display: none;
30 | }
31 |
32 | code {
33 | font-family: "Menlo", PT Mono,Consolas,Monaco,"Andale Mono","Ubuntu Mono",monospace !important;
34 | }
35 |
36 | video {
37 | /* border: 2px solid #262626;
38 | border-radius: 10px; */
39 | width: 100%;
40 | margin: 2em 0;
41 | /* padding: 0 2em;
42 | background: black; */
43 | }
44 |
45 | .dark p, .dark ol, .dark li {
46 | color: #e3e3e3;
47 | }
48 | .light p, .light ol, .light li {
49 | color: #000;
50 | }
51 | .dark .demo p,
52 | .light .demo p {
53 | color: inherit;
54 | }
55 | .styled-list ol {
56 | counter-reset: section;
57 | }
58 | .styled-list ol > li {
59 | counter-increment: section;
60 | position: relative;
61 | break-inside: avoid;
62 | }
63 | .styled-list ol > li::before {
64 | background-color: var(--ol-item-bg-color);
65 | border-radius: 50%;
66 | box-sizing: content-box;
67 | color: var(--ol-item-text-color);
68 | content: counter(section);
69 | font-size: 0.85rem;
70 | display: inline-block;
71 | font-weight: bold;
72 | height: 1rem;
73 | line-height: 1rem;
74 | left: -1.5rem;
75 | padding: 2px;
76 | position: absolute;
77 | top: 3px;
78 | text-align: center;
79 | width: 1rem;
80 | }
81 | .styled-list ol > li::marker {
82 | color: var(--ol-item-text-color);
83 | font-size: 0.85rem;
84 | margin-right: 0.5em;
85 | }
86 | .overview-columns {
87 | column-count: 2;
88 | column-gap: 20px;
89 | }
90 | iframe {
91 | border-radius: 7px;
92 | margin: 2em 0;
93 | }
94 |
95 | p > strong {
96 | color: #FF42A1;
97 | }
98 |
99 | .nextra-sidebar-menu {
100 | box-shadow: none !important;
101 | }
102 |
103 | .dark .nextra-sidebar-menu > div {
104 | background: black !important;
105 | }
106 |
107 | .light .nextra-sidebar-menu > div {
108 | background: white !important;
109 | }
110 |
111 | a > h1 {
112 | font-weight: 500;
113 | }
114 |
115 | ul.first\:nx-mt-0 {
116 | margin-top: 0;
117 | }
118 |
119 | main a {
120 | font-weight: bold;
121 | }
122 | .demo .badge {
123 | background-color: red;
124 | border-radius: 50%;
125 | color: white;
126 | font-size: 1rem;
127 | font-weight: bold;
128 | height: 1.2rem;
129 | line-height: 1.2rem;
130 | position: absolute;
131 | right: 2px;
132 | text-align: center;
133 | top: -.5rem;
134 | width: 1.2rem;
135 | }
--------------------------------------------------------------------------------
/data/index.tsx:
--------------------------------------------------------------------------------
1 | import { Product } from '../types';
2 |
3 | export const ProductDogCoat: Product = {
4 | breadcrumb: { title: 'Dog Coats, Packs & PFDs', slug: '/dog-coats-packs-pfds' },
5 | companyName: 'Ruffwear',
6 | companySlug: '/ruffwear',
7 | productTitle: 'K9 Float Coat',
8 | overallRating: '5 out of 5 stars',
9 | reviews: [
10 | {
11 | rating: 5,
12 | comment: 'So glad I bought this',
13 | },
14 | ],
15 | assetPath: '/exercises/product-dog-coat/',
16 | price: '$89.95',
17 | colors: ['Blue Dusk', 'Red Sumac', 'Wave Orange'],
18 | colorwayImages: ['BLUDUS.jpg', 'REDSUM.jpg', 'WAVORA.jpg'],
19 | sizes: ['XXS', 'XS', 'S', 'L', 'XL'],
20 | images: {
21 | imagePath: '/exercises/product-dog-coat/',
22 | mainImage: {
23 | src: 'BLUDUS.jpg',
24 | alt: 'Ruffwear - K-9 Float Coat - Blue Dusk',
25 | },
26 | galleryImages: [
27 | {
28 | src: 'BLUDUS_D7.jpg',
29 | },
30 | {
31 | src: 'BLUDUS_D6.jpg',
32 | },
33 | {
34 | src: 'BLUDUS_D5.jpg',
35 | },
36 | {
37 | src: 'BLUDUS_D3.jpg',
38 | },
39 | {
40 | src: 'BLUDUS_D2.jpg',
41 | },
42 | {
43 | src: 'BLUDUS_D1.jpg',
44 | },
45 | ],
46 | },
47 | };
48 | export const ProductDogHarness = {
49 | breadcrumb: { title: 'Leashes, Harnesses & Collars', slug: '/dog-coats-packs-pfds' },
50 | companyName: 'Fido Pro',
51 | companySlug: '/fido-pro',
52 | productTitle: 'Panza Harness + Deployable Emergency Dog Rescue Sling',
53 | overallRating: '2.75 out of 5 stars',
54 | reviews: [],
55 | price: '$139.00',
56 | assetPath: '/exercises/product-dog-harness/',
57 | colors: ['Black', 'Red'],
58 | colorwayImages: ['/BLA.jpg', 'RED.jpg'],
59 | sizes: ['One Size'],
60 | images: {
61 | imagePath: '/exercises/product-dog-harness/',
62 | mainImage: {
63 | src: 'BLA(1).jpg',
64 | alt: 'Fido Pro - Panza Harness + Deployable Emergency Dog Rescue Sling - Black',
65 | },
66 | galleryImages: [
67 | {
68 | src: 'RED_D7.jpg',
69 | },
70 | {
71 | src: 'RED_D6.jpg',
72 | },
73 | {
74 | src: 'RED_D5.jpg',
75 | },
76 | {
77 | src: 'RED_D8.jpg',
78 | },
79 | ],
80 | },
81 | };
82 |
--------------------------------------------------------------------------------
/declarations.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | //TODO: remove this when inert is supported in React types
3 | namespace JSX {
4 | interface IntrinsicAttributes {
5 | /**
6 | * Indicates that the browser will ignore this element and its descendants,
7 | * preventing some interactions and hiding it from assistive technology.
8 | * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inert
9 | * @todo Remove this stub declaration after https://github.com/facebook/react/pull/24730 is merged.
10 | */
11 | inert?: '';
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/hooks/use-reduced-motion.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const QUERY = '(prefers-reduced-motion: no-preference)';
4 |
5 | function usePrefersReducedMotion() {
6 | const [prefersReducedMotion, setPrefersReducedMotion] = React.useState(true);
7 | React.useEffect(() => {
8 | const mediaQueryList = window.matchMedia(QUERY);
9 | // Set the true initial value, now that we're on the client:
10 | setPrefersReducedMotion(!window.matchMedia(QUERY).matches);
11 |
12 | const listener = (event) => {
13 | setPrefersReducedMotion(!event.matches);
14 | };
15 | mediaQueryList.addEventListener('change', listener);
16 | return () => {
17 | mediaQueryList.removeEventListener('change', listener);
18 | };
19 | }, []);
20 | return prefersReducedMotion;
21 | }
22 | export default usePrefersReducedMotion;
23 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const withNextra = require("nextra")({
4 | theme: "nextra-theme-docs",
5 | themeConfig: "./theme.config.js",
6 | });
7 |
8 | const baseOptions = {
9 | sassOptions: {
10 | includePaths: [path.join(__dirname, 'styles')],
11 | },
12 | }
13 |
14 | module.exports = Object.assign({}, withNextra({
15 | // reactStrictMode: true,
16 | async redirects() {
17 | return [
18 | {
19 | source: '/',
20 | destination: '/topics',
21 | permanent: true,
22 | },
23 | ]
24 | },
25 | }), baseOptions);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-accessibility-v3",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next",
7 | "start": "next start",
8 | "build": "next build ",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@chakra-ui/react": "^2.8.2",
13 | "@emotion/react": "^11.11.1",
14 | "@emotion/styled": "^11.11.0",
15 | "@heroicons/react": "1.0.5",
16 | "@react-aria/live-announcer": "^3.3.1",
17 | "@react-aria/ssr": "3.1.2",
18 | "framer-motion": "^10.16.5",
19 | "next": "^13.4.19",
20 | "nextra": "^2.12.3",
21 | "nextra-theme-docs": "^2.12.3",
22 | "react": "18",
23 | "react-dom": "^18.2.0",
24 | "sass": "^1.69.5",
25 | "tailwind-merge": "^2.1.0"
26 | },
27 | "devDependencies": {
28 | "@types/node": "^18.7.13",
29 | "@types/react": "17.0.37",
30 | "autoprefixer": "10.2.6",
31 | "eslint": "^8.55.0",
32 | "eslint-config-prettier": "^9.0.0",
33 | "postcss": "8.3.5",
34 | "tailwindcss": "3.3",
35 | "typescript": "^4.8.2"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/pages/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcysutton/frontend-masters-web-accessibility-v3/b130bc1d81af6de904ed622e1947aebe4c3a52a6/pages/.DS_Store
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "../styles.css";
2 | import "nextra-theme-docs/style.css";
3 | import '../custom.css';
4 | import { SSRProvider } from "@react-aria/ssr";
5 |
6 | // Shim requestIdleCallback in Safari
7 | if (typeof window !== "undefined" && !("requestIdleCallback" in window)) {
8 | window.requestIdleCallback = (fn) => setTimeout(fn, 1);
9 | window.cancelIdleCallback = (e) => clearTimeout(e);
10 | }
11 |
12 | export default function Nextra({ Component, pageProps }) {
13 | const getLayout = Component.getLayout || ((page) => page);
14 |
15 | return getLayout(
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/google-font-display */
2 |
3 | import Document, { Html, Head, Main, NextScript } from "next/document";
4 |
5 | class MyDocument extends Document {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
28 | export default MyDocument;
29 |
--------------------------------------------------------------------------------
/pages/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "*": {
3 | "type": "page"
4 | },
5 | "topics": {
6 | "title": "All Topics"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import React from 'react';
3 |
4 | export default function Index() {
5 | const router = useRouter();
6 |
7 | React.useEffect(() => {
8 | router.push('/topics');
9 | }, []);
10 |
11 | return null;
12 | }
13 |
--------------------------------------------------------------------------------
/pages/product-page-1.tsx:
--------------------------------------------------------------------------------
1 | import ProductPage from '../components/ProductPage';
2 | import { ProductDogCoat as Product } from '../data';
3 |
4 | const FullProductPage = () => ;
5 |
6 | export default FullProductPage;
7 |
--------------------------------------------------------------------------------
/pages/product-page-exercise-3.tsx:
--------------------------------------------------------------------------------
1 | import ProductPage from '../completed-components/exercise-3-html/ProductPage';
2 | import { ProductDogCoat as Product } from '../data';
3 |
4 | const FullProductPage = () => ;
5 |
6 | export default FullProductPage;
7 |
--------------------------------------------------------------------------------
/pages/product-page-exercise-4.tsx:
--------------------------------------------------------------------------------
1 | import ProductPage from '../completed-components/exercise-4-ARIA/ProductPage';
2 | import { ProductDogCoat as Product } from '../data';
3 |
4 | const FullProductPage = () => ;
5 |
6 | export default FullProductPage;
7 |
--------------------------------------------------------------------------------
/pages/product-page-exercise-5.tsx:
--------------------------------------------------------------------------------
1 | import ProductPage from '../completed-components/exercise-5-focus/ProductPage';
2 | import { ProductDogCoat as Product } from '../data';
3 |
4 | const FullProductPage = () => ;
5 |
6 | export default FullProductPage;
7 |
--------------------------------------------------------------------------------
/pages/product-page-exercise-6.tsx:
--------------------------------------------------------------------------------
1 | import ProductPage from '../completed-components/exercise-6-motion/ProductPage';
2 | import { ProductDogCoat as Product } from '../data';
3 |
4 | const FullProductPage = () => ;
5 |
6 | export default FullProductPage;
7 |
--------------------------------------------------------------------------------
/pages/topics/ARIA/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "introduction": "What is ARIA?",
3 | "roles-states-properties": "Roles, States, & Properties",
4 | "accessible-names-descriptions": "Accessible Names & Descriptions",
5 | "live-regions": "Live Regions",
6 | "exercise-4": "Exercise 4 - Write & Test ARIA Code"
7 | }
--------------------------------------------------------------------------------
/pages/topics/ARIA/accessible-names-descriptions.mdx:
--------------------------------------------------------------------------------
1 | # Accessible Names and Descriptions
2 |
3 | ARIA brings with it a foundational concept for all HTML markup: **accessible names and descriptions**.
4 |
5 | The text content or value exposed from a default button, link, form input, or even a heading is referred to as an “accessible name”.
6 |
7 | Text exposed on an element with a `title` attribute is called an “accessible description.” Descriptions are announced in a screen reader after a bit of a delay and can be configured on or off (so don’t count on them being announced). Descriptions can also fill in as accessible names when there is no accessible name otherwise.
8 |
9 | Both types of content are important in Assistive Technology: what is that element even for? The accessible name or description should give users that information.
10 |
11 | ## Aria-label, aria-labelledby, & aria-describedby
12 |
13 | You can also bolt on both accessible names and descriptions using explicit ARIA attributes like `aria-label`, `aria-labelledby`, and `aria-describedby`!
14 |
15 | You can put a string value in `aria-label` on an element with a qualifying role, like a `