본문으로 건너뛰기
레이아웃 / app-shell

AppShell

페이지 전체 레이아웃 chrome — 슬롯 골격과 Design/Gate 공용 workspace 표면.
레이아웃app-shellStatic specimenapp shell앱셸layout레이아웃
01 · Specimen

먼저 보고, 그다음 계약을 읽습니다

페이지 전체 레이아웃 chrome — 슬롯 골격과 Design/Gate 공용 workspace 표면.

Workbench 불러오는 중…
Component intent의도와 경계 읽기

variant 로 그리드 골격을 고른다: docs = Sidebar | Content | TOC, dashboard = Sidebar | Content, landing = Content. `.axe-workspace-shell`을 함께 쓰면 Design과 Gate가 공유하는 280px rail · 68rem bounded canvas · 30px navigation row 계약이 활성화된다. 본질은 CSS-only — Rust(maud)/Python(jinja) 도 같은 markup으로 같은 chrome을 그릴 수 있고, React wrapper는 슬롯 합성과 semantic role만 얹는 thin 층이다. 1024–1439px에서는 우측 context를 topbar disclosure로 옮기고, ≤1023px에서는 navigation과 context 양쪽을 native disclosure로 노출한다.

02 · Use

예제

SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.

01docs varianttsxfragment
docs variant
import { AppShell, Sidebar, Toc, TopNav, Footer } from "@axe/ui";<AppShell  variant="docs"  topbar={<TopNav /* ... */ />}  sidebar={    <Sidebar items={navItems} value={cur} onValueChange={setCur} />  }  toc={    <Toc      activeId={activeHeading}      items={[        { id: "intro", label: "소개" },        { id: "api", label: "API", level: 3 },      ]}    />  }  footer={<Footer />}>  <article className="axe-prose">…</article></AppShell>
02dashboard variant (toc 없음)tsxfragment
dashboard variant (toc 없음)
import { AppShell, Sidebar } from "@axe/ui";<AppShell  variant="dashboard"  sidebar={<Sidebar items={navItems} value={cur} onValueChange={setCur} />}  subnavLabel="탐색">  <section>…대시보드 본문…</section></AppShell>
03CSS-only 골격 (비-React)htmlfragment
CSS-only 골격 (비-React)
<div class="axe-app-shell" data-shell="docs">  <a href="#main" class="axe-skip-link">본문으로 건너뛰기</a>  <div class="axe-app-shell__topbar">    <!-- topbar -->    <details class="axe-app-shell__subnav">      <summary class="axe-app-shell__subnav-summary">        <span class="axe-app-shell__subnav-label">메뉴</span>      </summary>      <div class="axe-app-shell__subnav-panel">        <nav class="axe-sidebar" aria-label="Primary"><!-- 네비 복제 --></nav>      </div>    </details>  </div>  <div class="axe-app-shell__main">    <aside class="axe-app-shell__sidebar" aria-label="Primary">…</aside>    <main id="main" class="axe-app-shell__content">…</main>    <aside class="axe-app-shell__toc" aria-label="On this page">…</aside>  </div>  <div class="axe-app-shell__footer"><!-- footer --></div></div>
03 · React

Props

TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.

이름타입필수기본값설명
variant"docs" | "dashboard" | "landing""docs"그리드 골격 선택. data-shell 속성으로 CSS 가 분기.
topbarReact.ReactNode상단 바 슬롯(보통 <TopNav>).
sidebarReact.ReactNode좌측 네비 슬롯(보통 <Sidebar>). landing 에서는 무시.
tocReact.ReactNode우측 목차 슬롯(보통 <Toc>). variant="docs" 에서만 표시.
footerReact.ReactNode하단 슬롯(보통 <Footer>).
contentClassNamestring본문 <main> 에 추가 className.
contentAs"main" | "div""main"본문 태그. 다른 문서 안에 specimen으로 중첩할 때만 div.
contentIdstring"main"본문 id와 skip-link target.
showSkipLinkbooleantrue스킵 링크 렌더 여부. 중첩 specimen에서는 false.
sidebarLabelstring"Primary"sidebar <aside> 의 aria-label.
tocLabelstring"On this page"toc <aside> 의 aria-label.
subnavReact.ReactNode | false≤1023px disclosure 내용. 기본 = sidebar 재사용. 명시 DOM id가 있는 sidebar는 고유 id의 별도 노드를 넘기거나 false로 끈다.
subnavLabelReact.ReactNode"메뉴"≤1023px disclosure summary 라벨.
childrenReact.ReactNode본문 — <main id="main"> 안에 렌더.
...restReact.HTMLAttributes<HTMLDivElement>나머지는 루트 <div> 로 전파.
Toc.itemsTocItem[]필수{ id, label, level?: 2 | 3 } 배열. level=2 top / 3 sub(들여쓰기), 기본 2.
Toc.titleReact.ReactNode"On this page"목차 상단 라벨. string 이면 <nav> aria-label 로도 사용.
Toc.activeIdstring현재 활성 heading id(외부 스크롤 spy 가 전달). 해당 링크에 aria-current="location".
04 · Any stack

.axe-* 클래스 계약

React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.

클래스안정성용도
.axe-app-shellstable루트 <div>. data-shell="docs|dashboard|landing" 로 그리드 분기.
.axe-skip-linkstable본문(#main)으로 건너뛰는 스킵 링크(포커스 시 노출).
.axe-app-shell__topbarstable상단 바 영역(topbar + ≤1023px subnav 포함).
.axe-app-shell__mainstablesidebar|content|toc 그리드 행.
.axe-app-shell__sidebarstable좌측 네비 슬롯 <aside>(sticky/scroll).
.axe-app-shell__contentstable본문 <main id="main">. variant 별 max-width.
.axe-app-shell__tocstable우측 목차 슬롯 <aside>(docs 전용).
.axe-app-shell__footerstable하단 footer 슬롯.
.axe-app-shell__toc-titlestableToc 상단 라벨.
.axe-app-shell__toc-liststableToc 링크 <ul>.
.axe-app-shell__toc-linkstableToc 앵커 링크. 활성 시 aria-current="location".
.axe-app-shell__toc-link--h3stableh3(level 3) Toc 링크 — 들여쓰기 변형.
.axe-app-shell__subnavstable≤1023px 보조 네비 <details> disclosure(≥1024px display:none).
.axe-app-shell__subnav-summarystabledisclosure <summary> 토글.
.axe-app-shell__subnav-labelstabledisclosure summary 라벨 span.
.axe-app-shell__subnav-panelstabledisclosure 펼침 패널(네비 복제 렌더).
.axe-app-shell__subnav-tocstablesubnav 패널 안 목차 래퍼(docs + toc 있을 때).
.axe-app-shell__subnav-chevroninternalsubnav summary chevron(장식, [open] 회전은 CSS).
.axe-workspace-shellstableDesign/Gate 공용 neutral workspace root. AppShell docs variant와 함께 사용.
.axe-workspace-barstable56px workspace top bar.
.axe-workspace-bar__leftstabletop bar 좌측 rail track.
.axe-workspace-bar__centerstablebounded center route track.
.axe-workspace-bar__rightstable검색과 context action track.
.axe-workspace-bar__routesstabledesktop 중앙 route navigation.
.axe-workspace-bar__mobile-brandstablerail이 접힐 때 표시하는 mobile product identity.
.axe-workspace-bar__icon-buttonstable44px menu/context icon trigger.
.axe-workspace-disclosurestablenative details 기반 navigation/context sheet root.
.axe-workspace-disclosure--navigationstable≤1023px 좌측 navigation disclosure.
.axe-workspace-disclosure--contextstable<1440px 우측 context disclosure.
.axe-workspace-disclosure__panelstableviewport 고정 disclosure panel.
.axe-workspace-railstablefull-height primary rail flex root.
.axe-workspace-rail__brandstable56px entity/search header.
.axe-workspace-rail__bodystablerail 내부의 유일한 세로 scroll 영역.
.axe-workspace-rail__identitystable48px 고정 identity footer.
.axe-workspace-identitystablepill 없이 rail 폭을 채우는 고정 identity row.
.axe-workspace-identity__avatarstable20px brand-tone identity avatar.
.axe-workspace-identity__labelstable말줄임 처리되는 identity label.
.axe-workspace-identity-menustablerail 하단에서 위로 열리는 native identity menu.
.axe-workspace-identity-menu__panelstableidentity action을 담는 고정 폭 popover panel.
.axe-workspace-rail__navstable30px row와 1px seam을 소유하는 navigation.
.axe-workspace-rail__group-labelstable대메뉴 또는 비상호작용 group label.
.axe-workspace-rail__linkstablehover/active/focus가 정렬된 navigation link.
.axe-workspace-entitystablerail entity anchor 또는 native details switcher.
.axe-workspace-searchstabletext 또는 data-icon-only 검색 trigger.
.axe-workspace-search-dialogstablestatic CommandPalette markup을 native dialog로 호스트하는 workspace 검색 표면.
.axe-workspace-contextstablefull-height 우측 context rail.
.axe-workspace-context__headerstable현재 업무의 제목과 요약.
.axe-workspace-context__eyebrowstablecontext overline.
.axe-workspace-context__titlestablecontext title.
.axe-workspace-context__summarystablecontext 설명.
.axe-workspace-context__tabsstable균등 분할 context tab row.
.axe-workspace-context__tabstable현재 tab 하단 indicator trigger/link.
.axe-workspace-context__bodystablecontext panel content.
.axe-workspace-context__section-titlestablecontext section caption.
.axe-workspace-context__linkstable32px context location link.
.axe-workspace-context__footerstablecontext rail 고정 하단 metadata.
비-React 소비 노트
그리드 분기는 클래스가 아니라 루트의 data-shell="docs|dashboard|landing" 속성이 결정한다 — CSS-only 로 쓸 때 반드시 이 속성을 준다. ≤1023px 보조 네비는 네이티브 <details> 라 JS 없이도 토글되며, 데스크톱 컬럼과 동일 markup 을 subnav 패널에 복제해 넣으면 된다.
05 · Inclusive

접근성

키보드, ARIA, 구현 노트를 함께 검토합니다.

Keyboard
  • Tab 첫 스텝에서 .axe-skip-link 가 나타나 Enter 로 #main 본문 점프
  • ≤1023px subnav: <summary> 에서 Enter/Space 로 펼침/접힘(네이티브 <details>)
  • subnav 펼침 중 Esc — 닫고 summary 로 포커스 복원
  • workspace disclosure는 summary에서 Enter/Space로 열고, host JS가 Esc·외부 클릭·링크 이동 시 닫은 뒤 trigger로 포커스를 복원
ARIA

본문은 <main id="main">, sidebar/toc 는 각각 <aside aria-label>. ≤1023px 보조 네비는 네이티브 <details>/<summary> — <summary> 가 expanded 시맨틱을 스스로 고지하므로 aria-expanded 를 수동 부여하지 않는다(이중 고지 방지). 기본 subnav는 sidebar/toc ReactNode를 데스크톱 컬럼과 모바일 패널에 두 번 렌더하지만 미디어쿼리가 항상 한쪽만 표시해 숨긴 쪽은 AX 트리에서 제외된다. 단 소비자가 직접 지정한 DOM id·htmlFor·aria-labelledby는 복제될 수 있으므로 별도 고유 id 노드 또는 subnav={false}가 필요하다.

Notes

무JS(jinja/maud) 환경에서도 subnav와 workspace disclosure는 네이티브 <details> 토글로 동작한다. Host JS가 있으면 패널 링크·외부 pointerdown·Esc 닫기와 포커스 복원을 얹는다. 검색은 정적 .axe-cmdk markup만으로 모달 동작이 완성되지 않으므로 native dialog 또는 동등한 focus 관리 runtime이 필요하다.

06 · Judgment

권장 · 지양

권장
  • 본문 스크롤 대상 랜드마크로 <main id="main"> 를 유지(스킵 링크 타깃).
  • 문서 안에 AppShell을 specimen으로 중첩할 때만 contentAs="div" + 고유 contentId + showSkipLink={false}를 함께 사용.
  • toc 는 variant="docs" 에서만 의미가 있다 — dashboard/landing 에는 넘기지 말 것.
  • sidebar 자손에 명시 DOM id/htmlFor/aria-labelledby가 있으면 고유 id를 쓰는 별도 subnav 노드를 넘기거나 subnav={false}로 중복 렌더를 피할 것.
  • sidebar 를 넘기면 ≤1023px subnav 가 자동으로 그 네비를 재사용한다.
지양
  • AppShellSubnav 를 직접 import 하지 말 것 — 내부 전용(barrel 미노출), subnav/subnavLabel props 로 합성.
  • subnav <summary> 에 aria-expanded 를 수동으로 붙이지 말 것(네이티브가 이미 고지).
검색창을 열면 컴포넌트 인덱스를 불러옵니다.