[{"data":1,"prerenderedAt":219},["ShallowReactive",2],{"post-designing-a-reusable-editorial-system-en":3,"related-posts-designing-a-reusable-editorial-system-en":184,"related-categories-designing-a-reusable-editorial-system-en":208},{"id":4,"title":5,"author":6,"authorImageUrl":7,"authorUrl":8,"body":9,"categorySlug":161,"categoryText":162,"dateModified":163,"datePublished":164,"description":165,"extension":166,"faqs":167,"image":85,"language":174,"meta":175,"navigation":176,"path":177,"seo":178,"stem":179,"tags":180,"__hash__":183},"blog\u002Fblog\u002Fdesigning-a-reusable-editorial-system.md","Designing a Reusable Editorial System","Bruma","\u002Fimages\u002Fauthors\u002Fbruma.jpg","https:\u002F\u002Fbrumaombra.com",{"type":10,"value":11,"toc":151},"minimark",[12,17,21,24,36,41,46,49,52,59,65,68,72,75,78,87,91,124,130,134,137,141,144,147],[13,14,16],"h2",{"id":15},"start-from-the-editorial-rhythm","Start from the editorial rhythm",[18,19,20],"p",{},"Reusable blog UI works best when the typography and spacing rules are stable before you think about product branding. The moment every project needs to reinterpret headings, cards, or metadata blocks, the shared component layer stops saving time.",[18,22,23],{},"In this demo, the goal is not to simulate a CMS with endless data. It is to prove that the shared blog components can render a believable article page, category flow, and listing experience with the same surface language across routes.",[18,25,26,27,31,32,35],{},"The renderer keeps references such as ",[28,29,30],"code",{},"BlogContentRenderer"," and ",[28,33,34],{},"sm:my-8"," readable inside the editorial flow without turning them into full code blocks.",[37,38],"blog-list",{":items":39,"variant":40},"[\"Stable typography and spacing make the article page portable across products\",\"Shared content blocks reduce duplication between markdown-heavy apps\",\"Wrapper components preserve app-level compatibility while moving real UI into the library\"]","checkmark",[42,43,45],"h3",{"id":44},"a-reusable-system-needs-clear-boundaries","A reusable system needs clear boundaries",[18,47,48],{},"The library owns the reusable chrome: hero cards, carousels, category grids, FAQs, author information, and table-of-contents behavior. The demo app owns the content source and route composition.",[18,50,51],{},"That boundary is what makes the setup portable. Another project can change the post inventory, author information, or publishing cadence without rewriting the reusable UI blocks.",[53,54],"flow",{":items":55,"description":56,"orientation":57,"title":58},"[\"Markdown entry\",\"Content collection\",\"Shared component\",\"Localized route\"]","Each layer keeps its own responsibility while the article stays reusable.","horizontal","From content to route",[60,61],"code-block",{"code":62,"language":63,"title":64},"{\n  \"title\": \"Designing a Reusable Editorial System\",\n  \"category\": \"Design Systems\",\n  \"tags\": [\"Content\", \"Vue\"]\n}\n","json","A content entry",[66,67],"hr",{},[13,69,71],{"id":70},"real-content-is-a-better-test-than-static-arrays","Real content is a better test than static arrays",[18,73,74],{},"Mock arrays are useful for initial exploration, but they hide the real shape of an editorial page. Once content lives in markdown, you immediately verify path handling, frontmatter fields, related content lookups, and rich body rendering.",[18,76,77],{},"This is where production-like behavior matters. A working post page is not just a hero image plus a few paragraphs. It includes:",[18,79,80],{},[81,82],"img",{"alt":83,"height":84,"src":85,"width":86},"Editorial planning workspace",933,"\u002Fimages\u002Fblog\u002Fdesigning-a-reusable-editorial-system\u002Fcover.jpg",1400,[37,88],{":items":89,"variant":90},"[\"Category metadata\",\"Author and publish information\",\"A content-driven table of contents\",\"Related article links\",\"Related category links\"]","circle",[92,93,98],"pre",{"className":94,"code":95,"language":96,"meta":97,"style":97},"language-bash shiki shiki-themes github-light github-dark","npm install\nnpm run dev\n","bash","",[28,99,100,113],{"__ignoreMap":97},[101,102,105,109],"span",{"class":103,"line":104},"line",1,[101,106,108],{"class":107},"sScJk","npm",[101,110,112],{"class":111},"sZZnC"," install\n",[101,114,116,118,121],{"class":103,"line":115},2,[101,117,108],{"class":107},[101,119,120],{"class":111}," run",[101,122,123],{"class":111}," dev\n",[125,126],"blog-table",{":headers":127,":highlightCol":128,":rows":129},"[\"Responsibility\",\"Library\",\"App\"]","1","[[\"Editorial UI blocks\",\"Cards, lists, tables, dividers, TOC, FAQ, author info\",\"Consumes them through wrappers or direct imports\"],[\"Content source\",\"No hardcoded product content\",\"Markdown entries and route queries\"],[\"Product-specific conversion blocks\",\"Optional\",\"Owns CTA cards like GiveItATryCard\"]]",[42,131,133],{"id":132},"markdown-helps-expose-content-assumptions","Markdown helps expose content assumptions",[18,135,136],{},"With markdown in the loop, the page has to handle long-form prose and nested headings gracefully. If the spacing, prose styles, or table-of-contents data break, that problem becomes obvious immediately.",[13,138,140],{"id":139},"the-demo-app-should-behave-like-a-consumer","The demo app should behave like a consumer",[18,142,143],{},"The demo app is most useful when it exercises the library the same way a real product would. That means importing the published subpaths, relying on the Nuxt modules the production app uses, and querying content through the same APIs instead of bypassing them.",[18,145,146],{},"That approach keeps the demo honest. If something is awkward here, it will be awkward for the next consuming app too.",[148,149,150],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":97,"searchDepth":115,"depth":115,"links":152},[153,157,160],{"id":15,"depth":115,"text":16,"children":154},[155],{"id":44,"depth":156,"text":45},3,{"id":70,"depth":115,"text":71,"children":158},[159],{"id":132,"depth":156,"text":133},{"id":139,"depth":115,"text":140},"design-systems","Design Systems","2026-06-12T00:00:00Z","2026-06-10T00:00:00Z","How we turned a one-off blog UI into a reusable content surface that still feels intentional once it leaves the original product.","md",[168,171],{"question":169,"answer":170},"Why move a blog UI into a shared library?","Because it lets multiple projects reuse the same content surfaces, spacing decisions, and interaction patterns without rebuilding them from scratch.",{"question":172,"answer":173},"What should stay app-specific?","Content querying, SEO policy, and product-specific conversion blocks usually belong to the app, while reusable cards, lists, sidebars, and info panels fit the library.","en",{},true,"\u002Fblog\u002Fdesigning-a-reusable-editorial-system",{"title":5,"description":165},"blog\u002Fdesigning-a-reusable-editorial-system",[162,181,182],"Content","Vue","JR2O7wX_O9bxyoqP5uQI64BfLt6tz3oA6gHl_xZOCgU",[185,190,196,202],{"title":186,"description":187,"image":188,"categoryText":162,"path":189},"Dark Mode Is a Second Design, Not a Filter","Inverting colors gives you a dark theme that technically works and looks slightly wrong everywhere. Semantic tokens fix most of it. Shadows and accents need extra care.","\u002Fimages\u002Fblog\u002Fdark-mode-is-a-second-design\u002Fcover.jpg","\u002Fblog\u002Fdark-mode-is-a-second-design",{"title":191,"description":192,"image":193,"categoryText":194,"path":195},"Why Spring Animations Feel Faster Than They Are","A spring that overshoots slightly can take longer than a plain fade and still feel quicker. Here is why, and where the trick stops working.","\u002Fimages\u002Fblog\u002Fwhy-spring-animations-feel-faster\u002Fcover.jpg","Motion Design","\u002Fblog\u002Fwhy-spring-animations-feel-faster",{"title":197,"description":198,"image":199,"categoryText":200,"path":201},"Designing Focus States People Actually Notice","Keyboard users rely on the focus ring to know where they are. Most designs hide it or make it faint. A few small rules make it both visible and good-looking.","\u002Fimages\u002Fblog\u002Ffocus-states-people-notice\u002Fcover.jpg","Accessibility","\u002Fblog\u002Ffocus-states-people-notice",{"title":203,"description":204,"image":205,"categoryText":206,"path":207},"Shipping the Blog Migration Into the Library","A practical summary of what changed once the original blog implementation was moved into a reusable Nuxt-friendly component library.","\u002Fimages\u002Fblog\u002Fshipping-the-blog-migration\u002Fcover.jpg","Product Updates","\u002Fblog\u002Fshipping-the-blog-migration",[209,211,213,217],{"name":194,"slug":210,"count":104,"image":193},"motion-design",{"name":200,"slug":212,"count":104,"image":199},"accessibility",{"name":214,"slug":215,"count":104,"image":216},"Engineering","engineering","\u002Fimages\u002Fblog\u002Fsource-published-packages\u002Fcover.jpg",{"name":206,"slug":218,"count":104,"image":205},"product-updates",1792259222176]