Raft 设计系统

组件 · 布局

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.

稳定也叫 Shell, App frame, App layout
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.

app-shell/compositions/responsive

When to use

SituationUse
The outer frame of an app screenAppShell
One bounded region with a header, a body and a footerPanel, inside a slot
A split people can dragResizable, with the slots inside its panels
The destinations on desktopAppRail, in AppShellRailSlot
The destinations on a phoneMobileNav, after AppShellMainSlot

Anatomy

  • AppShellRoot: the frame, on layer-canvas. A row from 768px, a column below it. Required.
  • AppShellRailSlot: an aside for the AppRail. Hidden below 768px.
  • AppShellSidebarSlot: an aside for the Sidebar. Hidden below 768px.
  • AppShellMainSlot: the main element, for the main area. The only slot shown on a phone. Required.
  • AppShellPanelSlot: an aside for 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 an AppShellMainSlot with overlay.
  • AppShellSidebarTrigger: the button that collapses and expands the sidebar. It is a PanelAction for the main area's panel header; render it as an AppRailItem to 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

VariantUse for
DefaultA sidebar that is always shown on desktop
sidebarCollapsible on AppShellRootA sidebar people can hide to give the main area the full width
app-shell/compositions/collapsible-sidebar

In the rail, the trigger is the first item of the footer, and its tooltip shows the shortcut:

app-shell/compositions/rail-trigger

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 MobileNav in the root after the main slot, shown only below 768px, so it sits at the bottom of the column.
app-shell/compositions/mobile-root
  • 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-slide while 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-sliding on 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 sidebarOpen and onSidebarOpenChange, 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: main for the main area and aside for the others. Name the navigation inside them: aria-label on the rail's nav and on MobileNavRoot.
  • AppShellSidebarTrigger is a disclosure button: aria-expanded says whether the sidebar is shown, and aria-controls names the sidebar slot (the slot's own id, or one the shell gives it). It shows and hides a region, so it is not a toggle button with aria-pressed. The shell labels it "Collapse Sidebar" or "Expand Sidebar", with the same text as a native title; when you wrap it in a Tooltip, pass title={undefined} and repeat the label in the tooltip. The built-in label is English: to translate it, pass your own aria-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't sidebarCollapsible, 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 overlay on AppShellMainSlot only when an AppShellOverlaySlot is inside it.

API

AppShellMainSlot

Prop类型默认值
overlaybooleanfalse
renderAllows 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类型默认值
renderAllows 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类型默认值
defaultSidebarOpenbooleantrue
onSidebarOpenChange(open: boolean) => void
sidebarCollapsiblebooleanfalse
sidebarOpenboolean
renderAllows 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类型默认值
renderAllows 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. AppShellRailSlot and AppShellSidebarSlot dropped their border classes in both themes. The rail|sidebar divider now lives on AppRailRoot (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 AppShellPanelSlot and AppShellOverlaySlot, overlay-aware main slots, attached/inset Panel edges, and the shared PanelActivity header part for composable multi-panel layouts.
  • 0.2.0 · FixedFixed conditional-hook rendering in AppShellSidebarTrigger and LegacyTaskPanelRoot, and corrected sortable Composer drag-overlay mapping.