组件 · 布局
AppShell
The outermost layout of an app, with slots for the rail, the sidebar, the main area, a side panel and an overlay, that switches to a single column with the tab bar below 768px.
import { AppShell } from "@botiverse/raft-ui";AppShell places the regions of an app screen: on desktop the rail, the sidebar, the main area and a side panel from left to right; below 768px the main area alone, with the tab bar under it. Its slots own placement only. What each region looks like belongs to the component inside it. Which regions a screen has, and how they change with width, is in Page structure and Responsive layout.
When to use
| Situation | Use |
|---|---|
| The outer frame of an app screen | AppShell |
| One bounded region with a header, a body and a footer | Panel, inside a slot |
| A split people can drag | Resizable, with the slots inside its panels |
| The destinations on desktop | AppRail, in AppShellRailSlot |
| The destinations on a phone | MobileNav, after AppShellMainSlot |
Anatomy
AppShellRoot: the frame, onlayer-canvas. A row from 768px, a column below it. Required.AppShellRailSlot: anasidefor the AppRail. Hidden below 768px.AppShellSidebarSlot: anasidefor the Sidebar. Hidden below 768px.AppShellMainSlot: themainelement, for the main area. The only slot shown on a phone. Required.AppShellPanelSlot: anasidefor a side panel next to the main area. Hidden below 768px.AppShellOverlaySlot: covers the main area without changing the columns, for a side panel that overlays it. Put it inside anAppShellMainSlotwithoverlay.AppShellSidebarTrigger: the button that collapses and expands the sidebar. It is aPanelActionfor the main area's panel header; render it as anAppRailItemto put it in the rail, where the products put it (Navigation).
The slots are placement markers, not required direct children: a slot can sit inside a ResizablePanel.
Variants
| Variant | Use for |
|---|---|
| Default | A sidebar that is always shown on desktop |
sidebarCollapsible on AppShellRoot | A sidebar people can hide to give the main area the full width |
In the rail, the trigger is the first item of the footer, and its tooltip shows the shortcut:
Behavior
- Below 768px the rail, sidebar and panel slots are hidden. The main slot shows the page that is open, whether that is a list, a conversation or a thread; the app's routes decide which (Responsive layout).
- Put
MobileNavin the root after the main slot, shown only below 768px, so it sits at the bottom of the column.
- The root sets the header height for the regions inside it: 56px, and 48px in a window shorter than 600px (Spacing).
- The root clips what overflows it instead of scrolling, so moving focus never shifts the whole app sideways.
- On a phone the root pads its top for the status bar's safe area.
- Collapsing the sidebar slides the main area over it with
ease-slidewhile the sidebar fades out; expanding reverses it. With reduced motion, both happen at once (Motion). - The main area rises over the sidebar (z-index 2, one above the expanded sidebar, with
data-slidingon the main slot) only while it slides. At rest it has no z-index, so it never covers the app's own fixed layers, such as a bar at z-40. The expanded sidebar stacks just above the main area, so a resize handle that pokes past its edge stays grabbable. - The open state is the app's to keep: pass
sidebarOpenandonSidebarOpenChange, and remember the value per device, so the sidebar comes back the way it was left. - The shell measures the sidebar's width, so a collapsible sidebar can also be resizable.
Accessibility
- The slots render landmarks:
mainfor the main area andasidefor the others. Name the navigation inside them:aria-labelon the rail'snavand onMobileNavRoot. AppShellSidebarTriggeris a disclosure button:aria-expandedsays whether the sidebar is shown, andaria-controlsnames the sidebar slot (the slot's ownid, or one the shell gives it). It shows and hides a region, so it is not a toggle button witharia-pressed. The shell labels it "Collapse Sidebar" or "Expand Sidebar", with the same text as a nativetitle; when you wrap it in a Tooltip, passtitle={undefined}and repeat the label in the tooltip. The built-in label is English: to translate it, pass your ownaria-label(it overrides the shell's) and use the same words in the tooltip. Pass only the icon, hidden from assistive technology. It renders nothing when the root isn'tsidebarCollapsible, and it is hidden below 768px.- A collapsed sidebar is
inert: its controls leave the tab order and the accessibility tree, and it ignores the pointer. A shortcut that collapses the sidebar while focus is inside it moves focus to the trigger first, so focus is never lost.
Rules
- Build every app screen on AppShell. Don't rebuild the frame from
divs, because the slots carry the breakpoints, the landmarks and the header height. - Don't put a background, a border, a width or a shadow on a slot. Put them on the component inside: a Panel with
edge="attached"draws the one line between it and the region before it (Page structure). - Give a screen one
AppShellMainSlot. - Set
overlayonAppShellMainSlotonly when anAppShellOverlaySlotis inside it.
API
AppShellMainSlot
| Prop | 类型 | 默认值 |
|---|---|---|
| boolean | false | |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
AppShellOverlaySlot, AppShellPanelSlot, AppShellRailSlot
| Prop | 类型 | 默认值 |
|---|---|---|
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
AppShellRoot
| Prop | 类型 | 默认值 |
|---|---|---|
| boolean | true | |
| (open: boolean) => void | ||
| boolean | false | |
| boolean | ||
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
AppShellSidebarSlot, AppShellSidebarTrigger
| Prop | 类型 | 默认值 |
|---|---|---|
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
相关组件
改动
- 0.4.0 · ChangedAppShell no longer draws column dividers.
AppShellRailSlotandAppShellSidebarSlotdropped their border classes in both themes. The rail|sidebar divider now lives onAppRailRoot(brutal); seam borders the shell used to own (e.g. sidebar|main with an inset main panel) must be added by the caller. ([e219fd8](https://github.com/botiverse/rui/commit/e219fd8)) - 0.3.0 · AddedAdded
AppShellPanelSlotandAppShellOverlaySlot, overlay-aware main slots, attached/inset Panel edges, and the sharedPanelActivityheader part for composable multi-panel layouts. - 0.2.0 · FixedFixed conditional-hook rendering in
AppShellSidebarTriggerandLegacyTaskPanelRoot, and corrected sortable Composer drag-overlay mapping.