How to add an accessible skip link and ARIA-correct navigation
How to build a keyboard-friendly skip link with the visually-hidden-until-focus pattern, and mark up navigation correctly with aria-label and aria-current.
26 posts
How to build a keyboard-friendly skip link with the visually-hidden-until-focus pattern, and mark up navigation correctly with aria-label and aria-current.
Why the Google Fonts <link> blocks rendering, and how importing @fontsource-variable/jetbrains-mono in your entry file ships the font with your bundle for a faster first paint.
What an OG image is, why it's exactly 1200x630, and how to design one as an SVG template and render it to the PNG that social platforms show in link previews.
Why JavaScript-based theming causes a flash of the wrong theme on static sites, and how baking class="dark" into index.html fixes it before any JS runs.
A beginner-friendly guide to writing a JSON-LD @graph with Person, ProfilePage, WebSite, and FAQPage nodes so search engines understand your portfolio and show rich results.
Tailwind v4 moves configuration into CSS. Here's how to set up @import, a dark variant, and map OKLCH color and radius tokens with @theme inline — with no tailwind.config file at all.
Recreate the old-monitor / hacker-terminal look with plain CSS: scanline overlays, a clip-path glitch effect, a blinking terminal cursor, and typewriter text — no JavaScript.
How I built a fully prerendered, horizontally-scrolling terminal-themed portfolio with React 19 and vite-react-ssg — and the constraints that shaped every decision, from direct DOM mutation to sr-only crawler mirrors.
Replace the mouse pointer with a glowing dot-and-ring cursor in React by mutating the DOM directly on mousemove, and invert it over any background with mix-blend-mode: difference.
Add left/right swipe navigation to a React slideshow using touchstart and touchend, a distance threshold to ignore accidental taps, and simple direction detection.
Add arrow-key and Home/End navigation to a React slideshow, and learn the one guard that stops it from stealing keystrokes while a visitor is typing in a form.
A mouse wheel fires dozens of events per flick. Learn how to use a ref-based lock plus a timeout so one scroll gesture advances exactly one panel in a React slideshow.
Build a full-viewport horizontal slideshow in React where each section is a panel and navigation moves the whole track sideways with a single translateX on a ref.
A typewriter effect that starts from an empty string breaks prerendering and causes hydration mismatches. The fix: seed state with the full text, then replay the typing on the client as pure decoration.
Build one small Astro component that formats dates for humans and machines at once, using Intl.DateTimeFormat and the HTML time element with a datetime attribute.
Add a real comment section to a static Astro blog with zero backend — Giscus stores every comment as a GitHub Discussion. Here's the setup, the config shape, and how to match a dark theme.
How Astro's 404.astro convention works, what makes a helpful not-found page, and how static hosts serve it — walked through with this blog's real 404 page.
Learn how Astro's getStaticPaths turns your content into individual static pages at build time, using the real [slug].astro route from this blog.
Ship the three SEO basics every Astro blog needs: an @astrojs/rss feed, an @astrojs/sitemap, and one BaseHead component that emits canonical, Open Graph, Twitter, and robots meta on every page.
Restaurant SaaS, a dating app in two weeks, a five-year-old CRA migration, and leading a startup's frontend. The concrete, unglamorous lessons from moving fast across very different codebases.
Build a complete blog tag system in Astro — a helper that counts tags, a /tags index page, and dynamically generated per-tag pages using getStaticPaths.
Three small helper functions turn an Astro content collection into related-post suggestions and prev/next links — draft hiding, tag-based ranking, and adjacency.
Centralize every bit of site copy — personal info, experience, skills, projects — into one typed TypeScript data file so components render from a single source of truth instead of hardcoded strings.
A tiny CSS keyframe that reveals an image by animating from blurred and over-bright to sharp, with a subtle scale settle — perfect for a load-in or on-screen reveal.
Animate skill/progress bars from 0 to their target width using a CSS keyframe, a CSS variable for the target, cubic-bezier easing, and forwards fill-mode — driven straight from your data.
Pass an isActive prop down to each section so its entrance animations replay exactly when the panel scrolls into view — driven by a single current index in the parent.