[{"data":1,"prerenderedAt":270},["ShallowReactive",2],{"post-designing-empty-states-that-teach":3,"blog-posts":14},{"_path":4,"title":5,"description":6,"date":7,"tags":8,"readingTime":12,"body":13},"\u002Fblog\u002Fdesigning-empty-states-that-teach\u002F","Designing Empty States That Teach the Product","Empty screens are not placeholders—they are onboarding. How to write, layout, and sequence first-run UI so people know what to do next.","2026-04-19",[9,10,11],"ux","frontend","design",5,"# Designing Empty States That Teach the Product\n\nAn empty list is not a failure of content. It is the first conversation your product has with someone who has not yet succeeded. Treat that screen like onboarding compressed into one panel.\n\n## Empty is a mode, not a missing design\n\nMost products ship three related states and only polish one of them: the populated happy path. The other two—empty and error—get a dashed box and a shrug. Users spend disproportionate time in those states early on, which means your weakest UI is what forms first impressions.\n\nName the modes explicitly in your component API or design notes:\n\n- **Blank** — never used; no data expected yet\n- **Filtered empty** — data exists, current filters hide it\n- **Error empty** — load failed; retry is possible\n- **Success empty** — action completed and cleared the list (rarer, but real)\n\nEach mode needs different copy and actions. “No posts yet” is wrong when the user applied a tag filter that matches nothing. “Couldn’t load posts” is wrong when the API succeeded with `[]`. Collapsing those into one generic empty component is how people bounce.\n\nWire the modes in code early. A single `EmptyState` with `variant` props beats three one-off markup blocks that drift apart. Shared structure, different message and primary action—that is the pattern.\n\n## Write copy that points at the next click\n\nEmpty-state copy fails when it describes the void instead of the path out of it. “Nothing here” is accurate and useless. Prefer verbs tied to the primary control on the screen.\n\nStrong patterns:\n\n- **Name the outcome** — “Create your first paste” beats “No pastes”\n- **Name the constraint** — “Upload an Instagram export ZIP to compare followers”\n- **Name the fix for filters** — “No posts match ‘css’. Clear filters or try another tag.”\n\nKeep secondary text short. One supporting sentence for context, then the button. Walls of explanation belong in docs, not in the middle of a list region. If you need a longer “how to export your data” guide, link it—do not paste the whole guide into the empty panel.\n\nTone should match stakes. A fun side project can be warm; a privacy tool should be calm and precise. Humor that jokes about “your sad empty inbox” can feel dismissive when someone is anxious about data. Clarity first, personality second.\n\nAlso watch for blame. “You haven’t added anything” reads colder than “Add a post to get started.” The user did not fail; the product is waiting. The same rule applies to errors: “We couldn’t read that file—try a ZIP export from Settings → Your activity” teaches recovery without implying the person did something foolish.\n\nWhen localization enters the picture later, keep strings complete sentences rather than glued fragments. Empty states are oddly sensitive to awkward grammar because they sit alone on a quiet screen.\n\n## Layout the empty state like a real screen\n\nVisually, empty states often float as tiny centered blobs in a huge white region. That wastes hierarchy. Give the empty state the same structural weight as a populated list: consistent padding, a clear title level, and a primary button that matches other CTAs in the app.\n\nPractical layout tips:\n\n- Place the empty state inside the content column, not as a global modal\n- Use the same heading size you would for a section title\n- Put the primary action where the eye already looks for “create” elsewhere\n- If illustrations help, keep them quiet; they should not outrank the text\n\nFor filtered empty, keep the filter controls visible and focused. The fix is usually adjusting filters, not navigating away. For error empty, put retry next to a short reason—and avoid burying the only recovery action under “Learn more.”\n\nKeyboard and screen-reader users need the same clarity: the empty region should announce the state, and the primary action should be reachable without hunting. Do not rely on color alone to distinguish error from blank.\n\n## Sequence first-run so empty becomes rare quickly\n\nThe best empty state is temporary. Design the first session so people cross into “has data” without friction.\n\nTactics that work on small sites and tools:\n\n- Offer a sample dataset or demo mode with a clear “using sample data” badge\n- Prefill with one template the user can edit instead of starting from zero\n- After first success, celebrate briefly, then get out of the way\n- Remember dismissals: do not re-show a “welcome empty” after they have content\n\nBe careful with fake content that looks real. Sample posts labeled as samples teach the UI; placeholder Latin that looks like a bug teaches distrust. If you seed examples, mark them and make removal obvious. A “Load demo file” button next to “Choose file” often converts curiosity into a completed flow without requiring personal data on the first visit.\n\nAlso plan the return visit. Someone who succeeded once and comes back to a true empty list (they deleted everything, or storage cleared) should still see helpful copy—not a dead end that assumes they “already know.” Persistence of onboarding tips can be soft: show a compact hint once, then stay quiet unless the list is empty again.\n\nMeasure qualitatively if you lack analytics: watch one friend use the blank screen. Where they hesitate is where copy or layout failed. Fix that before adding another feature to the populated view. One observed hesitation is worth more than a week of guessing in Figma alone.\n\n## Wrap-up\n\nEmpty states teach the product when you treat them as first-class modes with honest copy, solid layout, and a path to the first success. Design blank, filtered, and error separately, point every sentence at the next click, and shorten the time people spend staring at nothing. The list will fill—your job is making sure they stay long enough to fill it.",[15,23,33,41,48,56,63,71,78,85,92,100,102,109,116,124,130,138,147,155,161,168,174,182,190,197,203,209,216,222,228,234,241,248,255,263],{"_path":16,"title":17,"description":18,"date":19,"tags":20,"readingTime":12},"\u002Fblog\u002Fclient-side-tools-that-earn-trust\u002F","Client-Side Tools That Earn Trust","How to design browser tools that feel private by default—local processing, clear data boundaries, and UX that never asks people to guess where their files go.","2026-07-20",[21,10,22],"privacy","tools",{"_path":24,"title":25,"description":26,"date":27,"tags":28,"readingTime":32},"\u002Fblog\u002Fusb-c-cables-that-actually-matter\u002F","USB-C Cables That Actually Matter","How to buy USB-C cables by wattage and data speed so you stop guessing which cord charges slowly or fails a file transfer.","2026-07-14",[29,30,31],"gadgets","hardware","productivity",4,{"_path":34,"title":35,"description":36,"date":37,"tags":38,"readingTime":12},"\u002Fblog\u002Fcolor-contrast-people-can-read\u002F","Color Contrast People Can Actually Read","Quick checks for text and UI colors that stay legible in light mode, dark mode, and on mediocre displays.","2026-07-07",[39,40,11],"accessibility","css",{"_path":42,"title":43,"description":44,"date":45,"tags":46,"readingTime":12},"\u002Fblog\u002Fportable-ssds-worth-carrying\u002F","Portable SSDs Worth Carrying","When a pocket SSD beats cloud sync for travel, backups, and large media—and how to pick one that stays fast and durable.","2026-06-26",[29,47,31],"storage",{"_path":49,"title":50,"description":51,"date":52,"tags":53,"readingTime":12},"\u002Fblog\u002Fwireless-earbuds-that-last-the-day\u002F","Wireless Earbuds That Last the Day","Simple charging and fit habits that stretch earbud battery life without babysitting the case every few hours.","2026-06-18",[29,54,55],"audio","habits",{"_path":57,"title":58,"description":59,"date":60,"tags":61,"readingTime":32},"\u002Fblog\u002Fwhen-a-second-monitor-helps\u002F","When a Second Monitor Actually Helps","A practical take on dual screens for coding, writing, and research—plus when a single good display is still the better desk.","2026-06-10",[29,31,62],"workspace",{"_path":64,"title":65,"description":66,"date":67,"tags":68,"readingTime":32},"\u002Fblog\u002Fshort-session-game-picks\u002F","Short-Session Games That Respect Your Evening","Genres and design patterns that fit a 30-minute window without the “one more hour” trap.","2026-06-04",[69,55,70],"gaming","indie",{"_path":72,"title":73,"description":74,"date":75,"tags":76,"readingTime":12},"\u002Fblog\u002Fphone-charging-habits-that-age-better\u002F","Phone Charging Habits That Age Better","Practical charging routines that keep a phone useful longer—without obsessing over the perfect battery percentage.","2026-05-23",[29,77,55],"mobile",{"_path":79,"title":80,"description":81,"date":82,"tags":83,"readingTime":12},"\u002Fblog\u002Ffinishing-side-projects-without-burning-out\u002F","Finishing Side Projects Without Burning Out","A practical playbook for shrinking scope, shipping thin vertical slices, and keeping personal projects fun long enough to actually launch.","2026-05-14",[31,84,55],"side-projects",{"_path":86,"title":87,"description":88,"date":89,"tags":90,"readingTime":12},"\u002Fblog\u002Faccessible-forms-people-finish\u002F","Accessible Forms That People Actually Finish","Practical accessibility checks for labels, errors, and focus so your forms work for more users.","2026-05-07",[39,91,10],"html",{"_path":93,"title":94,"description":95,"date":96,"tags":97,"readingTime":12},"\u002Fblog\u002Fvue-composition-api-patterns\u002F","Practical Vue Composition API Patterns","Reusable patterns for composables, shared state, and cleaner component logic in Vue 3.","2026-04-27",[98,99,10],"vue","javascript",{"_path":4,"title":5,"description":6,"date":7,"tags":101,"readingTime":12},[9,10,11],{"_path":103,"title":104,"description":105,"date":106,"tags":107,"readingTime":12},"\u002Fblog\u002Fwhy-indie-games-keep-winning\u002F","Why Indie Games Keep Winning Attention","How small teams punch above their weight with focused design, personality, and smart scope.","2026-04-13",[69,70,108],"industry",{"_path":110,"title":111,"description":112,"date":113,"tags":114,"readingTime":12},"\u002Fblog\u002Fkeeping-component-props-simple\u002F","Keeping Component Props Simple","When to split a component, when to use slots, and how to avoid prop objects that grow forever.","2026-04-03",[98,10,115],"best-practices",{"_path":117,"title":118,"description":119,"date":120,"tags":121,"readingTime":12},"\u002Fblog\u002Fcalm-frontend-performance\u002F","A Calm Approach to Frontend Performance","Performance work that sticks: measure what users feel, fix the critical path first, and avoid optimization theater on personal and portfolio sites.","2026-03-26",[122,10,123],"performance","web",{"_path":125,"title":126,"description":127,"date":128,"tags":129,"readingTime":12},"\u002Fblog\u002Fcss-layout-without-the-hacks\u002F","CSS Layout Without the Hacks","Modern Flexbox and Grid techniques that replace brittle floats, magic numbers, and overflow tricks.","2026-03-17",[40,10,11],{"_path":131,"title":132,"description":133,"date":134,"tags":135,"readingTime":12},"\u002Fblog\u002Fcloud-gaming-what-gets-in-the-way\u002F","Cloud Gaming: What Still Gets in the Way","Latency, input feel, and library lock-in—why streaming games is impressive and still uneven.","2026-03-10",[69,136,137],"cloud-gaming","tech",{"_path":139,"title":140,"description":141,"date":142,"tags":143,"readingTime":12},"\u002Fblog\u002Fshipping-static-sites-with-nuxt\u002F","Shipping Static Sites with Nuxt","How I build and deploy a Nuxt site to Cloudflare Pages with predictable routes and content.","2026-03-02",[144,145,146],"nuxt","ssg","devops",{"_path":148,"title":149,"description":150,"date":151,"tags":152,"readingTime":12},"\u002Fblog\u002Fdebugging-faster-in-the-browser\u002F","Debugging Faster in the Browser","A short toolkit of DevTools habits—breakpoints, network filters, and DOM inspection—that cut debug time.","2026-02-24",[99,153,154],"devtools","debugging",{"_path":156,"title":157,"description":158,"date":159,"tags":160,"readingTime":12},"\u002Fblog\u002Fsmall-javascript-habits-that-scale\u002F","Small JavaScript Habits That Scale","Everyday habits—naming, early returns, and clearer async flow—that keep front-end codebases maintainable.","2026-02-18",[99,115,10],{"_path":162,"title":163,"description":164,"date":165,"tags":166,"readingTime":32},"\u002Fblog\u002Fhealthier-gaming-routine\u002F","Building a Healthier Gaming Routine","Simple habits for sessions that stay fun—breaks, goals, and knowing when to stop.","2026-02-02",[69,55,167],"wellness",{"_path":169,"title":170,"description":171,"date":172,"tags":173,"readingTime":12},"\u002Fblog\u002Fresponsive-images-without-layout-shift\u002F","Responsive Images Without Layout Shift","Width, height, srcset, and lazy loading patterns that keep pages stable while images load.","2026-01-22",[91,122,10],{"_path":175,"title":176,"description":177,"date":178,"tags":179,"readingTime":12},"\u002Fblog\u002Fgit-habits-for-small-teams\u002F","Git Habits for Solo and Small Teams","Branch naming, commit messages, and PR hygiene that keep history useful without slowing you down.","2026-01-09",[180,181,31],"git","workflow",{"_path":183,"title":184,"description":185,"date":186,"tags":187,"readingTime":12},"\u002Fblog\u002Fwhat-makes-a-game-stream-worth-watching\u002F","What Makes a Game Stream Worth Watching","Commentary, pacing, and community—why some streams click even when the gameplay is messy.","2025-12-14",[69,188,189],"streaming","content",{"_path":191,"title":192,"description":193,"date":194,"tags":195,"readingTime":12},"\u002Fblog\u002Fwhen-to-reach-for-typescript-vue\u002F","When to Reach for TypeScript on a Vue App","A pragmatic take on where TypeScript pays off in Vue—and where plain JS is still fine.","2025-11-30",[196,98,10],"typescript",{"_path":198,"title":199,"description":200,"date":201,"tags":202,"readingTime":12},"\u002Fblog\u002Fenvironment-variables-for-static-sites\u002F","Environment Variables for Static Sites","How to use build-time env vars in Nuxt and other static generators without leaking secrets or breaking CI.","2025-10-05",[144,146,10],{"_path":204,"title":205,"description":206,"date":207,"tags":208,"readingTime":12},"\u002Fblog\u002Fpicking-a-side-project-stack\u002F","Picking a Side Project Stack Without Overthinking It","How to choose tools for a personal project when the goal is shipping, not building the perfect architecture.","2025-09-18",[31,10,115],{"_path":210,"title":211,"description":212,"date":213,"tags":214,"readingTime":32},"\u002Fblog\u002Fco-op-games-for-busy-friends\u002F","Co-op Games That Work When Everyone Is Busy","Async-friendly and drop-in co-op picks for friend groups that rarely share the same free evening.","2025-08-22",[69,215,55],"co-op",{"_path":217,"title":218,"description":219,"date":220,"tags":221,"readingTime":12},"\u002Fblog\u002Fsemantic-html-still-matters\u002F","Semantic HTML Still Matters","Landmarks, headings, and native elements that make pages easier to use, style, and maintain.","2025-07-06",[91,39,10],{"_path":223,"title":224,"description":225,"date":226,"tags":227,"readingTime":12},"\u002Fblog\u002Fgit-without-the-jargon\u002F","Learning Git Without the Jargon","A plain-language mental model for commits, branches, and merges when tutorials assume you already know Git.","2025-05-14",[180,181,31],{"_path":229,"title":230,"description":231,"date":232,"tags":233,"readingTime":12},"\u002Fblog\u002Fwhy-loading-states-matter\u002F","Why Loading States Matter More Than Animations","Skeletons, disabled buttons, and honest feedback beat flashy spinners when data takes time to arrive.","2025-03-28",[9,10,11],{"_path":235,"title":236,"description":237,"date":238,"tags":239,"readingTime":12},"\u002Fblog\u002Fnaming-things-so-future-you-survives\u002F","Naming Things So Future You Survives","Practical rules for naming variables, components, and files so a codebase stays readable months after the clever jokes fade.","2025-03-10",[99,115,240],"maintainability",{"_path":242,"title":243,"description":244,"date":245,"tags":246,"readingTime":12},"\u002Fblog\u002Fsave-systems-that-respect-time\u002F","Save Systems That Respect the Player’s Time","Checkpoints, autosaves, and manual slots are design choices. How thoughtful saving reduces rage-quits without deleting tension from the game.","2025-02-28",[69,247,9],"game-design",{"_path":249,"title":250,"description":251,"date":252,"tags":253,"readingTime":12},"\u002Fblog\u002Fmicrocopy-that-makes-interfaces-human\u002F","Microcopy That Makes Interfaces Feel Human","Button labels, error lines, and helper text are product design. How to write UI words that reduce hesitation without sounding like a chatbot.","2025-01-22",[9,254,11],"writing",{"_path":256,"title":257,"description":258,"date":259,"tags":260,"readingTime":12},"\u002Fblog\u002Freadmes-that-get-projects-running\u002F","READMEs That Get Projects Running","How to write a README that helps someone—including future you—install, run, and understand a project without opening a support chat.","2024-12-18",[261,262,55],"documentation","open-source",{"_path":264,"title":265,"description":266,"date":267,"tags":268,"readingTime":12},"\u002Fblog\u002Fprogressive-enhancement-still-pays-off\u002F","Progressive Enhancement Still Pays Off","Build the core experience in HTML first, then layer CSS and JavaScript so your site stays useful when networks, scripts, or devices misbehave.","2024-11-05",[91,10,269],"architecture",1784620504888]