Tips & micro-lessons
Small wins, big impact. Each tip is something you can apply the next time you open the playground.
HTML
Always set the lang attribute
<html lang="en"> tells screen readers and translation tools what language the page is in. It's a one-character change that improves accessibility and SEO.
Set a viewport meta tag
Without <meta name="viewport" content="width=device-width, initial-scale=1"> mobile browsers will zoom out and render your page at 980px wide.
Use real <button> elements
A clickable <div> isn't keyboard-accessible, doesn't focus, and isn't announced as a button by screen readers. <button> handles all of that for free.
Lazy-load below-the-fold images
Add loading="lazy" to <img> tags that aren't visible on first paint. The browser will defer loading them until the user scrolls close.
<img src="..." loading="lazy" alt="..." />Use srcset for responsive images
Let the browser pick the right image size for each device with srcset and sizes — smaller files on phones, sharper images on retina laptops.
CSS
Use clamp() for fluid typography
font-size: clamp(1rem, 2vw + 0.5rem, 2rem) scales smoothly between a minimum and maximum without media queries.
font-size: clamp(1rem, 2vw + 0.5rem, 2rem);auto-fill + minmax = responsive grid
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)) creates a fully responsive grid with zero media queries.
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));Use gap instead of margin
Both Flexbox and Grid support the gap property. It's cleaner than per-item margins and never adds extra space at the edges.
Reserve image space with aspect-ratio
aspect-ratio: 16/9 keeps space reserved while the image loads, eliminating cumulative layout shift.
Respect prefers-reduced-motion
Wrap animations in @media (prefers-reduced-motion: no-preference) so users sensitive to motion get a still UI.
Use color-mix() for tints
color-mix(in oklab, var(--primary) 20%, transparent) gives you a translucent tint of any color, without needing separate variables.
JavaScript
Use optional chaining
user?.profile?.name returns undefined if any link in the chain is null or undefined — no more nested && checks.
?? vs ||
Nullish coalescing (??) only falls back when the value is null or undefined. || falls back on any falsy value, which can hide bugs around 0 or empty strings.
Clone with the spread operator
const copy = [...arr] or { ...obj } gives you a shallow clone with one character — no need for slice() or Object.assign().
fetch() doesn't reject on 4xx/5xx
fetch() only rejects on network errors. You have to manually check res.ok and throw if it's false.
Debounce expensive handlers
Wrap scroll, resize, or keystroke handlers in a debounce so they only run once after the user stops, not on every event.
Use event delegation
Attach one listener to a parent instead of one to every child. Use event.target.closest() to find the matching element.
Format dates and numbers with Intl
Intl.DateTimeFormat and Intl.NumberFormat ship with the browser. Skip the date-fns dependency for simple formatting.