Exklusiv

Blog <mv-blog>

Redaktionelle Blog-Bereiche: ein nummeriertes Karten-Raster mit Kategoriefiltern, eine hervorgehobene Story neben einer nummerierten Liste, oder eine vollständige Artikelseite mit Breadcrumb, Autorenzeile, einem mitlaufenden Inhaltsverzeichnis, das dem Lesefortschritt folgt, einem Fließtext, Teilen-Funktion und verwandten Beiträgen.

KategorieBlöcke
TypBlock (fertige Sektion) (<mv-blog>)
Statusstabil
Installiert auchavatar, badge, breadcrumb, share-button, typography
Keywordsblog, articles, posts, news, journal, article page, table of contents, toc, reading time, byline, category filter, landing, marketing, editorial, exclusive

When to use

  • A marketing site needs a blog index of article cards with cover, category, author, date and reading time
  • A blog home should put one story first and list the next ones compactly beside it
  • A long article page needs a byline, a table of contents that tracks the section being read, sharing and related posts
  • Readers should narrow a list of posts by category without leaving the page

Avoid when

  • A single content card is needed on its own, outside a list of posts → use Card instead
  • The section presents product capabilities rather than dated articles → use Features instead
  • The content is customer quotes, ratings or case study results → use Testimonials instead

Installation

node scripts/add.mjs blog --out ./src/marvelous

KI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["blog"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, components/avatar/avatar.css, components/avatar/avatar.js, components/badge/badge.css, components/breadcrumb/breadcrumb.css, core/dom.js, core/element.js, core/focus.js, core/position.js, components/share-button/share-button.js, components/share-button/share-button.css, components/typography/typography.css, core/motion.js, core/observe.js, components/blog/blog.css, components/blog/blog.js.

Verwendung

Referenz-Markup zum Starten und Anpassen mit Attributen, data-* und CSS-Variablen:

<mv-tabs id="blog-demo" value="grid" data-size="sm" style="gap:0;background:var(--mv-bg)">
  <style>#blog-demo > [role="tablist"] { justify-self: center; margin: .75rem auto 0; }</style>
  <div role="tablist" aria-label="Block variant">
    <button role="tab" data-value="grid">Grid</button>
    <button role="tab" data-value="featured">Featured</button>
    <button role="tab" data-value="article">Article</button>
  </div>

  <!-- ═════════ Grid with category filters ═════════ -->
  <div role="tabpanel" data-value="grid">
    <mv-blog>
      <section class="mv-blog" aria-labelledby="blog-g-title">
        <div class="mv-blog-inner">
          <header class="mv-blog-header">
            <p class="mv-blog-eyebrow">Tessera journal <span class="mv-blog-count">/ 06</span></p>
            <h2 class="mv-blog-title" id="blog-g-title">Notes from the team building Tessera</h2>
            <p class="mv-blog-lead">Engineering deep dives, design decisions and the occasional postmortem, written by the people who shipped the work.</p>
          </header>
          <nav class="mv-blog-filters" aria-label="Filter articles by category">
            <ul>
              <li><a href="?category=all" data-filter="all" aria-current="page">All <span class="mv-blog-filter-count">06</span></a></li>
              <li><a href="?category=engineering" data-filter="engineering">Engineering <span class="mv-blog-filter-count">02</span></a></li>
              <li><a href="?category=design" data-filter="design">Design <span class="mv-blog-filter-count">02</span></a></li>
              <li><a href="?category=frontend" data-filter="frontend">Frontend <span class="mv-blog-filter-count">01</span></a></li>
              <li><a href="?category=product" data-filter="product">Product <span class="mv-blog-filter-count">01</span></a></li>
            </ul>
          </nav>
          <div class="mv-blog-grid">
          <article class="mv-blog-card" data-category="engineering">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%231e1b4b'/%3E%3Cstop offset='1' stop-color='%233730a3'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Cline x1='160' y1='110' x2='160' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='270' y1='110' x2='270' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='380' y1='110' x2='380' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='490' y1='110' x2='490' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='600' y1='110' x2='600' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='710' y1='110' x2='710' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='820' y1='110' x2='820' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='930' y1='110' x2='930' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='1040' y1='110' x2='1040' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Crect x='160' y='140' width='809.6' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='140' width='176' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='319' cy='157' r='7' fill='%234338ca'/%3E%3Crect x='160' y='222' width='651.2' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='222' width='140.8' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='283.8' cy='239' r='7' fill='%234338ca'/%3E%3Crect x='160' y='304' width='756.8' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='304' width='193.6' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='336.6' cy='321' r='7' fill='%234338ca'/%3E%3Crect x='160' y='386' width='528' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='386' width='105.6' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='248.60000000000002' cy='403' r='7' fill='%234338ca'/%3E%3Crect x='160' y='468' width='704' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='468' width='158.4' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='301.4' cy='485' r='7' fill='%234338ca'/%3E%3Crect x='160' y='550' width='598.4000000000001' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='550' width='132' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='275' cy='567' r='7' fill='%234338ca'/%3E%3Cline x1='353.6' y1='110' x2='353.6' y2='640' stroke='%23fbbf24' stroke-width='4' stroke-dasharray='10 12'/%3E%3C/svg%3E" alt="" width="1200" height="750" loading="lazy" decoding="async"></div>
            <p class="mv-blog-meta"><span class="mv-blog-category">Engineering</span></p>
            <h3 class="mv-blog-card-title"><a class="mv-blog-link" href="#blog-demo">How we cut our CI pipeline from 42 to 9 minutes</a></h3>
            <p class="mv-blog-excerpt">Remote caching, test sharding by historical duration and one honest conversation about flaky tests. What moved the needle, what didn’t, and the dashboards we kept.</p>
            <p class="mv-blog-byline"><span class="mv-avatar" data-size="sm"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23fbcfe8'/%3E%3Cstop offset='1' stop-color='%23db2777'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%233b0764'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23b97a56'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23b97a56'/%3E%3Cpath d='M19 30c-1-11 5-18 13-18s14 7 13 18c-1-5-3-8-4-9-3 2-8 3-13 2-4 0-7 3-9 7z' fill='%231c1917'/%3E%3Cpath d='M19 28c0 8 1 14 3 18l3-1c-2-5-3-11-2-16z M45 28c0 8-1 14-3 18l-3-1c2-5 3-11 2-16z' fill='%231c1917'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Priya Raman</span><span class="mv-blog-details"><time datetime="2026-09-18">Sep 18, 2026</time> · 9 min read</span></span></p>
          </article>
          <article class="mv-blog-card" data-category="frontend">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23042f2e'/%3E%3Cstop offset='1' stop-color='%230f766e'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Crect x='110' y='110' width='620' height='250' rx='22' fill='none' stroke='%2399f6e4' stroke-opacity='.55' stroke-width='3' stroke-dasharray='14 12'/%3E%3Crect x='140' y='140' width='220' height='190' rx='14' fill='%232dd4bf'/%3E%3Crect x='390' y='150' width='300' height='28' rx='14' fill='%23ccfbf1'/%3E%3Crect x='390' y='196' width='250' height='18' rx='14' fill='%235eead4'/%3E%3Crect x='390' y='228' width='280' height='18' rx='14' fill='%235eead4'/%3E%3Crect x='390' y='280' width='130' height='40' rx='14' fill='%23f0fdfa'/%3E%3Crect x='780' y='110' width='310' height='530' rx='22' fill='none' stroke='%2399f6e4' stroke-opacity='.55' stroke-width='3' stroke-dasharray='14 12'/%3E%3Crect x='810' y='140' width='250' height='210' rx='14' fill='%232dd4bf'/%3E%3Crect x='810' y='380' width='230' height='28' rx='14' fill='%23ccfbf1'/%3E%3Crect x='810' y='426' width='200' height='18' rx='14' fill='%235eead4'/%3E%3Crect x='810' y='458' width='220' height='18' rx='14' fill='%235eead4'/%3E%3Crect x='810' y='560' width='130' height='40' rx='14' fill='%23f0fdfa'/%3E%3Crect x='110' y='410' width='620' height='230' rx='22' fill='none' stroke='%2399f6e4' stroke-opacity='.55' stroke-width='3' stroke-dasharray='14 12'/%3E%3Crect x='140' y='440' width='170' height='170' rx='14' fill='%232dd4bf'/%3E%3Crect x='340' y='450' width='260' height='26' rx='14' fill='%23ccfbf1'/%3E%3Crect x='340' y='494' width='350' height='18' rx='14' fill='%235eead4'/%3E%3Crect x='340' y='526' width='300' height='18' rx='14' fill='%235eead4'/%3E%3C/svg%3E" alt="" width="1200" height="750" loading="lazy" decoding="async"></div>
            <p class="mv-blog-meta"><span class="mv-blog-category">Frontend</span></p>
            <h3 class="mv-blog-card-title"><a class="mv-blog-link" href="#blog-demo">A practical guide to CSS container queries</a></h3>
            <p class="mv-blog-excerpt">Media queries tell you how big the screen is. Container queries tell you how much room a component has, which is the question your design system was asking all along.</p>
            <p class="mv-blog-byline"><span class="mv-avatar" data-size="sm"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23bae6fd'/%3E%3Cstop offset='1' stop-color='%232563eb'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%23172554'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23e0ac69'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23e0ac69'/%3E%3Cpath d='M20.5 26c0-8 5-13 11.5-13s11.5 5 11.5 13c-2-4-6-6.5-11.5-6.5S22.5 22 20.5 26z' fill='%233b2418'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Mateo Alvarez</span><span class="mv-blog-details"><time datetime="2026-09-11">Sep 11, 2026</time> · 7 min read</span></span></p>
          </article>
          <article class="mv-blog-card" data-category="design">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23fcd34d'/%3E%3Cstop offset='1' stop-color='%23f97316'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Crect x='290' y='95' width='620' height='560' rx='36' fill='%23fffbeb' stroke='%23f59e0b' stroke-opacity='.35' stroke-width='3'/%3E%3Crect x='340' y='135' width='200' height='30' rx='15' fill='%2392400e' fill-opacity='.85'/%3E%3Ccircle cx='810' cy='150' r='18' fill='%23fde68a'/%3E%3Ccircle cx='860' cy='150' r='18' fill='%23fde68a'/%3E%3Crect x='340' y='210' width='62' height='62' rx='16' fill='%23fef3c7'/%3E%3Crect x='418' y='210' width='62' height='62' rx='16' fill='%23fef3c7'/%3E%3Crect x='496' y='210' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='574' y='210' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='652' y='210' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='730' y='210' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='808' y='210' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='340' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='418' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='496' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='574' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='652' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='730' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='808' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='340' y='374' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='418' y='374' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='496' y='374' width='62' height='62' rx='16' fill='%23fcd34d'/%3E%3Crect x='574' y='374' width='62' height='62' rx='16' fill='%23fcd34d'/%3E%3Crect x='652' y='374' width='62' height='62' rx='16' fill='%23b45309'/%3E%3Crect x='643' y='365' width='80' height='80' rx='22' fill='none' stroke='%231c1917' stroke-width='5'/%3E%3Crect x='730' y='374' width='62' height='62' rx='16' fill='%23fcd34d'/%3E%3Crect x='808' y='374' width='62' height='62' rx='16' fill='%23fcd34d'/%3E%3Crect x='340' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='418' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='496' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='574' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='652' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='730' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='808' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='340' y='538' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='418' y='538' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='496' y='538' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='574' y='538' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='652' y='538' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='730' y='538' width='62' height='62' rx='16' fill='%23fef3c7'/%3E%3Crect x='808' y='538' width='62' height='62' rx='16' fill='%23fef3c7'/%3E%3C/svg%3E" alt="" width="1200" height="750" loading="lazy" decoding="async"></div>
            <p class="mv-blog-meta"><span class="mv-blog-category">Design</span></p>
            <h3 class="mv-blog-card-title"><a class="mv-blog-link" href="#blog-demo">Designing an accessible date picker, one keystroke at a time</a></h3>
            <p class="mv-blog-excerpt">Arrow keys, Page Up, live announcements and the grid pattern that finally behaved in all three major screen readers. Notes from eleven rounds of testing.</p>
            <p class="mv-blog-byline"><span class="mv-avatar" data-size="sm"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23ddd6fe'/%3E%3Cstop offset='1' stop-color='%237c3aed'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%231e1b4b'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23f1c7a8'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23f1c7a8'/%3E%3Cpath d='M20 27c-1-9 4-15 12-15 7 0 13 5 12 14-3-3-6-5-9-5-4 3-10 3-15 6z' fill='%23111827'/%3E%3Ccircle cx='32' cy='12' r='6' fill='%23111827'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Aiko Tanaka</span><span class="mv-blog-details"><time datetime="2026-09-04">Sep 4, 2026</time> · 11 min read</span></span></p>
          </article>
          <article class="mv-blog-card" data-category="engineering">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%230b1120'/%3E%3Cstop offset='1' stop-color='%231e3a8a'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Crect x='150' y='110' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='127' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='127' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='127' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='127' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='150' y='178' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='195' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='195' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='195' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='195' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='150' y='246' width='900' height='48' rx='12' fill='%23dbeafe' fill-opacity='1'/%3E%3Crect x='180' y='263' width='120' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='395' y='263' width='150' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='610' y='263' width='90' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='825' y='263' width='130' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='150' y='314' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='331' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='331' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='331' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='331' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='150' y='382' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='399' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='399' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='399' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='399' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='150' y='450' width='900' height='48' rx='12' fill='%23dbeafe' fill-opacity='1'/%3E%3Crect x='180' y='467' width='120' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='395' y='467' width='150' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='610' y='467' width='90' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='825' y='467' width='130' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='150' y='518' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='535' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='535' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='535' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='535' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='150' y='586' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='603' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='603' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='603' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='603' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Ccircle cx='960' cy='560' r='92' fill='%230f172a' stroke='%2393c5fd' stroke-width='4'/%3E%3Crect x='920' y='555' width='80' height='62' rx='12' fill='%23dbeafe'/%3E%3Cpath d='M935 555v-22a25 25 0 0 1 50 0v22' fill='none' stroke='%23dbeafe' stroke-width='12'/%3E%3C/svg%3E" alt="" width="1200" height="750" loading="lazy" decoding="async"></div>
            <p class="mv-blog-meta"><span class="mv-blog-category">Engineering</span></p>
            <h3 class="mv-blog-card-title"><a class="mv-blog-link" href="#blog-demo">Postgres row-level security without the foot-guns</a></h3>
            <p class="mv-blog-excerpt">Policies that leak through views, functions that quietly run as their owner, and the connection pooler setting that let every tenant see everything in staging. A field guide to getting RLS right the first time.</p>
            <p class="mv-blog-byline"><span class="mv-avatar" data-size="sm"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23fde68a'/%3E%3Cstop offset='1' stop-color='%23d97706'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%23422006'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%237c4a2d'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%237c4a2d'/%3E%3Cpath d='M21 24c1-7 6-11 11-11s10 4 11 11c-3-2-7-3-11-3s-8 1-11 3z' fill='%230c0a09'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Kwame Mensah</span><span class="mv-blog-details"><time datetime="2026-08-28">Aug 28, 2026</time> · 12 min read</span></span></p>
          </article>
          <article class="mv-blog-card" data-category="product">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23052e16'/%3E%3Cstop offset='1' stop-color='%2315803d'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Cpath d='M600 375 Q 355 272.5 230 170' fill='none' stroke='%2386efac' stroke-opacity='0.7' stroke-width='4' /%3E%3Cpath d='M600 375 Q 570 242.5 420 110' fill='none' stroke='%2386efac' stroke-opacity='0.35' stroke-width='4' stroke-dasharray='6 14'/%3E%3Cpath d='M600 375 Q 670 257.5 860 140' fill='none' stroke='%2386efac' stroke-opacity='0.7' stroke-width='4' /%3E%3Cpath d='M600 375 Q 860 352.5 1000 330' fill='none' stroke='%2386efac' stroke-opacity='0.7' stroke-width='4' /%3E%3Cpath d='M600 375 Q 705 482.5 930 590' fill='none' stroke='%2386efac' stroke-opacity='0.35' stroke-width='4' stroke-dasharray='6 14'/%3E%3Cpath d='M600 375 Q 665 512.5 610 650' fill='none' stroke='%2386efac' stroke-opacity='0.7' stroke-width='4' /%3E%3Cpath d='M600 375 Q 390 477.5 300 580' fill='none' stroke='%2386efac' stroke-opacity='0.7' stroke-width='4' /%3E%3Cpath d='M600 375 Q 445 377.5 170 380' fill='none' stroke='%2386efac' stroke-opacity='0.35' stroke-width='4' stroke-dasharray='6 14'/%3E%3Crect x='196' y='122' width='68' height='96' rx='14' fill='%23dcfce7' stroke='%2386efac' stroke-width='3'/%3E%3Ccircle cx='230' cy='200' r='5' fill='%2316a34a'/%3E%3Crect x='386' y='62' width='68' height='96' rx='14' fill='%2314532d' stroke='%2386efac' stroke-width='3'/%3E%3Ccircle cx='420' cy='140' r='5' fill='%23fbbf24'/%3E%3Crect x='826' y='92' width='68' height='96' rx='14' fill='%23dcfce7' stroke='%2386efac' stroke-width='3'/%3E%3Ccircle cx='860' cy='170' r='5' fill='%2316a34a'/%3E%3Crect x='966' y='282' width='68' height='96' rx='14' fill='%23dcfce7' stroke='%2386efac' stroke-width='3'/%3E%3Ccircle cx='1000' cy='360' r='5' fill='%2316a34a'/%3E%3Crect x='896' y='542' width='68' height='96' rx='14' fill='%2314532d' stroke='%2386efac' stroke-width='3'/%3E%3Ccircle cx='930' cy='620' r='5' fill='%23fbbf24'/%3E%3Crect x='576' y='602' width='68' height='96' rx='14' fill='%23dcfce7' stroke='%2386efac' stroke-width='3'/%3E%3Ccircle cx='610' cy='680' r='5' fill='%2316a34a'/%3E%3Crect x='266' y='532' width='68' height='96' rx='14' fill='%23dcfce7' stroke='%2386efac' stroke-width='3'/%3E%3Ccircle cx='300' cy='610' r='5' fill='%2316a34a'/%3E%3Crect x='136' y='332' width='68' height='96' rx='14' fill='%2314532d' stroke='%2386efac' stroke-width='3'/%3E%3Ccircle cx='170' cy='410' r='5' fill='%23fbbf24'/%3E%3Ccircle cx='600' cy='375' r='86' fill='%2322c55e' fill-opacity='.18'/%3E%3Ccircle cx='600' cy='375' r='54' fill='%23bbf7d0'/%3E%3Cpath d='M578 372l16 16 30-32' fill='none' stroke='%2314532d' stroke-width='10' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E" alt="" width="1200" height="750" loading="lazy" decoding="async"></div>
            <p class="mv-blog-meta"><span class="mv-blog-category">Product</span></p>
            <h3 class="mv-blog-card-title"><a class="mv-blog-link" href="#blog-demo">Shipping offline-first sync to 40,000 field technicians</a></h3>
            <p class="mv-blog-excerpt">Conflict resolution sounds academic until a technician in a basement closes the same work order twice. Where we chose last-writer-wins, and where we refused to.</p>
            <p class="mv-blog-byline"><span class="mv-avatar" data-size="sm"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%2399f6e4'/%3E%3Cstop offset='1' stop-color='%230f766e'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%23042f2e'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23f5d0b5'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23f5d0b5'/%3E%3Cpath d='M19 30c-1-11 5-18 13-18s14 7 13 18c-1-5-3-8-4-9-3 2-8 3-13 2-4 0-7 3-9 7z' fill='%23d6a756'/%3E%3Cpath d='M19 28c0 8 1 14 3 18l3-1c-2-5-3-11-2-16z M45 28c0 8-1 14-3 18l-3-1c2-5 3-11 2-16z' fill='%23d6a756'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Sofia Lindqvist</span><span class="mv-blog-details"><time datetime="2026-08-21">Aug 21, 2026</time> · 8 min read</span></span></p>
          </article>
          <article class="mv-blog-card" data-category="design">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%230f0a1f'/%3E%3Cstop offset='1' stop-color='%231e1b4b'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Crect x='0' y='0' width='600' height='750' fill='%23fff7fb'/%3E%3Crect x='110' y='150' width='36' height='450' rx='18' fill='%23fdf2f8'/%3E%3Crect x='154' y='150' width='36' height='450' rx='18' fill='%23fce7f3'/%3E%3Crect x='198' y='150' width='36' height='450' rx='18' fill='%23fbcfe8'/%3E%3Crect x='242' y='150' width='36' height='450' rx='18' fill='%23f9a8d4'/%3E%3Crect x='286' y='150' width='36' height='450' rx='18' fill='%23f472b6'/%3E%3Crect x='330' y='150' width='36' height='450' rx='18' fill='%23ec4899'/%3E%3Crect x='374' y='150' width='36' height='450' rx='18' fill='%23db2777'/%3E%3Crect x='418' y='150' width='36' height='450' rx='18' fill='%23be185d'/%3E%3Crect x='462' y='150' width='36' height='450' rx='18' fill='%239d174d'/%3E%3Crect x='690' y='150' width='36' height='450' rx='18' fill='%231e1b4b'/%3E%3Crect x='734' y='150' width='36' height='450' rx='18' fill='%232e1065'/%3E%3Crect x='778' y='150' width='36' height='450' rx='18' fill='%234c1d95'/%3E%3Crect x='822' y='150' width='36' height='450' rx='18' fill='%235b21b6'/%3E%3Crect x='866' y='150' width='36' height='450' rx='18' fill='%236d28d9'/%3E%3Crect x='910' y='150' width='36' height='450' rx='18' fill='%237c3aed'/%3E%3Crect x='954' y='150' width='36' height='450' rx='18' fill='%238b5cf6'/%3E%3Crect x='998' y='150' width='36' height='450' rx='18' fill='%23a78bfa'/%3E%3Crect x='1042' y='150' width='36' height='450' rx='18' fill='%23c4b5fd'/%3E%3Ccircle cx='392' cy='120' r='12' fill='%23db2777'/%3E%3Ccircle cx='1016' cy='120' r='12' fill='%23a78bfa'/%3E%3C/svg%3E" alt="" width="1200" height="750" loading="lazy" decoding="async"></div>
            <p class="mv-blog-meta"><span class="mv-blog-category">Design</span></p>
            <h3 class="mv-blog-card-title"><a class="mv-blog-link" href="#blog-demo">Choosing color tokens that survive dark mode</a></h3>
            <p class="mv-blog-excerpt">Why we stopped inverting palettes, how OKLCH lightness steps keep contrast predictable, and the three tokens every component actually needs.</p>
            <p class="mv-blog-byline"><span class="mv-avatar" data-size="sm"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23fecdd3'/%3E%3Cstop offset='1' stop-color='%23e11d48'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%234c0519'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23c68642'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23c68642'/%3E%3Cpath d='M20 27c-1-9 4-15 12-15 7 0 13 5 12 14-3-3-6-5-9-5-4 3-10 3-15 6z' fill='%23292524'/%3E%3Ccircle cx='32' cy='12' r='6' fill='%23292524'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Noor Haddad</span><span class="mv-blog-details"><time datetime="2026-08-14">Aug 14, 2026</time> · 6 min read</span></span></p>
          </article>
          </div>
        </div>
      </section>
    </mv-blog>
  </div>

  <!-- ═════════ Featured story + numbered list ═════════ -->
  <div role="tabpanel" data-value="featured">
    <section class="mv-blog" data-variant="featured" aria-labelledby="blog-f-title">
      <div class="mv-blog-inner">
        <header class="mv-blog-header">
          <p class="mv-blog-eyebrow">This week</p>
          <h2 class="mv-blog-title" id="blog-f-title">Field notes on software people rely on</h2>
          <a class="mv-blog-more" href="#blog-demo">View all 48 articles</a>
        </header>
        <div class="mv-blog-featured">
          <article class="mv-blog-card" data-size="lg">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%230b1120'/%3E%3Cstop offset='1' stop-color='%231e3a8a'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Crect x='150' y='110' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='127' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='127' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='127' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='127' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='150' y='178' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='195' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='195' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='195' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='195' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='150' y='246' width='900' height='48' rx='12' fill='%23dbeafe' fill-opacity='1'/%3E%3Crect x='180' y='263' width='120' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='395' y='263' width='150' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='610' y='263' width='90' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='825' y='263' width='130' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='150' y='314' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='331' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='331' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='331' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='331' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='150' y='382' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='399' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='399' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='399' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='399' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='150' y='450' width='900' height='48' rx='12' fill='%23dbeafe' fill-opacity='1'/%3E%3Crect x='180' y='467' width='120' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='395' y='467' width='150' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='610' y='467' width='90' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='825' y='467' width='130' height='14' rx='7' fill='%231d4ed8' fill-opacity='0.8'/%3E%3Crect x='150' y='518' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='535' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='535' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='535' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='535' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='150' y='586' width='900' height='48' rx='12' fill='%2360a5fa' fill-opacity='0.14'/%3E%3Crect x='180' y='603' width='120' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='395' y='603' width='150' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='610' y='603' width='90' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Crect x='825' y='603' width='130' height='14' rx='7' fill='%2393c5fd' fill-opacity='0.3'/%3E%3Ccircle cx='960' cy='560' r='92' fill='%230f172a' stroke='%2393c5fd' stroke-width='4'/%3E%3Crect x='920' y='555' width='80' height='62' rx='12' fill='%23dbeafe'/%3E%3Cpath d='M935 555v-22a25 25 0 0 1 50 0v22' fill='none' stroke='%23dbeafe' stroke-width='12'/%3E%3C/svg%3E" alt="" width="1200" height="750" loading="lazy" decoding="async"></div>
            <p class="mv-blog-meta"><span class="mv-blog-category">Engineering</span></p>
            <h3 class="mv-blog-card-title"><a class="mv-blog-link" href="#blog-demo">Postgres row-level security without the foot-guns</a></h3>
            <p class="mv-blog-excerpt">Policies that leak through views, functions that quietly run as their owner, and the connection pooler setting that let every tenant see everything in staging. A field guide to getting RLS right the first time.</p>
            <p class="mv-blog-byline"><span class="mv-avatar" data-size="sm"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23fde68a'/%3E%3Cstop offset='1' stop-color='%23d97706'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%23422006'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%237c4a2d'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%237c4a2d'/%3E%3Cpath d='M21 24c1-7 6-11 11-11s10 4 11 11c-3-2-7-3-11-3s-8 1-11 3z' fill='%230c0a09'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Kwame Mensah</span><span class="mv-blog-details"><time datetime="2026-08-28">Aug 28, 2026</time> · 12 min read</span></span></p>
          </article>
          <div class="mv-blog-latest">
            <h3 class="mv-blog-eyebrow">More stories</h3>
            <ol class="mv-blog-list">
              <li class="mv-blog-item">
                <p class="mv-blog-meta"><span class="mv-blog-category">Engineering</span></p>
                <h4 class="mv-blog-item-title"><a class="mv-blog-link" href="#blog-demo">How we cut our CI pipeline from 42 to 9 minutes</a></h4>
                <p class="mv-blog-details">Priya Raman · <time datetime="2026-09-18">Sep 18, 2026</time> · 9 min read</p>
              </li>
              <li class="mv-blog-item">
                <p class="mv-blog-meta"><span class="mv-blog-category">Frontend</span></p>
                <h4 class="mv-blog-item-title"><a class="mv-blog-link" href="#blog-demo">A practical guide to CSS container queries</a></h4>
                <p class="mv-blog-details">Mateo Alvarez · <time datetime="2026-09-11">Sep 11, 2026</time> · 7 min read</p>
              </li>
              <li class="mv-blog-item">
                <p class="mv-blog-meta"><span class="mv-blog-category">Design</span></p>
                <h4 class="mv-blog-item-title"><a class="mv-blog-link" href="#blog-demo">Designing an accessible date picker, one keystroke at a time</a></h4>
                <p class="mv-blog-details">Aiko Tanaka · <time datetime="2026-09-04">Sep 4, 2026</time> · 11 min read</p>
              </li>
              <li class="mv-blog-item">
                <p class="mv-blog-meta"><span class="mv-blog-category">Product</span></p>
                <h4 class="mv-blog-item-title"><a class="mv-blog-link" href="#blog-demo">Shipping offline-first sync to 40,000 field technicians</a></h4>
                <p class="mv-blog-details">Sofia Lindqvist · <time datetime="2026-08-21">Aug 21, 2026</time> · 8 min read</p>
              </li>
            </ol>
          </div>
        </div>
      </div>
    </section>
  </div>

  <!-- ═════════ Article page ═════════ -->
  <div role="tabpanel" data-value="article">
    <mv-blog>
      <article class="mv-blog" data-variant="article" aria-labelledby="blog-a-title">
        <div class="mv-blog-inner">
          <header class="mv-blog-article-header">
            <nav class="mv-breadcrumb" data-size="sm" aria-label="Breadcrumb">
              <ol>
                <li><a href="#blog-demo">Blog</a></li>
                <li><a href="#blog-demo">Frontend</a></li>
                <li><span aria-current="page">Container queries guide</span></li>
              </ol>
            </nav>
            <p class="mv-blog-meta"><span class="mv-blog-category">Frontend</span></p>
            <h1 class="mv-blog-article-title" id="blog-a-title">A practical guide to CSS container queries</h1>
            <p class="mv-blog-standfirst">Media queries tell you how big the screen is. Container queries tell you how much room a component has, which is the question your design system was asking all along.</p>
            <div class="mv-blog-byline"><span class="mv-avatar" data-size="lg"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23bae6fd'/%3E%3Cstop offset='1' stop-color='%232563eb'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%23172554'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23e0ac69'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23e0ac69'/%3E%3Cpath d='M20.5 26c0-8 5-13 11.5-13s11.5 5 11.5 13c-2-4-6-6.5-11.5-6.5S22.5 22 20.5 26z' fill='%233b2418'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Mateo Alvarez</span><span class="mv-blog-details">Staff Frontend Engineer</span></span><span class="mv-blog-details"><time datetime="2026-09-11">Sep 11, 2026</time> · 7 min read</span></div>
          </header>

          <figure class="mv-blog-figure">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23042f2e'/%3E%3Cstop offset='1' stop-color='%230f766e'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Crect x='110' y='110' width='620' height='250' rx='22' fill='none' stroke='%2399f6e4' stroke-opacity='.55' stroke-width='3' stroke-dasharray='14 12'/%3E%3Crect x='140' y='140' width='220' height='190' rx='14' fill='%232dd4bf'/%3E%3Crect x='390' y='150' width='300' height='28' rx='14' fill='%23ccfbf1'/%3E%3Crect x='390' y='196' width='250' height='18' rx='14' fill='%235eead4'/%3E%3Crect x='390' y='228' width='280' height='18' rx='14' fill='%235eead4'/%3E%3Crect x='390' y='280' width='130' height='40' rx='14' fill='%23f0fdfa'/%3E%3Crect x='780' y='110' width='310' height='530' rx='22' fill='none' stroke='%2399f6e4' stroke-opacity='.55' stroke-width='3' stroke-dasharray='14 12'/%3E%3Crect x='810' y='140' width='250' height='210' rx='14' fill='%232dd4bf'/%3E%3Crect x='810' y='380' width='230' height='28' rx='14' fill='%23ccfbf1'/%3E%3Crect x='810' y='426' width='200' height='18' rx='14' fill='%235eead4'/%3E%3Crect x='810' y='458' width='220' height='18' rx='14' fill='%235eead4'/%3E%3Crect x='810' y='560' width='130' height='40' rx='14' fill='%23f0fdfa'/%3E%3Crect x='110' y='410' width='620' height='230' rx='22' fill='none' stroke='%2399f6e4' stroke-opacity='.55' stroke-width='3' stroke-dasharray='14 12'/%3E%3Crect x='140' y='440' width='170' height='170' rx='14' fill='%232dd4bf'/%3E%3Crect x='340' y='450' width='260' height='26' rx='14' fill='%23ccfbf1'/%3E%3Crect x='340' y='494' width='350' height='18' rx='14' fill='%235eead4'/%3E%3Crect x='340' y='526' width='300' height='18' rx='14' fill='%235eead4'/%3E%3C/svg%3E" alt="" width="1200" height="750" fetchpriority="high" decoding="async"></div>
            <figcaption>The same card component in three containers. Only the space around it changed.</figcaption>
          </figure>

          <nav class="mv-blog-toc" aria-labelledby="blog-a-toc">
            <p class="mv-blog-toc-title" id="blog-a-toc">On this page</p>
            <ol>
              <li><a href="#cq-why">Why media queries weren’t enough</a></li>
              <li><a href="#cq-declare">Declaring a container</a></li>
              <li><a href="#cq-units">Units that follow the container</a></li>
              <li><a href="#cq-style">Style queries</a></li>
              <li><a href="#cq-checklist">A migration checklist</a></li>
            </ol>
          </nav>

          <div class="mv-prose">
            <p>Our product card lives in eleven places: a three-column grid, a sidebar, a checkout summary, two email previews and a handful of dialogs. For years it answered one question to decide its layout: how wide is the browser window? That was never the question that mattered.</p>

            <h2 id="cq-why">Why media queries weren’t enough</h2>
            <p>A card in a 320px sidebar on a 1440px screen gets the desktop layout, because the viewport is wide. So every placement grew its own override, and the overrides grew their own exceptions. When we audited the card, it had fourteen breakpoint rules, and only four of them were about the screen.</p>
            <p>Container queries move the decision to where it belongs. The card asks its container how much room there is, and lays itself out accordingly, wherever it is placed.</p>

            <h2 id="cq-declare">Declaring a container</h2>
            <p>An element becomes a query container with the <code>container</code> shorthand. Name it, so nested containers don’t answer each other’s questions, and give it <code>inline-size</code> containment: height stays driven by content.</p>
            <pre><code>.card-list {
  container: cards / inline-size;
}

@container cards (min-width: 36rem) {
  .card {
    grid-template-columns: 12rem 1fr;
  }
}</code></pre>
            <p>One rule to remember: an element can’t query itself. The container is always an ancestor, so the card’s parent, or a thin wrapper, carries the declaration.</p>

            <h2 id="cq-units">Units that follow the container</h2>
            <p>The <code>cqi</code> unit is 1% of the container’s inline size. Combined with <code>clamp()</code>, it gives type and spacing that scale with the component instead of the page.</p>
            <blockquote><p>The best part was deleting code. In the first week, 140 lines of breakpoint overrides went away, and nobody noticed a visual change.</p></blockquote>
            <p>Keep a minimum in rem so text never drops below a readable size, and a maximum so a very wide container doesn’t produce billboard headings.</p>

            <h2 id="cq-style">Style queries</h2>
            <p>Size is only half of it. Style queries let a component react to a custom property set by its container, such as <code>--density: compact</code> on a data table toolbar. Every major engine supports them for custom properties today, which is all most design systems need.</p>

            <h2 id="cq-checklist">A migration checklist</h2>
            <ol>
              <li>List the components with the most breakpoint overrides; start with the worst one.</li>
              <li>Name every container after the layout it describes, not the component inside it.</li>
              <li>Replace viewport units in component styles with <code>cqi</code> and a rem floor.</li>
              <li>Keep media queries for page layout, user preferences and print.</li>
            </ol>
            <p>We migrated the card in two days and the rest of the catalog over the following sprint. The overrides are gone, and new placements now work the first time.</p>
          </div>

          <footer class="mv-blog-article-footer">
            <div class="mv-blog-share">
              <ul class="mv-blog-tags" aria-label="Topics">
                <li><a class="mv-badge" data-variant="outline" data-shape="pill" href="#blog-demo">CSS</a></li>
                <li><a class="mv-badge" data-variant="outline" data-shape="pill" href="#blog-demo">Design systems</a></li>
                <li><a class="mv-badge" data-variant="outline" data-shape="pill" href="#blog-demo">Responsive design</a></li>
              </ul>
              <mv-share-button url="https://tessera.dev/blog/css-container-queries-guide?utm_source=newsletter" title="A practical guide to CSS container queries" text="Container queries, container units and style queries, with a migration checklist." networks="x linkedin bluesky hackernews"></mv-share-button>
            </div>
            <div class="mv-blog-bio">
              <span class="mv-avatar" data-size="xl"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23bae6fd'/%3E%3Cstop offset='1' stop-color='%232563eb'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%23172554'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23e0ac69'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23e0ac69'/%3E%3Cpath d='M20.5 26c0-8 5-13 11.5-13s11.5 5 11.5 13c-2-4-6-6.5-11.5-6.5S22.5 22 20.5 26z' fill='%233b2418'/%3E%3C/svg%3E" alt="" width="64" height="64"></span>
              <p class="mv-blog-bio-name">Mateo Alvarez<span class="mv-blog-details">Staff Frontend Engineer, Design Systems</span></p>
              <p>Mateo leads the component platform at Tessera. Before that he spent six years making checkout flows faster at a travel company in Madrid.</p>
              <a class="mv-blog-more" href="#blog-demo">More from Mateo</a>
            </div>
          </footer>

          <section class="mv-blog-related" aria-labelledby="blog-a-related">
            <div class="mv-blog-related-header">
              <h2 class="mv-blog-related-title" id="blog-a-related">Keep reading</h2>
              <a class="mv-blog-more" href="#blog-demo">All articles</a>
            </div>
            <div class="mv-blog-grid">
          <article class="mv-blog-card">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%230f0a1f'/%3E%3Cstop offset='1' stop-color='%231e1b4b'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Crect x='0' y='0' width='600' height='750' fill='%23fff7fb'/%3E%3Crect x='110' y='150' width='36' height='450' rx='18' fill='%23fdf2f8'/%3E%3Crect x='154' y='150' width='36' height='450' rx='18' fill='%23fce7f3'/%3E%3Crect x='198' y='150' width='36' height='450' rx='18' fill='%23fbcfe8'/%3E%3Crect x='242' y='150' width='36' height='450' rx='18' fill='%23f9a8d4'/%3E%3Crect x='286' y='150' width='36' height='450' rx='18' fill='%23f472b6'/%3E%3Crect x='330' y='150' width='36' height='450' rx='18' fill='%23ec4899'/%3E%3Crect x='374' y='150' width='36' height='450' rx='18' fill='%23db2777'/%3E%3Crect x='418' y='150' width='36' height='450' rx='18' fill='%23be185d'/%3E%3Crect x='462' y='150' width='36' height='450' rx='18' fill='%239d174d'/%3E%3Crect x='690' y='150' width='36' height='450' rx='18' fill='%231e1b4b'/%3E%3Crect x='734' y='150' width='36' height='450' rx='18' fill='%232e1065'/%3E%3Crect x='778' y='150' width='36' height='450' rx='18' fill='%234c1d95'/%3E%3Crect x='822' y='150' width='36' height='450' rx='18' fill='%235b21b6'/%3E%3Crect x='866' y='150' width='36' height='450' rx='18' fill='%236d28d9'/%3E%3Crect x='910' y='150' width='36' height='450' rx='18' fill='%237c3aed'/%3E%3Crect x='954' y='150' width='36' height='450' rx='18' fill='%238b5cf6'/%3E%3Crect x='998' y='150' width='36' height='450' rx='18' fill='%23a78bfa'/%3E%3Crect x='1042' y='150' width='36' height='450' rx='18' fill='%23c4b5fd'/%3E%3Ccircle cx='392' cy='120' r='12' fill='%23db2777'/%3E%3Ccircle cx='1016' cy='120' r='12' fill='%23a78bfa'/%3E%3C/svg%3E" alt="" width="1200" height="750" loading="lazy" decoding="async"></div>
            <p class="mv-blog-meta"><span class="mv-blog-category">Design</span></p>
            <h3 class="mv-blog-card-title"><a class="mv-blog-link" href="#blog-demo">Choosing color tokens that survive dark mode</a></h3>
            <p class="mv-blog-excerpt">Why we stopped inverting palettes, how OKLCH lightness steps keep contrast predictable, and the three tokens every component actually needs.</p>
            <p class="mv-blog-byline"><span class="mv-avatar" data-size="sm"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23fecdd3'/%3E%3Cstop offset='1' stop-color='%23e11d48'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%234c0519'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23c68642'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23c68642'/%3E%3Cpath d='M20 27c-1-9 4-15 12-15 7 0 13 5 12 14-3-3-6-5-9-5-4 3-10 3-15 6z' fill='%23292524'/%3E%3Ccircle cx='32' cy='12' r='6' fill='%23292524'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Noor Haddad</span><span class="mv-blog-details"><time datetime="2026-08-14">Aug 14, 2026</time> · 6 min read</span></span></p>
          </article>
          <article class="mv-blog-card">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23fcd34d'/%3E%3Cstop offset='1' stop-color='%23f97316'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Crect x='290' y='95' width='620' height='560' rx='36' fill='%23fffbeb' stroke='%23f59e0b' stroke-opacity='.35' stroke-width='3'/%3E%3Crect x='340' y='135' width='200' height='30' rx='15' fill='%2392400e' fill-opacity='.85'/%3E%3Ccircle cx='810' cy='150' r='18' fill='%23fde68a'/%3E%3Ccircle cx='860' cy='150' r='18' fill='%23fde68a'/%3E%3Crect x='340' y='210' width='62' height='62' rx='16' fill='%23fef3c7'/%3E%3Crect x='418' y='210' width='62' height='62' rx='16' fill='%23fef3c7'/%3E%3Crect x='496' y='210' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='574' y='210' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='652' y='210' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='730' y='210' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='808' y='210' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='340' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='418' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='496' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='574' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='652' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='730' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='808' y='292' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='340' y='374' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='418' y='374' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='496' y='374' width='62' height='62' rx='16' fill='%23fcd34d'/%3E%3Crect x='574' y='374' width='62' height='62' rx='16' fill='%23fcd34d'/%3E%3Crect x='652' y='374' width='62' height='62' rx='16' fill='%23b45309'/%3E%3Crect x='643' y='365' width='80' height='80' rx='22' fill='none' stroke='%231c1917' stroke-width='5'/%3E%3Crect x='730' y='374' width='62' height='62' rx='16' fill='%23fcd34d'/%3E%3Crect x='808' y='374' width='62' height='62' rx='16' fill='%23fcd34d'/%3E%3Crect x='340' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='418' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='496' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='574' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='652' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='730' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='808' y='456' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='340' y='538' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='418' y='538' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='496' y='538' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='574' y='538' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='652' y='538' width='62' height='62' rx='16' fill='%23fde68a'/%3E%3Crect x='730' y='538' width='62' height='62' rx='16' fill='%23fef3c7'/%3E%3Crect x='808' y='538' width='62' height='62' rx='16' fill='%23fef3c7'/%3E%3C/svg%3E" alt="" width="1200" height="750" loading="lazy" decoding="async"></div>
            <p class="mv-blog-meta"><span class="mv-blog-category">Design</span></p>
            <h3 class="mv-blog-card-title"><a class="mv-blog-link" href="#blog-demo">Designing an accessible date picker, one keystroke at a time</a></h3>
            <p class="mv-blog-excerpt">Arrow keys, Page Up, live announcements and the grid pattern that finally behaved in all three major screen readers. Notes from eleven rounds of testing.</p>
            <p class="mv-blog-byline"><span class="mv-avatar" data-size="sm"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23ddd6fe'/%3E%3Cstop offset='1' stop-color='%237c3aed'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%231e1b4b'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23f1c7a8'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23f1c7a8'/%3E%3Cpath d='M20 27c-1-9 4-15 12-15 7 0 13 5 12 14-3-3-6-5-9-5-4 3-10 3-15 6z' fill='%23111827'/%3E%3Ccircle cx='32' cy='12' r='6' fill='%23111827'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Aiko Tanaka</span><span class="mv-blog-details"><time datetime="2026-09-04">Sep 4, 2026</time> · 11 min read</span></span></p>
          </article>
          <article class="mv-blog-card">
            <div class="mv-blog-cover"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 750'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%231e1b4b'/%3E%3Cstop offset='1' stop-color='%233730a3'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1200' height='750' fill='url%28%23bg%29'/%3E%3Cline x1='160' y1='110' x2='160' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='270' y1='110' x2='270' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='380' y1='110' x2='380' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='490' y1='110' x2='490' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='600' y1='110' x2='600' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='710' y1='110' x2='710' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='820' y1='110' x2='820' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='930' y1='110' x2='930' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Cline x1='1040' y1='110' x2='1040' y2='640' stroke='%23a5b4fc' stroke-opacity='.14' stroke-width='2'/%3E%3Crect x='160' y='140' width='809.6' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='140' width='176' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='319' cy='157' r='7' fill='%234338ca'/%3E%3Crect x='160' y='222' width='651.2' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='222' width='140.8' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='283.8' cy='239' r='7' fill='%234338ca'/%3E%3Crect x='160' y='304' width='756.8' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='304' width='193.6' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='336.6' cy='321' r='7' fill='%234338ca'/%3E%3Crect x='160' y='386' width='528' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='386' width='105.6' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='248.60000000000002' cy='403' r='7' fill='%234338ca'/%3E%3Crect x='160' y='468' width='704' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='468' width='158.4' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='301.4' cy='485' r='7' fill='%234338ca'/%3E%3Crect x='160' y='550' width='598.4000000000001' height='34' rx='17' fill='%23818cf8' fill-opacity='.22'/%3E%3Crect x='160' y='550' width='132' height='34' rx='17' fill='%23e0e7ff'/%3E%3Ccircle cx='275' cy='567' r='7' fill='%234338ca'/%3E%3Cline x1='353.6' y1='110' x2='353.6' y2='640' stroke='%23fbbf24' stroke-width='4' stroke-dasharray='10 12'/%3E%3C/svg%3E" alt="" width="1200" height="750" loading="lazy" decoding="async"></div>
            <p class="mv-blog-meta"><span class="mv-blog-category">Engineering</span></p>
            <h3 class="mv-blog-card-title"><a class="mv-blog-link" href="#blog-demo">How we cut our CI pipeline from 42 to 9 minutes</a></h3>
            <p class="mv-blog-excerpt">Remote caching, test sharding by historical duration and one honest conversation about flaky tests. What moved the needle, what didn’t, and the dashboards we kept.</p>
            <p class="mv-blog-byline"><span class="mv-avatar" data-size="sm"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23fbcfe8'/%3E%3Cstop offset='1' stop-color='%23db2777'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%233b0764'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23b97a56'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23b97a56'/%3E%3Cpath d='M19 30c-1-11 5-18 13-18s14 7 13 18c-1-5-3-8-4-9-3 2-8 3-13 2-4 0-7 3-9 7z' fill='%231c1917'/%3E%3Cpath d='M19 28c0 8 1 14 3 18l3-1c-2-5-3-11-2-16z M45 28c0 8-1 14-3 18l-3-1c2-5 3-11 2-16z' fill='%231c1917'/%3E%3C/svg%3E" alt="" width="64" height="64"></span><span><span class="mv-blog-author">Priya Raman</span><span class="mv-blog-details"><time datetime="2026-09-18">Sep 18, 2026</time> · 9 min read</span></span></p>
          </article>
            </div>
          </section>
        </div>
      </article>
    </mv-blog>
  </div>
</mv-tabs>

API

Attributes

NameTypDefaultDescription
data-variantfeatured | article(grid)On .mv-blog. (default) header then .mv-blog-filters and a .mv-blog-grid of cards (1, 2, then 3 columns); featured: a lead .mv-blog-card[data-size="lg"] beside a .mv-blog-latest list from 60rem; article: header, cover, then .mv-blog-toc in a sticky left rail beside the .mv-prose body from 62rem (above it, boxed, when narrower), footer and related posts.
categorystring(all)On <mv-blog>: the active filter value. Entries whose data-category (space separated) does not contain it get hidden. Removing it shows everything. Set by clicks on the filters, and reflected.
data-categorystringOn each filterable entry (.mv-blog-card, .mv-blog-item): its category values, space separated.
data-filterstringOn each filter link or button inside .mv-blog-filters: the category value it selects; "all" (or empty) selects everything. Keep a real href on links so filtering still works server side without JS.
data-sizelgOn .mv-blog-card: the lead story size (larger title, 4 line excerpt).

Properties

NameTypDescription
categorystring | nullActive filter value (null for all).
stringsobjectTranslatable announcements, merged with the defaults: { all: { one: "Showing {n} article", other: "Showing {n} articles" }, filtered: { one: "Showing {n} article in {category}", other: "Showing {n} articles in {category}" } }. Plural forms follow Intl.PluralRules and numbers Intl.NumberFormat for the nearest [lang] (default en-US). Can be set before the element is defined.

Events

NameDescription
mv-filterAfter a filter is applied by the user or the category attribute: detail = { category, count } (category is "all" when nothing is filtered).

Content structure

NameDescription
.mv-blog-headerGrid and featured variants: .mv-blog-eyebrow (accent square, optional .mv-blog-count), h2.mv-blog-title, .mv-blog-lead and/or a.mv-blog-more; title left, lead or link right from 48rem.
nav.mv-blog-filters > ulCategory filters: <a href="?category=design" data-filter="design">Design <span class="mv-blog-filter-count">02</span></a>. aria-current="page" marks the active one. Scrolls horizontally on narrow screens.
.mv-blog-grid > article.mv-blog-cardCard: .mv-blog-cover > img (width, height, loading="lazy"), p.mv-blog-meta > .mv-blog-category (entry number added on the right), h3.mv-blog-card-title > a.mv-blog-link (covers the whole card, the only link in it), p.mv-blog-excerpt (clamped), p.mv-blog-byline (.mv-avatar, .mv-blog-author, .mv-blog-details with <time>).
.mv-blog-featuredFeatured variant: a .mv-blog-card[data-size="lg"], then .mv-blog-latest with a heading (.mv-blog-eyebrow) and ol.mv-blog-list > li.mv-blog-item (.mv-blog-meta, h4.mv-blog-item-title > a.mv-blog-link, .mv-blog-details). Numbering continues from the lead story.
.mv-blog-article-headerArticle variant: nav.mv-breadcrumb, .mv-blog-meta > .mv-blog-category, h1.mv-blog-article-title, p.mv-blog-standfirst, .mv-blog-byline (avatar, author and role, then date and reading time pushed to the end).
figure.mv-blog-figureArticle cover: .mv-blog-cover > img (16:10, 21:9 from 48rem) and an optional figcaption.
nav.mv-blog-tocTable of contents: p.mv-blog-toc-title, then ol > li > a[href="#heading-id"] (numbered; a nested ol for subsections). <mv-blog> sets aria-current="location" on the section being read.
.mv-proseArticle body, styled by the typography component; a direct child of .mv-blog-inner (keep the mv-prose class alone on that element). Give each h2 the id its table of contents link targets.
footer.mv-blog-article-footer.mv-blog-share (ul.mv-blog-tags of a.mv-badge, and a <mv-share-button>) and .mv-blog-bio (.mv-avatar, p.mv-blog-bio-name with .mv-blog-details, a p, a.mv-blog-more).
section.mv-blog-related.mv-blog-related-header (h2.mv-blog-related-title, a.mv-blog-more) and a .mv-blog-grid of cards.

CSS classes

NameDescription
mv-blogRoot section (or <article> for the article variant), mv-blog query container.
mv-blog-innerCentered content grid with the section padding.

CSS variables

NameDefaultDescription
--mv-blog-max72rem (70rem for article)Max width.
--mv-blog-columns3Card grid columns from 60rem.
--mv-blog-cover-ratio16 / 10Cover aspect ratio.
--mv-blog-excerpt-lines3 (4 for the lead story)Lines shown before the excerpt is truncated.
--mv-blog-sticky-topvar(--mv-space-8)Sticky table of contents offset and heading scroll margin (your navbar height).
--mv-blog-rulevar(--mv-border)Hairline color.
--mv-blog-bgtransparentSection background.

Accessibility

Each card has exactly one link, its title, stretched over the card with a pseudo-element, so there are no nested links and the card reads as its heading; covers are decorative (alt="") and dates are <time datetime>. Entry numbers are CSS-generated with empty alternative text, so screen readers skip them. Filters are links in a labeled <nav> that still reach a server-rendered category page without JS; with JS, a click filters in place, moves aria-current="page" and a polite status announces the count. The table of contents is a labeled <nav>; the section being read gets aria-current="location", and headings keep a scroll margin under a sticky navbar. Focus rings cover the whole card; forced colors mode keeps outlines, the active filter and the current section.

Diese Seite wurde mit KI übersetzt. Übersetzungsfehler melden