├── .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 | {fullscreenImage.alt} 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 | 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 |
66 | 67 |
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 | {fullscreenImage.alt} 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 | 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 |
65 | 66 |
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 | {fullscreenImage.alt} 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 | 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 |
62 | 63 |
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 | {fullscreenImage.alt} 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 | 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 |
61 | 62 |
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 | 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 |
61 | 62 |
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 | a dirt path next to a small lake, with Fall colors of red, yellow and green. The sky is blue with wispy high clouds. 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 |