Advanced HTML for Performance and Accessibility

A presentation at NDC Sydney 2024 in February 2024 in Sydney NSW, Australia by Mandy Michael

Slide 1

Slide 1

Advanced HTML for Good Developers

Slide 2

Slide 2

Advanced HTML for Good Developers

Slide 3

Slide 3

THANKS BURNT TOAST CREATIVE @burnttoastcre8v @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 4

Slide 4

Jello MANDY MICHAEL Sta Software Engineer Google Developer Expert Microsoft Regional Director ff @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 5

Slide 5

Advanced HTML for Good Developers

Slide 6

Slide 6

HTML for Performance & Accessibility

Slide 7

Slide 7

ACCESSIBILITY : Accessibility is the practice of making your websites & applications usable by as many people as possible. https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 8

Slide 8

WEB PERFORMANCE : Web performance is the objective measurement and perceived user experience of a website or application. https://developer.mozilla.org/en-US/docs/Learn/Performance/What_is_web_performance @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 9

Slide 9

HTML IS SIMPLE & VERY FRIENDLY. @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 10

Slide 10

WE MAKE THE MISTAKE OF ASSUMING BECAUSE IT’S SIMPLE IT’S NOT VALUABLE @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 11

Slide 11

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 12

Slide 12

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 13

Slide 13

<html> … <body> <header> <h1>Some text</h1> </header> <p>A paragraph…</p> <ul> <li>A list item</li> <li>Another item</li> </ul> <p>More content…</p> <hr> @mandy_kerr mandykerr @mandymichael@bsky.social …. @mandymichael@front-end.social

Slide 14

Slide 14

THE DOM TREE IS CONSTRUCTED OFF WHAT WE PROVIDE. @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 15

Slide 15

TYPE SCRIPT @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 16

Slide 16

interface dog { name: string age: number isFluffy: boolean } @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 17

Slide 17

WE DETERMINE WHAT WE EXPECT THE DATA TO BE @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 18

Slide 18

<html> <body> <header> <h1>Dogs: They are good</h1> </header> <main> <h2>Why are dogs good?</h2> <p>All dogs are good, they are the goodest doggies.</p> <figure> <img href=”dog.png” alt=”A white golden retriever, with his head dropping to the side and tongue dangling out of his mouth” /> <figcaption>Michaelangelo AKA “Jello”</figcaption> </figure> </main> </body> </html>

Slide 19

Slide 19

interface dog { name: any age: any isFluffy: any } @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 20

Slide 20

<html> <body> <div> <div>Dogs: They are good</div> </div> <div> <div>Why are dogs good?</div> <div>All dogs are good, they are the goodest doggies.</div> <div> <img href=”dog.png” alt=”A golden retriever” /> <div>Michaelangelo AKA “Jello”</div> </div> </div> </body> </html>

Slide 21

Slide 21

IF YOU CHOOSE A GENERIC ELEMENT YOU GET A GENERIC OUTPUT @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 22

Slide 22

JUST <DIV> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 23

Slide 23

MEANINGFUL HTML @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 24

Slide 24

NOT EVERYONE INTERACTS THE SAME @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 25

Slide 25

ACCESSIBILITY TREE @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 26

Slide 26

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 27

Slide 27

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 28

Slide 28

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 29

Slide 29

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 30

Slide 30

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 31

Slide 31

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 32

Slide 32

OVERLOOKING HTML CAN LEAD TO A LARGE COMPLEX DOM TREE. @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 33

Slide 33

A LARGE DOM WILL IMPACT LOAD TIMES @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 34

Slide 34

A LARGE DOM TREE CAN SLOW DOWN RENDERING & AFFECT RUNTIME PERFORMANCE @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 35

Slide 35

GOOGLE RECOMMENDS Less than 1,500 nodes. warning at 800 nodes Max depth of 32 nodes No parent node with more than 60 child nodes @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 36

Slide 36

CREATE NODES ONLY WHEN NEEDED. DESTROY AFTER @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 37

Slide 37

ANY PERFORMANCE IMPACTS ON THE DOM FURTHER IMPACT THE ACCESSIBILITY TREE @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 38

Slide 38

When the accessibility tree is slowed down by excessive code, it can create a lack of synchronization between the current state of the page and what is being reported by assistive tech ” Eric Bailey @mandy_kerr mandykerr @mandymichael@bsky.social ” @mandymichael@front-end.social

Slide 39

Slide 39

A LARGE COMPLICATED ACCESSIBILITY TREE CAN CRASH THE BROWSER @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 40

Slide 40

EVERYTHING ADDS UP @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 41

Slide 41

DON’T JUST USE DIVS @mandy_kerr batmandy @mandymichael@bsky.social @mandymichael@front-end.social

Slide 42

Slide 42

USE THE NAMED ELEMENT FOR WHAT YOU ARE BUILDING @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 43

Slide 43

If you have a header, use the <header> element <header>This is a header</header> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 44

Slide 44

HTML is intentionally simple, elements are named to help you out. @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 45

Slide 45

Use heading elements in the correct order <h1> <h2> <h3> <h4> <h5> <h6> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 46

Slide 46

THE ROTOR CTRL+OPT+U @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 47

Slide 47

MAKE THE MOST OF BUILT-IN FUNCTIONALITY @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 48

Slide 48

<details> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 49

Slide 49

<datalist> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 50

Slide 50

<input type=“date”> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 51

Slide 51

JUST BECAUSE IT’S IN A DESIGN DOES NOT MEAN YOU HAVE TO DO IT. @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 52

Slide 52

<dialog> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 53

Slide 53

Popover <button popovertarget=”mypopover”> Toggle </button> <div id=”mypopover” popover> Popover content </div> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 54

Slide 54

I AM A BUTTON @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 55

Slide 55

<div onclick=”doSomething();”> I am a button </div> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 56

Slide 56

If you need a button, use the <button> element <button>Amazing button</button> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 57

Slide 57

<div> FEATURES <button> Clickable / Tapable Focusable Keyboard Interaction Announce via assistive tech Submit / Reset Forms Disabled attribute Reported to accessibility tree :active, :hover, :focus & :disabled states @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 58

Slide 58

<div tabindex=”0” role=”button” aria-pressed=“button” onclick=”doSomething();”> I am a button </div> const ENTER = 13; const SPACE = 32; myButton.addEventListener(‘keydown’, function(event) { if (event.keyCode === ENTER || event.keyCode === SPACE) { event.preventDefault(); doSomething(event); } }); function toggleButton(button) { button.setAttribute(“aria-pressed”, button.getAttribute(“aria-pressed”) === “true” ? “false” : “true” )}; } function doSomething() { // Your actual code to do what you want )}; @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 59

Slide 59

<div tabindex=”0” role=”button” aria-pressed=“button” onclick=”doSomething();”> I am a button </div> const ENTER = 13; const SPACE = 32; myButton.addEventListener(‘keydown’, function(event) { if (event.keyCode === ENTER || event.keyCode === SPACE) { event.preventDefault(); doSomething(event); } }); function toggleButton(button) { button.setAttribute(“aria-pressed”, button.getAttribute(“aria-pressed”) === “true” ? “false” : “true” )}; } function doSomething() { // Your actual code to do what you want )}; @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 60

Slide 60

A Button CSS Reset By Andy Bell button { display: inline-block; border: none; margin: 0; padding: 0; font-family: sans-serif; font-size: 1rem; line-height: 1; background: transparent; -webkit-appearance: none; } @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 61

Slide 61

If you need a link, use the <a> element <div> FEATURES <a> Navigate to a page or view Change url Browser redraw/refresh Focusable Keyboard Interaction Open in new window Reported to accessibility tree :active, :hover, :focus, :link & :visited states @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 62

Slide 62

IT’S LIKE USING A PRE-INSTALLED JS LIBRARY @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 63

Slide 63

DON’T FORGET ABOUT THE ATTRIBUTES @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 64

Slide 64

ARIA ATTRIBUTES FILL THE GAPS IN STANDARD HTML TO GIVE MORE CONTEXT TO THE ACCESSIBILITY TREE @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 65

Slide 65

ARIA ATTRIBUTES aria-pressed aria-selected aria-expanded aria-grabbed aria-live aria-hidden aria-labelledby https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 66

Slide 66

“ If you’re writing CSS that conveys information. Ask yourself is that information also in the accessibility tree? If not, a bit of ARIA might help. JULIE GRUNDY @mandy_kerr mandykerr @mandymichael@bsky.social ” @mandymichael@front-end.social

Slide 67

Slide 67

LOADING & PERFORMANCE @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 68

Slide 68

IMPROVING IMAGE PERFORMANCE @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 69

Slide 69

width & height for improving Cumulative Layout Shift <img src=“jello.jpg” width=”640” height=”360” alt=“Jello rolling over”> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 70

Slide 70

Cumulative Layout Shift : measures how much elements shift on the page as content is being downloaded @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 71

Slide 71

Jello nursery rhyme Jello nursery rhyme Little uffy Jello likes to play outside down came the rain and Jello had to hide out came the sun and dried up all the rain So little uffy Jello could play outside again Little uffy Jello likes to play outside down came the rain and Jello had to hide out came the sun and dried up all the rain So little uffy Jello could play outside again <img src=“jello.jpg” alt=“Jello with rope in mouth”> fl fl fl fl @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 72

Slide 72

Jello nursery rhyme Jello nursery rhyme Little uffy Jello likes to play outside down came the rain and Jello had to hide out came the sun and dried up all the rain So little uffy Jello could play outside again Little uffy Jello likes to play outside down came the rain and Jello had to hide out came the sun and dried up all the rain So little uffy Jello could play outside again <img src=“jello.jpg” width=”640” height=”360” alt=“Jello with rope in mouth”> fl fl fl fl @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 73

Slide 73

Responsive images with srcset <img width=”1000” height=”1000” src=”jello-1000.jpg” srcset=”jello-1000.jpg 1000w, jello-2000.jpg 2000w” alt=”Jello rolling over” /> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 74

Slide 74

Responsive images with sizes <img width=”1000” height=”1000” src=”jello-1000.jpg” srcset=”jello-1000.jpg 1000w, jello-2000.jpg 2000w,” sizes=”(min-width: 1000px) 1000px, 400px” alt=”Jello rolling over” /> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 75

Slide 75

Responsive images with <source> <picture> <source media=”(max-width: 799px)” srcset=”jello-480w-sml.jpg” /> <source media=”(min-width: 800px)” srcset=”jello-800w.jpg” /> <img src=”jello-800w.jpg” alt=“Jello rolling over” /> </picture> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 76

Slide 76

IMAGE LAZY LOADING @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 77

Slide 77

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 78

Slide 78

Native lazy-loading <img src=”img.png” loading=“lazy” width=”300” height=”300”/>

<iframe src=“img.png” loading=“lazy” /> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 79

Slide 79

Only lazy load images that are not in viewport @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 80

Slide 80

LARGEST CONTENTFUL PAINT (LCP) : the render time of the largest image or text block visible within the viewport when the page rst starts loading. mandykerr @mandymichael@bsky.social fi @mandy_kerr @mandymichael@front-end.social

Slide 81

Slide 81

Only lazy load images that are not in viewport @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 82

Slide 82

Lazy Loading browser support @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 83

Slide 83

Optimising loading & Prioritisation @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 84

Slide 84

Browser Prioritisation HTTP/1 HTTP/2 & HTTP/3 @mandy_kerr mandykerr Multiple TCP Connections each load 1 resource at a time. Single TCP/QUIC Connection sending multiple requests at the same time @mandymichael@bsky.social @mandymichael@front-end.social

Slide 85

Slide 85

Order of prioritisation can have a big impact on your web performance metrics. @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 86

Slide 86

Browser Prioritisation varies between browsers https://calendar.perfplanet.com/2022/http-3-prioritization-demysti ed/ mandykerr @mandymichael@bsky.social fi @mandy_kerr @mandymichael@front-end.social

Slide 87

Slide 87

Priority Hints using the fetchpriority attribute @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 88

Slide 88

Fetch Priority high: You want the browser to prioritize it low: You want the browser to deprioritize it auto: You want the browser to decide (Default) @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 89

Slide 89

fetchpriority sets the relative priority @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 90

Slide 90

Relative Priority (CSS) <link rel=”stylesheet” href=”styles.css” fetchpriority=”high”> Highest Priority Highest priority

<link rel=”stylesheet” href=”styles.css” fetchpriority=”low”> Highest Priority High Priority web.dev/fetch-priority @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 91

Slide 91

Relative Priority (Image) <img src=”image.jpg” fetchpriority=”high”> Low Priority High Priority <img src=”image.jpg” fetchpriority=”low”> Low Priority Low Priority web.dev/fetch-priority @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 92

Slide 92

Fetch Priority <ul class=“carousel”> <li><img src=”img/1.jpg” <li><img src=”img/2.jpg” <li><img src=”img/3.jpg” <li><img src=”img/4.jpg” </ul> @mandy_kerr mandykerr fetchpriority=”high”></li> fetchpriority=”low”></li> fetchpriority=”low”></li> fetchpriority=”low”></li> @mandymichael@bsky.social @mandymichael@front-end.social

Slide 93

Slide 93

fetchpriority browser support @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 94

Slide 94

Improve speed with Resource Hints @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 95

Slide 95

RESOURCE HINTS <link rel=”preconnect” href=“https://gooddogs.com/img”> <link rel=”dns-prefetch” href=“https://gooddogs.com/img”> @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 96

Slide 96

RESOURCE HINTS <link rel=”preconnect” href=”….”> Starts the connection process as soon as possible @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 97

Slide 97

RESOURCE HINTS <link rel=”preconnect” href=”….”> Starts the connection process as soon as possible Only for critical connections @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 98

Slide 98

RESOURCE HINTS <link rel=”dns-prefetch” href=”….”> Starts the DNS lookup https://caniuse.com/?search=dns-prefetch @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 99

Slide 99

EVERYTHING ADDS UP @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 100

Slide 100

dns-prefetch browser support @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 101

Slide 101

Preloading resources @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 102

Slide 102

PRELOADING

<link rel=”preload” as=”script” href=”critical.js”> Only preload critical resources @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 103

Slide 103

Preload is intended to let browser know about resources you need “later” @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 104

Slide 104

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 105

Slide 105

@mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 106

Slide 106

PRELOADING fi @mandy_kerr CSS: Resources that are inside CSS, like fonts or images. js: Preload chunks of critical js bundles, or resources that JS can request like JSON, imported scripts, or web workers Large files: Large les like video mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 107

Slide 107

“ …see preload as a queue-jump. If you and yer pals get queue jumped into a club (baller), it doesn’t only get you in quicker, it makes everyone behind you get in a little slower. - Harry Roberts, @csswizardry @mandy_kerr mandykerr @mandymichael@bsky.social ” @mandymichael@front-end.social

Slide 108

Slide 108

103 Early Hints (A sneaky bonus) @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 109

Slide 109

103 Early Hint Link: https://cdn.example.com; rel=preconnect, https://cdn.example.com; rel=preconnect; crossorigin https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103 @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 110

Slide 110

Early Hints browser support PRELOAD https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103 @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 111

Slide 111

EVERYTHING ADDS UP @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 112

Slide 112

DON’T JUST USE DIVS USE THE NAMED ELEMENTS LEVERAGE BUILT-IN FUNCTIONALITY CONVEY INFORMATION & CONTEXT EXPLORE THE ATTRIBUTES @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 113

Slide 113

MAKE IT USEFUL MAKE IT USABLE @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social

Slide 114

Slide 114

THANK YOU bit.ly/htmlresource @mandy_kerr mandykerr @mandymichael@bsky.social @mandymichael@front-end.social