AppShell 应用骨架
整页的骨架:顶栏、侧栏、主区、辅助栏、底栏。整体占满一屏,各区各自滚动。侧栏和辅助栏是和边缘留缝的圆角卡片:宽屏停靠在两侧、占据布局空间;窄屏时同一份内容改由悬浮卡片从对应一侧滑入。你现在看的这个文档站,外壳就是它。
三种形态
侧栏有三种形态:停靠(完整宽度)、停靠(收成图标窄条)、抽屉。默认按宽度自动在停靠和抽屉之间切换;collapsed 可以强制。AppShell.Trigger 在抽屉形态下开关抽屉,停靠时在完整宽度和图标窄条之间切换。
- contained。演示里的骨架嵌在一个 480px 高的框里:高度取 100% 而不是 100dvh,宽窄按自身宽度判断(
@container),抽屉和遮罩也只盖住这个框。把浏览器拉窄到框不足 768px,“自动”会变成抽屉形态。 - 同一份 children。侧栏的内容只写一次。抽屉形态下它被渲染进 悬浮卡片(模态:圈焦点、Esc 关闭),点了里面的导航项后自动关上(
closeOnNavigate)。 - 图标窄条。停靠的侧栏收窄后,里面的
Nav自动折叠成只剩图标,文字改由title/aria-label提供。所以导航项要配图标。 - 各部件要作为 AppShell 的直接子节点,不要在中间包别的元素。
最小用法
真实应用里的写法(不加 contained)没法在文档页里嵌一份,看代码,或者直接看示例应用。drawerOnly 的开关按钮只在抽屉形态下出现。
API
AppShell 属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
contained | boolean | false | 嵌在固定高度的容器里:高度取 100%,按自身宽度判断形态,抽屉只盖住这个容器。 |
collapsed | 'auto' | boolean | 'auto' | 侧栏 / 辅助栏是否收成抽屉。auto 按断点判断;true / false 强制。 |
collapseBelow | 'sm' | 'md' | 'lg' | 'xl' | 'md' | auto 时,宽度小于该断点侧栏变抽屉。 |
asideCollapseBelow | 'sm' | 'md' | 'lg' | 'xl' | 'lg' | auto 时,宽度小于该断点辅助栏变抽屉。 |
sidebarOpen / defaultSidebarOpen / onSidebarOpenChange | boolean / boolean / (open) => void | — | 侧栏抽屉的开关(受控 / 非受控)。 |
asideOpen / defaultAsideOpen / onAsideOpenChange | boolean / boolean / (open) => void | — | 辅助栏的显示状态(受控 / 非受控)。停靠时控制是否显示(默认显示),抽屉形态下控制抽屉开合(默认关闭)。 |
rail / defaultRail / onRailChange | boolean / boolean / (rail) => void | — | 停靠的侧栏是否收成图标窄条(受控 / 非受控)。 |
gap | Space | 3(窄屏 2) | 卡片之间、卡片到边缘的缝。 |
skipLinkLabel | string | '跳到主要内容' | 跳转链接的文字。 |
mainId | string | — | <main> 的 id;不传自动生成。 |
…rest | HTMLAttributes | — | ref、className、style 和其余原生属性透传到根节点。 |
AppShell.Sidebar / AppShell.Aside 属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | '侧栏' / '辅助栏' | 无障碍名称。 |
width | number | string | 260 / 320 | 停靠时的宽度(数字按 px)。 |
drawerWidth | number | string | 300 / 360 | 抽屉形态下的宽度,不会超过可用宽度。 |
padding | Space | 5 | 卡片内边距(20px:外圆角 32 − 导航项圆角 12)。 |
closeOnNavigate | boolean | true / false | 抽屉形态下,点了里面的导航项或链接后自动关上。 |
AppShell.Header / AppShell.Footer / AppShell.Trigger 属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Header / Footer · variant | 'plain' | 'card' | 'plain' | plain 直接坐在画布上;card 和侧栏一样是一张卡片。 |
Trigger · target | 'sidebar' | 'aside' | 'sidebar' | 控制哪一栏。辅助栏的开关在停靠和抽屉两种形态下都可用。 |
Trigger · label | string | '切换导航' / '切换辅助栏' | 按钮的无障碍名称。 |
Trigger · drawerOnly | boolean | false | 只在抽屉形态下渲染(不需要图标窄条时用)。 |
Trigger · children | ReactNode | — | 自定义图标,默认三条横线。 |
useAppShell() 的返回值
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
sidebarCollapsed / asideCollapsed | boolean | — | 当前是不是抽屉形态。 |
sidebarOpen / setSidebarOpen / openSidebar / closeSidebar | boolean / 函数 | — | 侧栏抽屉的状态与控制。 |
toggleSidebar | () => void | — | 抽屉形态:开关抽屉;停靠形态:展开 / 收成图标窄条。 |
rail / setRail / toggleRail | boolean / 函数 | — | 图标窄条的状态与控制。 |
asideOpen / setAsideOpen / toggleAside | boolean / 函数 | — | 辅助栏的显示状态与控制,停靠和抽屉形态通用。 |
contained | boolean | — | 是否嵌在容器里。 |
mainId | string | — | <main> 的 id。 |
无障碍
- 地标齐全:Header 是
<header>,Sidebar / Aside 是带名字的<aside>,Main 是<main>,Footer 是<footer>。 - 骨架的第一个可聚焦元素是“跳到主要内容”链接:平时看不见,Tab 聚焦时滑下来,回车后焦点直接进入主区。
- 抽屉形态下侧栏是模态对话框:打开时焦点移进去并圈住,Esc 或点遮罩关闭,关闭后焦点回到开关按钮。
- 开关按钮带
aria-expanded;抽屉形态下还有aria-haspopup="dialog"。自定义图标时名字仍由label提供。 - 主区可以被聚焦(
tabIndex=-1)并独立滚动;给 Sidebar、Aside 起能区分的label。