App Shell <mv-app-shell>

완성형 대시보드 블록: 접을 수 있는 사이드바, 고정 헤더(이동 경로, Ctrl/⌘ + K 검색, 읽지 않은 알림, 계정), 스파크라인이 있는 KPI 타일, 기간별 매출 차트, 활동 표.

작동 방식

완성형 대시보드 블록: 접을 수 있는 사이드바, 고정 헤더(이동 경로, Ctrl/⌘ + K 검색, 읽지 않은 알림, 계정), 스파크라인이 있는 KPI 타일, 기간별 매출 차트, 활동 표. 레이아웃은 컨테이너 쿼리로 제어됩니다.

카테고리블록
유형Web Component (<mv-app-shell>)
상태안정
함께 설치되는 항목badge, card, chart, command, dialog, popover, tooltip, sidebar, table
Keywordsdashboard, app-shell, layout, admin, kpi, header, command-palette

When to use

  • An admin or SaaS dashboard needs a ready layout with sidebar, sticky header, KPI tiles, a chart and an activity table
  • Users expect a Ctrl/Cmd + K search palette, breadcrumbs and unread notifications in the app header
  • A back-office prototype must adapt to its container width without separate mobile templates

Avoid when

  • Only the collapsible side navigation is needed inside an existing layout → use Sidebar instead
  • The page is a public marketing site rather than a logged-in app → use Navbar instead

설치

node scripts/add.mjs app-shell --out ./src/marvelous

Marvelous UI MCP 서버를 사용하는 AI 에이전트: install_components({ slugs: ["app-shell"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

복사되는 파일(의존성 포함): tokens/tokens.css, core/base.css, components/badge/badge.css, components/card/card.css, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/chart/chart.js, components/chart/chart.css, components/command/command.js, components/command/command.css, components/dialog/dialog.js, components/dialog/dialog.css, core/focus.js, core/position.js, core/svg.js, components/popover/popover.js, components/popover/popover.css, components/tooltip/tooltip.js, components/tooltip/tooltip.css, components/sidebar/sidebar.js, components/sidebar/sidebar.css, components/table/table.css, components/table/table.js, components/app-shell/app-shell.js, components/app-shell/app-shell.css.

사용법

기본 마크업입니다. 여기서 시작해 속성, data-*, CSS 변수로 커스터마이즈하세요:

<mv-app-shell id="as-demo">
  <div class="mv-sidebar-layout" data-variant="inset" style="height:780px;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);overflow:hidden">
    <mv-sidebar label="Main navigation" storage-key="mv-demo-app-shell">
      <div class="mv-sidebar-header">
        <mv-menu placement="bottom-start">
          <button class="mv-sidebar-item" data-size="lg" type="button">
            <span class="mv-sidebar-logo" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V5l8 7 8-7v14"/></svg></span>
            <span class="mv-sidebar-label"><strong>Nova Supply</strong><small>Online store</small></span>
            <mv-icon class="mv-sidebar-item-end" name="chevrons-up-down"></mv-icon>
          </button>
          <div data-content style="--mv-menu-width:15rem">
            <div class="mv-menu-label">Workspaces</div>
            <div role="group" data-name="workspace">
              <button role="menuitemradio" aria-checked="true" value="nova">Nova Supply</button>
              <button role="menuitemradio" value="lumen">Studio Lumen</button>
            </div>
            <hr>
            <button role="menuitem" value="new-workspace">New workspace…</button>
          </div>
        </mv-menu>
      </div>

      <nav class="mv-sidebar-content" aria-label="Sections">
        <div class="mv-sidebar-group">
          <h2 class="mv-sidebar-group-label">Store</h2>
          <ul class="mv-sidebar-menu">
            <li><a class="mv-sidebar-item" href="#as-demo" aria-current="page"><mv-icon name="dashboard"></mv-icon><span class="mv-sidebar-label">Dashboard</span></a></li>
            <li><a class="mv-sidebar-item" href="#as-demo"><mv-icon name="shopping-bag"></mv-icon><span class="mv-sidebar-label">Orders</span><span class="mv-sidebar-badge" data-variant="accent">8<span class="mv-sr-only"> to process</span></span></a></li>
            <li><a class="mv-sidebar-item" href="#as-demo"><mv-icon name="package"></mv-icon><span class="mv-sidebar-label">Products</span></a></li>
            <li><a class="mv-sidebar-item" href="#as-demo"><mv-icon name="users"></mv-icon><span class="mv-sidebar-label">Customers</span></a></li>
            <li class="mv-sidebar-sub">
              <button class="mv-sidebar-item"><mv-icon name="chart"></mv-icon><span class="mv-sidebar-label">Reports</span></button>
              <ul class="mv-sidebar-submenu">
                <li><a class="mv-sidebar-subitem" href="#as-demo">Sales</a></li>
                <li><a class="mv-sidebar-subitem" href="#as-demo">Traffic</a></li>
                <li><a class="mv-sidebar-subitem" href="#as-demo">Retention</a></li>
              </ul>
            </li>
            <li><a class="mv-sidebar-item" href="#as-demo"><mv-icon name="megaphone"></mv-icon><span class="mv-sidebar-label">Marketing</span></a></li>
          </ul>
        </div>
        <div class="mv-sidebar-group" data-push>
          <ul class="mv-sidebar-menu">
            <li><a class="mv-sidebar-item" href="#as-demo"><mv-icon name="settings"></mv-icon><span class="mv-sidebar-label">Settings</span></a></li>
            <li><a class="mv-sidebar-item" href="#as-demo"><mv-icon name="help"></mv-icon><span class="mv-sidebar-label">Help</span></a></li>
          </ul>
        </div>
      </nav>

      <div class="mv-sidebar-footer">
        <mv-menu placement="right-start">
          <button class="mv-sidebar-item" data-size="lg" type="button">
            <span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:200" aria-hidden="true"><span class="mv-avatar-fallback">CR</span></span>
            <span class="mv-sidebar-label"><strong>Chloe Reed</strong><small>Admin</small></span>
            <mv-icon class="mv-sidebar-item-end" name="chevrons-up-down"></mv-icon>
          </button>
          <div data-content style="--mv-menu-width:14rem">
            <button role="menuitem" value="profile">Profile</button>
            <button role="menuitem" value="billing">Billing</button>
            <hr>
            <button role="menuitem" value="logout">Log out</button>
          </div>
        </mv-menu>
      </div>
    </mv-sidebar>

    <div class="mv-sidebar-inset">
      <header class="mv-app-shell-header">
        <button class="mv-button" data-variant="ghost" data-size="icon" data-mv-sidebar-toggle aria-label="Toggle sidebar" title="Sidebar (Ctrl B)">
          <mv-icon name="sidebar"></mv-icon>
        </button>
        <div class="mv-separator" role="separator" aria-orientation="vertical" data-orientation="vertical" style="height:1rem;min-height:1rem;align-self:center"></div>
        <nav class="mv-breadcrumb" aria-label="Breadcrumb" data-size="sm">
          <ol>
            <li><a href="#as-demo">Nova Supply</a></li>
            <li><span aria-current="page">Dashboard</span></li>
          </ol>
        </nav>

        <div class="mv-app-shell-actions">
          <mv-dialog data-app-shell-search>
            <button class="mv-app-shell-search" type="button" data-mv-open aria-label="Search (Ctrl K)" aria-keyshortcuts="Control+K Meta+K /">
              <mv-icon name="search"></mv-icon>
              <span>Search…</span>
              <kbd class="mv-kbd-group"><kbd class="mv-kbd" data-size="sm">Ctrl</kbd><kbd class="mv-kbd" data-size="sm">K</kbd></kbd>
            </button>
            <dialog class="mv-command-dialog" aria-label="Search">
              <mv-command label="Search Nova Supply">
                <input placeholder="Search orders, customers, pages…">
                <div class="mv-command-list">
                  <div class="mv-command-group" data-label="Pages">
                    <div class="mv-command-item" data-value="orders" data-keywords="sales carts">
                      <mv-icon name="shopping-bag"></mv-icon>
                      Orders <span class="mv-command-item-meta">8 to process</span>
                    </div>
                    <div class="mv-command-item" data-value="customers" data-keywords="contacts buyers">
                      <mv-icon name="user"></mv-icon>
                      Customers
                    </div>
                    <div class="mv-command-item" data-value="reports" data-keywords="analytics sales traffic">
                      <mv-icon name="chart"></mv-icon>
                      Sales reports
                    </div>
                  </div>
                  <div class="mv-command-group" data-label="Actions">
                    <div class="mv-command-item" data-value="new-product" data-keywords="add create item">
                      <mv-icon name="plus"></mv-icon>
                      New product <span class="mv-command-shortcut"><kbd>N</kbd></span>
                    </div>
                    <div class="mv-command-item" data-value="export" data-keywords="csv download">
                      <mv-icon name="download"></mv-icon>
                      Export this month’s sales
                    </div>
                  </div>
                </div>
                <div class="mv-command-empty">No results.</div>
              </mv-command>
            </dialog>
          </mv-dialog>

          <mv-popover placement="bottom-end">
            <button class="mv-button mv-badge-anchor" data-variant="ghost" data-size="icon" data-app-shell-unread-label="Notifications" aria-label="Notifications">
              <mv-icon name="bell"></mv-icon>
              <span class="mv-badge" data-variant="danger" data-appearance="solid" data-shape="pill" data-size="sm" data-position="top-right" style="--mv-badge-offset:4px" data-app-shell-unread aria-hidden="true">3</span>
            </button>
            <div data-content style="--mv-popover-width:22rem">
              <div class="mv-app-shell-notifs">
                <div class="mv-app-shell-notifs-head">
                  <h3>Notifications</h3>
                  <button class="mv-button" data-variant="ghost" data-size="sm" data-app-shell-read-all>Mark all as read</button>
                </div>
                <ul>
                  <li><a class="mv-app-shell-notif" href="#as-demo" data-unread><span class="mv-avatar" data-size="sm" data-variant="tinted" style="--mv-avatar-hue:40" aria-hidden="true"><span class="mv-avatar-fallback">JD</span></span><span><p><strong>James Davis</strong> placed an order for $214.90</p><time datetime="2026-09-23T10:42">4 min ago</time></span></a></li>
                  <li><a class="mv-app-shell-notif" href="#as-demo" data-unread><span class="mv-avatar" data-size="sm" data-variant="tinted" style="--mv-avatar-hue:160" aria-hidden="true"><span class="mv-avatar-fallback">MC</span></span><span><p><strong>Mateo Cruz</strong> left a 5-star review on “Eclipse Lamp”</p><time datetime="2026-09-23T09:15">1 hr ago</time></span></a></li>
                  <li><a class="mv-app-shell-notif" href="#as-demo" data-unread><span class="mv-avatar" data-size="sm" data-variant="tinted" style="--mv-avatar-hue:20" aria-hidden="true"><span class="mv-avatar-fallback">!</span></span><span><p><strong>Low stock:</strong> only 3 “Opal Vase” left</p><time datetime="2026-09-23T08:02">2 hr ago</time></span></a></li>
                  <li><a class="mv-app-shell-notif" href="#as-demo"><span class="mv-avatar" data-size="sm" data-variant="tinted" style="--mv-avatar-hue:280" aria-hidden="true"><span class="mv-avatar-fallback">IM</span></span><span><p><strong>Imani Moore</strong> joined the team</p><time datetime="2026-09-22T17:30">Yesterday</time></span></a></li>
                </ul>
                <div class="mv-app-shell-notifs-foot"><a href="#as-demo">View all notifications</a></div>
              </div>
            </div>
          </mv-popover>

          <mv-menu placement="bottom-end">
            <button class="mv-button" data-variant="ghost" data-size="icon" aria-label="My account" style="border-radius:var(--mv-radius-full)">
              <span class="mv-avatar" data-size="sm" data-variant="tinted" style="--mv-avatar-hue:200" aria-hidden="true"><span class="mv-avatar-fallback">CR</span></span>
            </button>
            <div data-content style="--mv-menu-width:14rem">
              <div class="mv-menu-label">[email protected]</div>
              <button role="menuitem" value="profile">Profile</button>
              <button role="menuitem" value="settings">Settings <span class="mv-menu-shortcut">⌘,</span></button>
              <hr>
              <button role="menuitem" value="logout">Log out</button>
            </div>
          </mv-menu>
        </div>
      </header>

      <main class="mv-app-shell-main" aria-labelledby="as-demo-title">
        <div class="mv-app-shell-heading">
          <div>
            <h1 class="mv-app-shell-title" id="as-demo-title">Dashboard</h1>
            <p class="mv-app-shell-subtitle">Hi Chloe, here’s how the store did in September.</p>
          </div>
          <div class="mv-app-shell-toolbar">
            <button class="mv-button" data-variant="outline" data-size="sm">
              <mv-icon name="download"></mv-icon>
              Export
            </button>
            <button class="mv-button" data-size="sm">
              <mv-icon name="plus"></mv-icon>
              New product
            </button>
          </div>
        </div>

        <section class="mv-app-shell-stats" aria-label="Key metrics">
          <article class="mv-card mv-app-shell-stat">
            <div class="mv-app-shell-stat-head"><span>Revenue</span><mv-icon name="dollar"></mv-icon></div>
            <div class="mv-app-shell-stat-value">$48,290</div>
            <div class="mv-app-shell-stat-foot"><span class="mv-badge" data-variant="success" data-size="sm">▲ 12.4%</span> vs Aug</div>
            <mv-chart sparkline type="area" label="Revenue, last 12 weeks" format="currency" data='{"labels":["W27","W28","W29","W30","W31","W32","W33","W34","W35","W36","W37","W38"],"series":[{"name":"Revenue","values":[8100,8900,8400,9600,9100,10200,9800,10900,11400,10800,11900,12600]}]}'></mv-chart>
          </article>
          <article class="mv-card mv-app-shell-stat">
            <div class="mv-app-shell-stat-head"><span>New customers</span><mv-icon name="user-plus"></mv-icon></div>
            <div class="mv-app-shell-stat-value">1,284</div>
            <div class="mv-app-shell-stat-foot"><span class="mv-badge" data-variant="success" data-size="sm">▲ 8.1%</span> vs Aug</div>
            <mv-chart sparkline type="area" label="New customers, last 12 weeks" data='{"labels":["W27","W28","W29","W30","W31","W32","W33","W34","W35","W36","W37","W38"],"series":[{"name":"Customers","values":[240,262,251,270,288,276,301,297,312,305,322,331]}]}'></mv-chart>
          </article>
          <article class="mv-card mv-app-shell-stat">
            <div class="mv-app-shell-stat-head"><span>Orders</span><mv-icon name="shopping-bag"></mv-icon></div>
            <div class="mv-app-shell-stat-value">3,912</div>
            <div class="mv-app-shell-stat-foot"><span class="mv-badge" data-variant="danger" data-size="sm">▼ 2.3%</span> vs Aug</div>
            <mv-chart sparkline type="area" label="Orders, last 12 weeks" data='{"labels":["W27","W28","W29","W30","W31","W32","W33","W34","W35","W36","W37","W38"],"series":[{"name":"Orders","values":[1010,1042,998,1065,1031,1002,987,1019,972,961,948,936]}]}'></mv-chart>
          </article>
          <article class="mv-card mv-app-shell-stat">
            <div class="mv-app-shell-stat-head"><span>Conversion rate</span><mv-icon name="trending-up"></mv-icon></div>
            <div class="mv-app-shell-stat-value">3.42%</div>
            <div class="mv-app-shell-stat-foot"><span class="mv-badge" data-variant="success" data-size="sm">▲ 0.4 pt</span> vs Aug</div>
            <mv-chart sparkline type="area" label="Conversion rate, last 12 weeks" format="percent" data='{"labels":["W27","W28","W29","W30","W31","W32","W33","W34","W35","W36","W37","W38"],"series":[{"name":"Conversion","values":[2.9,3.0,2.95,3.1,3.05,3.2,3.15,3.28,3.3,3.25,3.38,3.42]}]}'></mv-chart>
          </article>
        </section>

        <div class="mv-app-shell-row">
          <article class="mv-card">
            <header class="mv-card-header mv-app-shell-card-head">
              <div>
                <h2 class="mv-card-title">Sales</h2>
                <p class="mv-card-description" id="as-demo-range-desc">Last 30 days, in USD</p>
              </div>
              <mv-tabs data-variant="segmented" data-size="sm" value="30" id="as-demo-range">
                <div role="tablist" aria-label="Period">
                  <button role="tab" data-value="7">7d</button>
                  <button role="tab" data-value="30">30d</button>
                  <button role="tab" data-value="90">3 mo</button>
                </div>
              </mv-tabs>
            </header>
            <div class="mv-card-content">
              <mv-chart id="as-demo-chart" type="area" format="currency" height="250" label="Daily sales"></mv-chart>
            </div>
          </article>

          <article class="mv-card">
            <header class="mv-card-header">
              <h2 class="mv-card-title">Traffic sources</h2>
              <p class="mv-card-description">24,812 visits this month</p>
            </header>
            <div class="mv-card-content">
              <ul class="mv-app-shell-breakdown">
                <li><span>Organic search</span><strong>42%<small> 10,421</small></strong><progress class="mv-progress" data-size="sm" max="100" value="42" aria-label="Organic search, 42%"></progress></li>
                <li><span>Direct</span><strong>27%<small> 6,699</small></strong><progress class="mv-progress" data-size="sm" max="100" value="27" aria-label="Direct, 27%"></progress></li>
                <li><span>Social media</span><strong>18%<small> 4,466</small></strong><progress class="mv-progress" data-size="sm" max="100" value="18" aria-label="Social media, 18%"></progress></li>
                <li><span>Newsletter</span><strong>13%<small> 3,226</small></strong><progress class="mv-progress" data-size="sm" max="100" value="13" aria-label="Newsletter, 13%"></progress></li>
              </ul>
            </div>
          </article>
        </div>

        <article class="mv-card">
          <header class="mv-card-header">
            <h2 class="mv-card-title" id="as-demo-orders-title">Recent orders</h2>
            <p class="mv-card-description">5 of 3,912 orders this month</p>
            <div class="mv-card-action"><a class="mv-button" data-variant="outline" data-size="sm" href="#as-demo">View all</a></div>
          </header>
          <div class="mv-card-content">
            <div class="mv-table-wrap" role="region" aria-labelledby="as-demo-orders-title" tabindex="0">
              <table class="mv-table" data-hover aria-labelledby="as-demo-orders-title">
                <thead>
                  <tr><th scope="col">Customer</th><th scope="col" data-app-shell-hide="sm">Order</th><th scope="col">Status</th><th scope="col" data-app-shell-hide="sm">Date</th><th scope="col" data-align="end">Amount</th></tr>
                </thead>
                <tbody>
                  <tr><td><span class="mv-app-shell-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:40" aria-hidden="true"><span class="mv-avatar-fallback">JD</span></span><span><strong>James Davis</strong><small>[email protected]</small></span></span></td><td data-app-shell-hide="sm">#10492</td><td><span class="mv-table-status" data-status="success">Paid</span></td><td data-app-shell-hide="sm">Sep 23</td><td data-align="end">$214.90</td></tr>
                  <tr><td><span class="mv-app-shell-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:160" aria-hidden="true"><span class="mv-avatar-fallback">MC</span></span><span><strong>Mateo Cruz</strong><small>[email protected]</small></span></span></td><td data-app-shell-hide="sm">#10491</td><td><span class="mv-table-status" data-status="info">Shipped</span></td><td data-app-shell-hide="sm">Sep 23</td><td data-align="end">$89.00</td></tr>
                  <tr><td><span class="mv-app-shell-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:320" aria-hidden="true"><span class="mv-avatar-fallback">SN</span></span><span><strong>Sofia Nguyen</strong><small>[email protected]</small></span></span></td><td data-app-shell-hide="sm">#10490</td><td><span class="mv-table-status" data-status="warning">Pending</span></td><td data-app-shell-hide="sm">Sep 22</td><td data-align="end">$1,249.00</td></tr>
                  <tr><td><span class="mv-app-shell-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:240" aria-hidden="true"><span class="mv-avatar-fallback">TG</span></span><span><strong>Tomás García</strong><small>[email protected]</small></span></span></td><td data-app-shell-hide="sm">#10489</td><td><span class="mv-table-status" data-status="danger">Refunded</span></td><td data-app-shell-hide="sm">Sep 22</td><td data-align="end">$64.50</td></tr>
                  <tr><td><span class="mv-app-shell-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:100" aria-hidden="true"><span class="mv-avatar-fallback">LM</span></span><span><strong>Leila Mansour</strong><small>[email protected]</small></span></span></td><td data-app-shell-hide="sm">#10488</td><td><span class="mv-table-status" data-status="success">Paid</span></td><td data-app-shell-hide="sm">Sep 21</td><td data-align="end">$372.00</td></tr>
                </tbody>
              </table>
            </div>
          </div>
        </article>
      </main>
    </div>
  </div>
</mv-app-shell>
<script type="module">
  const shell = document.getElementById("as-demo");
  const chart = shell?.querySelector("#as-demo-chart");
  const desc = shell?.querySelector("#as-demo-range-desc");
  // Deterministic pseudo-random series so the demo is stable between reloads.
  const series = (n, seed, base) => {
    let s = seed;
    const rnd = () => ((s = (s * 16807) % 2147483647) / 2147483647);
    return Array.from({ length: n }, (_, i) => Math.round(base + i * base * 0.012 + (rnd() - 0.4) * base * 0.45 + Math.sin(i / 2.2) * base * 0.12));
  };
  const day = (offset) => new Date(Date.UTC(2026, 8, 23 - offset)).toLocaleDateString("en-US", { day: "numeric", month: "short", timeZone: "UTC" });
  const ranges = {
    7: { labels: Array.from({ length: 7 }, (_, i) => day(6 - i)), cur: series(7, 11, 1650), prev: series(7, 5, 1420), text: "Last 7 days, in USD" },
    30: { labels: Array.from({ length: 30 }, (_, i) => day(29 - i)), cur: series(30, 7, 1450), prev: series(30, 3, 1300), text: "Last 30 days, in USD" },
    90: { labels: Array.from({ length: 13 }, (_, i) => `W${26 + i}`), cur: series(13, 9, 10200), prev: series(13, 2, 9100), text: "Last 13 weeks, in USD" },
  };
  const show = (key) => {
    const r = ranges[key];
    if (!chart || !r) return;
    chart.data = { labels: r.labels, series: [{ name: "This period", values: r.cur }, { name: "Previous period", values: r.prev }] };
    if (desc) desc.textContent = r.text;
  };
  show("30");
  shell?.querySelector("#as-demo-range")?.addEventListener("mv-change", (e) => show(e.detail.value));
</script>

API

Attributes

Name유형DefaultDescription
hotkeystringkKey combined with Ctrl/⌘ to open search (“/” also opens it outside text fields); empty to disable.
no-skip-linkbooleanfalseDo not create the “Skip to content” link (when the page already has one). A link you write yourself as a.mv-app-shell-skip is kept.

Properties

Name유형Description
stringsPartial<Record<string, string | { one, other }>>Overrides for the default text: skip (skip link), unread (bell label, {label} and {n}, plural forms as { one, other }), unreadItem (hidden prefix read before an unread notification). Numbers and plurals follow the nearest lang (Intl). English defaults. Can be set before the element is defined.

Methods

NameDescription
openSearch()Opens the search palette.

Events

NameDescription
mv-navigateCommand picked in the palette (which then closes). detail: { value, label }.

Content structure

NameDescription
mv-dialog[data-app-shell-search]Search palette (a .mv-command-dialog dialog containing an <mv-command>).
[data-unread]Unread notification (dot); clicking marks it as read.
[data-app-shell-read-all]“Mark all as read” button.
[data-app-shell-unread]Unread counter, hidden at 0.
[data-app-shell-unread-label]On the bell button: its aria-label becomes “<value>, N unread”.

CSS classes

NameDescription
mv-app-shell-skipSkip link, first in the tab order, visible on focus; created automatically, it moves focus to .mv-app-shell-main (or <main>) without changing the URL.
mv-app-shell-header / -actions / -searchSticky blurred header, right-hand action area, fake search field (icon only in narrow containers).
mv-app-shell-main / -heading / -title / -subtitle / -toolbarContent (mv-app-shell query container), page title and actions.
mv-app-shell-stats / -stat / -stat-head / -stat-value / -stat-foot1 → 2 → 4 column grid (34 / 56rem) of KPI tiles.
mv-app-shell-row2/3 + 1/3 row above 56rem.
mv-app-shell-notifs / -notifNotification list inside a popover.
mv-app-shell-breakdown / -person / -card-headBar breakdown, customer cell (avatar + name), card header with actions.
[data-app-shell-hide="sm"]Column hidden below a 40rem container.

Accessibility

Skip link to the main content, first in the tab order. Landmarks: navigation region, header, <main> named by the page title. Search: button with aria-keyshortcuts, modal dialog (focus trap, Esc, focus returned). Bell: aria-label updated with the unread count (Intl plurals and numbers); unread notifications get a hidden “Unread:” prefix. KPIs: named section; accessible charts (data table). Tables with column headers, named by their card title. Forced colors: focus and unread dots drawn with system colors.

이 페이지는 AI로 번역되었습니다. 번역 문제 신고