[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"header-categories":3,"post-building-accessible-web-applications":73,"parsed-post-efe5eac4-404f-4d7e-8d95-5ad07cdcb7ff":148},{"success":4,"data":5},true,{"items":6,"pagination":70},[7,16,24,32,40,48,56,63],{"id":8,"name":9,"description":10,"parentId":11,"createdAt":12,"updatedAt":12,"parent":11,"children":13,"_count":14},"ai-future","AI & The Future","Artificial intelligence and emerging technology",null,"2026-07-24T06:02:45.990Z",[],{"posts":15},1,{"id":17,"name":18,"description":19,"parentId":11,"createdAt":20,"updatedAt":20,"parent":11,"children":21,"_count":22},"tools-productivity","Tools & Productivity","Developer tools and workflow optimization","2026-07-24T06:02:45.712Z",[],{"posts":23},7,{"id":25,"name":26,"description":27,"parentId":11,"createdAt":28,"updatedAt":28,"parent":11,"children":29,"_count":30},"career","Career & Growth","Professional development for developers","2026-07-24T06:02:45.486Z",[],{"posts":31},3,{"id":33,"name":34,"description":35,"parentId":11,"createdAt":36,"updatedAt":36,"parent":11,"children":37,"_count":38},"opinion","Opinion & Analysis","Industry analysis and technical opinions","2026-07-24T06:02:45.272Z",[],{"posts":39},6,{"id":41,"name":42,"description":43,"parentId":11,"createdAt":44,"updatedAt":44,"parent":11,"children":45,"_count":46},"tutorials","Tutorials","Step-by-step learning guides","2026-07-24T06:02:44.965Z",[],{"posts":47},21,{"id":49,"name":50,"description":51,"parentId":11,"createdAt":52,"updatedAt":52,"parent":11,"children":53,"_count":54},"software-engineering","Software Engineering","Best practices, patterns, and principles","2026-07-24T06:02:44.689Z",[],{"posts":55},24,{"id":57,"name":58,"description":59,"parentId":11,"createdAt":60,"updatedAt":60,"parent":11,"children":61,"_count":62},"devops-cloud","DevOps & Cloud","Infrastructure, deployment, and cloud computing","2026-07-24T06:02:44.453Z",[],{"posts":39},{"id":64,"name":65,"description":66,"parentId":11,"createdAt":67,"updatedAt":67,"parent":11,"children":68,"_count":69},"web-development","Web Development","Frontend and backend web development tutorials and guides","2026-07-24T06:02:44.169Z",[],{"posts":55},{"page":15,"limit":71,"total":71,"totalPages":15,"hasNext":72,"hasPrev":72},8,false,{"success":4,"data":74},{"id":75,"slug":76,"title":77,"excerpt":78,"content":79,"status":80,"visibility":81,"featuredImage":82,"canonicalUrl":11,"readingTime":83,"viewCount":84,"commentEnabled":4,"publishedAt":85,"scheduledAt":11,"createdAt":86,"updatedAt":87,"seoTitle":88,"seoDescription":78,"seoKeywords":11,"authorId":89,"author":90,"coAuthors":95,"tags":96,"categories":112,"comments":115,"_count":116,"relatedPosts":118},"efe5eac4-404f-4d7e-8d95-5ad07cdcb7ff","building-accessible-web-applications","Building Accessible Web Applications: Beyond ARIA Labels","Accessibility isn't an afterthought. Learn to build inclusive web apps with semantic HTML, keyboard navigation, and ARIA done right.","## Accessibility Is Not a Checkbox\n\n1 in 4 adults in the US has a disability. Accessibility isn't just ethical — it's practical. Accessible apps work better for everyone: keyboard power users, people with slow connections, users of screen readers, and people in bright sunlight.\n\n## Start with Semantic HTML\n\n```html\n\u003C!-- ❌ Div soup -->\n\u003Cdiv class=\"header\">\n  \u003Cdiv class=\"nav\">\n    \u003Cdiv class=\"link\" onclick=\"navigate()\">Home\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\n\u003C!-- ✅ Semantic HTML — free accessibility -->\n\u003Cheader>\n  \u003Cnav aria-label=\"Main navigation\">\n    \u003Ca href=\"\u002F\">Home\u003C\u002Fa>\n  \u003C\u002Fnav>\n\u003C\u002Fheader>\n```\n\nSemantic elements provide meaning to assistive technology without extra attributes.\n\n## Keyboard Navigation\n\nEvery interactive element must be keyboard-accessible:\n\n```vue\n\u003Ctemplate>\n  \u003Cdiv\n    role=\"tablist\"\n    @keydown.left=\"selectPrev\"\n    @keydown.right=\"selectNext\"\n    @keydown.home=\"selectFirst\"\n    @keydown.end=\"selectLast\"\n  >\n    \u003Cbutton\n      v-for=\"(tab, i) in tabs\"\n      :key=\"tab.id\"\n      role=\"tab\"\n      :tabindex=\"i === activeIndex ? 0 : -1\"\n      :aria-selected=\"i === activeIndex\"\n      @click=\"select(i)\"\n    >\n      {{ tab.label }}\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n::callout{icon=\"i-lucide-keyboard\" color=\"primary\"}\nTest your app by unplugging your mouse for 30 minutes. If you can't complete core workflows with keyboard alone, you have accessibility issues.\n::\n\n## Focus Management\n\nWhen content changes dynamically, manage focus:\n\n```typescript\n\u002F\u002F After opening a modal\nfunction openModal() {\n  isOpen.value = true\n  nextTick(() => {\n    modalRef.value?.focus()\n  })\n}\n\n\u002F\u002F After deleting an item from a list\nfunction deleteItem(index: number) {\n  items.value.splice(index, 1)\n  nextTick(() => {\n    \u002F\u002F Focus the next item, or previous if it was the last\n    const nextIndex = Math.min(index, items.value.length - 1)\n    itemRefs.value[nextIndex]?.focus()\n  })\n}\n```\n\n## Color and Contrast\n\n- Minimum 4.5:1 contrast ratio for normal text\n- Minimum 3:1 for large text (18px+ or 14px+ bold)\n- Never use color as the only indicator\n\n```html\n\u003C!-- ❌ Color only -->\n\u003Cspan class=\"text-red-500\">Error\u003C\u002Fspan>\n\n\u003C!-- ✅ Color + icon + text -->\n\u003Cspan class=\"text-red-500 flex items-center gap-1\">\n  \u003CIconAlertCircle aria-hidden=\"true\" \u002F>\n  Error: Email is required\n\u003C\u002Fspan>\n```\n\n## ARIA: Use Sparingly\n\nThe first rule of ARIA: don't use ARIA if native HTML does the job.\n\n```html\n\u003C!-- ❌ Over-engineered -->\n\u003Cdiv role=\"button\" tabindex=\"0\" aria-pressed=\"false\" onclick=\"...\">\n\n\u003C!-- ✅ Just use a button -->\n\u003Cbutton>Click me\u003C\u002Fbutton>\n```\n\n## Testing Tools\n\n- **axe DevTools** — Browser extension for automated scanning\n- **VoiceOver** (Mac) \u002F **NVDA** (Windows) — Screen reader testing\n- **Lighthouse** — Accessibility audits in Chrome DevTools\n\nBuild for everyone from the start. It's always cheaper than retrofitting.\n","published","public","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1605379399642-870262d3d051?w=1200&q=80",9,2005,"2026-06-09T06:03:39.775Z","2026-07-24T06:03:39.778Z","2026-07-28T17:12:47.811Z","Building Accessible Web Applications: Beyond ARIA Labels | BitBlog","fddb5d93-7a2c-4d86-a06a-fa32e73a01c6",{"email":91,"bio":92,"id":89,"name":93,"avatarUrl":94},"mbeahessilfieprince@gmail.com","Fullstack Software Developer ","Mbeah Essilfie","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F93322394?v=4",[],[97,102,107],{"id":98,"name":99,"color":100,"description":101},"javascript","JavaScript","#f7df1e","The language of the web",{"id":103,"name":104,"color":105,"description":106},"vue","Vue","#4fc08d","The Progressive JavaScript Framework",{"id":108,"name":109,"color":110,"description":111},"css","CSS","#1572b6","Cascading Style Sheets",[113,114],{"id":64,"name":65,"description":66},{"id":49,"name":50,"description":51},[],{"comments":117},0,[119,128,138],{"id":120,"slug":121,"title":122,"excerpt":123,"featuredImage":124,"viewCount":125,"readingTime":71,"publishedAt":126,"author":127},"2997028f-4d22-4eb7-9d86-894a54cb559a","building-type-safe-apis-nuxt3-prisma","Building Type-Safe APIs with Nuxt 3 and Prisma","Learn how to build end-to-end type-safe APIs using Nuxt 3 server routes and Prisma ORM for a seamless developer experience.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1498050108023-c5249f4df085?w=1200&q=80",1922,"2026-07-23T06:02:46.910Z",{"id":89,"name":93,"avatarUrl":94},{"id":129,"slug":130,"title":131,"excerpt":132,"featuredImage":133,"viewCount":134,"readingTime":135,"publishedAt":136,"author":137},"da9e4553-8b0d-411e-b4ec-f9684017e163","mastering-tailwind-css-design-systems","Mastering Tailwind CSS: From Utility Classes to Design Systems","Go beyond basic utility classes and learn how to build cohesive, maintainable design systems with Tailwind CSS.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1555066931-4365d14bab8c?w=1200&q=80",257,10,"2026-07-21T06:02:49.268Z",{"id":89,"name":93,"avatarUrl":94},{"id":139,"slug":140,"title":141,"excerpt":142,"featuredImage":143,"viewCount":144,"readingTime":145,"publishedAt":146,"author":147},"50add5fc-4026-4267-b917-7f71ea9e35b0","complete-guide-vue3-composables","The Complete Guide to Vue 3 Composables","Understand the power of Vue 3's Composition API through practical, real-world composable patterns that you can use today.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1517694712202-14dd9538aa97?w=1200&q=80",990,12,"2026-07-19T06:02:51.753Z",{"id":89,"name":93,"avatarUrl":94},{"data":149,"body":151,"toc":355},{"title":150,"description":150},"",{"type":152,"children":153},"root",[154,163,169,175,188,193,199,204,213,224,230,235,245,251,271,279,285,290,298,304,345,350],{"type":155,"tag":156,"props":157,"children":159},"element","h2",{"id":158},"accessibility-is-not-a-checkbox",[160],{"type":161,"value":162},"text","Accessibility Is Not a Checkbox",{"type":155,"tag":164,"props":165,"children":166},"p",{},[167],{"type":161,"value":168},"1 in 4 adults in the US has a disability. Accessibility isn't just ethical — it's practical. Accessible apps work better for everyone: keyboard power users, people with slow connections, users of screen readers, and people in bright sunlight.",{"type":155,"tag":156,"props":170,"children":172},{"id":171},"start-with-semantic-html",[173],{"type":161,"value":174},"Start with Semantic HTML",{"type":155,"tag":176,"props":177,"children":182},"pre",{"className":178,"code":179,"language":180,"meta":150,"style":181},"language-html","\u003C!-- ❌ Div soup -->\n\u003Cdiv class=\"header\">\n  \u003Cdiv class=\"nav\">\n    \u003Cdiv class=\"link\" onclick=\"navigate()\">Home\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\n\u003C!-- ✅ Semantic HTML — free accessibility -->\n\u003Cheader>\n  \u003Cnav aria-label=\"Main navigation\">\n    \u003Ca href=\"\u002F\">Home\u003C\u002Fa>\n  \u003C\u002Fnav>\n\u003C\u002Fheader>\n","html","undefined",[183],{"type":155,"tag":184,"props":185,"children":186},"code",{"__ignoreMap":150},[187],{"type":161,"value":179},{"type":155,"tag":164,"props":189,"children":190},{},[191],{"type":161,"value":192},"Semantic elements provide meaning to assistive technology without extra attributes.",{"type":155,"tag":156,"props":194,"children":196},{"id":195},"keyboard-navigation",[197],{"type":161,"value":198},"Keyboard Navigation",{"type":155,"tag":164,"props":200,"children":201},{},[202],{"type":161,"value":203},"Every interactive element must be keyboard-accessible:",{"type":155,"tag":176,"props":205,"children":208},{"className":206,"code":207,"language":103,"meta":150,"style":181},"language-vue","\u003Ctemplate>\n  \u003Cdiv\n    role=\"tablist\"\n    @keydown.left=\"selectPrev\"\n    @keydown.right=\"selectNext\"\n    @keydown.home=\"selectFirst\"\n    @keydown.end=\"selectLast\"\n  >\n    \u003Cbutton\n      v-for=\"(tab, i) in tabs\"\n      :key=\"tab.id\"\n      role=\"tab\"\n      :tabindex=\"i === activeIndex ? 0 : -1\"\n      :aria-selected=\"i === activeIndex\"\n      @click=\"select(i)\"\n    >\n      {{ tab.label }}\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[209],{"type":155,"tag":184,"props":210,"children":211},{"__ignoreMap":150},[212],{"type":161,"value":207},{"type":155,"tag":214,"props":215,"children":218},"callout",{"color":216,"icon":217},"primary","i-lucide-keyboard",[219],{"type":155,"tag":164,"props":220,"children":221},{},[222],{"type":161,"value":223},"Test your app by unplugging your mouse for 30 minutes. If you can't complete core workflows with keyboard alone, you have accessibility issues.",{"type":155,"tag":156,"props":225,"children":227},{"id":226},"focus-management",[228],{"type":161,"value":229},"Focus Management",{"type":155,"tag":164,"props":231,"children":232},{},[233],{"type":161,"value":234},"When content changes dynamically, manage focus:",{"type":155,"tag":176,"props":236,"children":240},{"className":237,"code":238,"language":239,"meta":150,"style":181},"language-typescript","\u002F\u002F After opening a modal\nfunction openModal() {\n  isOpen.value = true\n  nextTick(() => {\n    modalRef.value?.focus()\n  })\n}\n\n\u002F\u002F After deleting an item from a list\nfunction deleteItem(index: number) {\n  items.value.splice(index, 1)\n  nextTick(() => {\n    \u002F\u002F Focus the next item, or previous if it was the last\n    const nextIndex = Math.min(index, items.value.length - 1)\n    itemRefs.value[nextIndex]?.focus()\n  })\n}\n","typescript",[241],{"type":155,"tag":184,"props":242,"children":243},{"__ignoreMap":150},[244],{"type":161,"value":238},{"type":155,"tag":156,"props":246,"children":248},{"id":247},"color-and-contrast",[249],{"type":161,"value":250},"Color and Contrast",{"type":155,"tag":252,"props":253,"children":254},"ul",{},[255,261,266],{"type":155,"tag":256,"props":257,"children":258},"li",{},[259],{"type":161,"value":260},"Minimum 4.5:1 contrast ratio for normal text",{"type":155,"tag":256,"props":262,"children":263},{},[264],{"type":161,"value":265},"Minimum 3:1 for large text (18px+ or 14px+ bold)",{"type":155,"tag":256,"props":267,"children":268},{},[269],{"type":161,"value":270},"Never use color as the only indicator",{"type":155,"tag":176,"props":272,"children":274},{"className":178,"code":273,"language":180,"meta":150,"style":181},"\u003C!-- ❌ Color only -->\n\u003Cspan class=\"text-red-500\">Error\u003C\u002Fspan>\n\n\u003C!-- ✅ Color + icon + text -->\n\u003Cspan class=\"text-red-500 flex items-center gap-1\">\n  \u003CIconAlertCircle aria-hidden=\"true\" \u002F>\n  Error: Email is required\n\u003C\u002Fspan>\n",[275],{"type":155,"tag":184,"props":276,"children":277},{"__ignoreMap":150},[278],{"type":161,"value":273},{"type":155,"tag":156,"props":280,"children":282},{"id":281},"aria-use-sparingly",[283],{"type":161,"value":284},"ARIA: Use Sparingly",{"type":155,"tag":164,"props":286,"children":287},{},[288],{"type":161,"value":289},"The first rule of ARIA: don't use ARIA if native HTML does the job.",{"type":155,"tag":176,"props":291,"children":293},{"className":178,"code":292,"language":180,"meta":150,"style":181},"\u003C!-- ❌ Over-engineered -->\n\u003Cdiv role=\"button\" tabindex=\"0\" aria-pressed=\"false\" onclick=\"...\">\n\n\u003C!-- ✅ Just use a button -->\n\u003Cbutton>Click me\u003C\u002Fbutton>\n",[294],{"type":155,"tag":184,"props":295,"children":296},{"__ignoreMap":150},[297],{"type":161,"value":292},{"type":155,"tag":156,"props":299,"children":301},{"id":300},"testing-tools",[302],{"type":161,"value":303},"Testing Tools",{"type":155,"tag":252,"props":305,"children":306},{},[307,318,335],{"type":155,"tag":256,"props":308,"children":309},{},[310,316],{"type":155,"tag":311,"props":312,"children":313},"strong",{},[314],{"type":161,"value":315},"axe DevTools",{"type":161,"value":317}," — Browser extension for automated scanning",{"type":155,"tag":256,"props":319,"children":320},{},[321,326,328,333],{"type":155,"tag":311,"props":322,"children":323},{},[324],{"type":161,"value":325},"VoiceOver",{"type":161,"value":327}," (Mac) \u002F ",{"type":155,"tag":311,"props":329,"children":330},{},[331],{"type":161,"value":332},"NVDA",{"type":161,"value":334}," (Windows) — Screen reader testing",{"type":155,"tag":256,"props":336,"children":337},{},[338,343],{"type":155,"tag":311,"props":339,"children":340},{},[341],{"type":161,"value":342},"Lighthouse",{"type":161,"value":344}," — Accessibility audits in Chrome DevTools",{"type":155,"tag":164,"props":346,"children":347},{},[348],{"type":161,"value":349},"Build for everyone from the start. It's always cheaper than retrofitting.",{"type":155,"tag":351,"props":352,"children":353},"style",{},[354],{"type":161,"value":150},{"title":150,"searchDepth":356,"depth":356,"links":357},2,[358,359,360,361,362,363,364],{"id":158,"depth":356,"text":162},{"id":171,"depth":356,"text":174},{"id":195,"depth":356,"text":198},{"id":226,"depth":356,"text":229},{"id":247,"depth":356,"text":250},{"id":281,"depth":356,"text":284},{"id":300,"depth":356,"text":303}]