App shell

App Frame

The app window: a sidebar docked on desktop (collapsible, animated) that becomes a modal drawer on phones, a top bar over a scrolling page, and an overlay layer for peeks.

Edited 2m ago

Q4 roadmap

Goals

Ship the redesigned onboarding, the new billing page and the Q4 roadmap review.

Milestones

Design review on Oct 20, beta on Nov 3, general availability on Dec 1.

Usage

  • Use AppFrame as the outermost layout of an app screen that has navigation: sidebar, top bar and pages.
  • Control it with sidebarOpen / onSidebarOpenChange when other UI needs to know whether the sidebar is shown; otherwise leave it uncontrolled.
  • Don’t nest AppFrames or put one inside a scrolling container: it fills its parent (give the parent a height, e.g. h-screen).
  • Don’t use it for a single document or marketing page; use PageLayout on its own.

Installation

Install the package (React 19):

terminal
npm install @krackedui/motion

Import the styles once, in your app entry or root layout:

/src/main.tsx
import '@krackedui/motion/styles.css';

Use it anywhere inside your app's <Theme> (see Installation):

/src/AppFrameExample.tsx
import { useState } from 'react';
import {
  AppFrame, Topbar, BreadcrumbTrail, PageLayout, PageCover, PageHeader, SidePeek,
  PeekBody, PeekProperties, PeekProperty, PeekComments,
  Sidebar, WorkspaceMenu, HelpMenu, AppsMenu, Icon,
} from '@krackedui/motion';

function AppSidebar() {
  return (
    <Sidebar.Root
      header={<><Sidebar.Search /><Sidebar.Tabs /></>}
      footer={<Sidebar.InviteCard />}
      bottomBar={
        <Sidebar.BottomBar>
          <WorkspaceMenu email="ada@example.com" workspaces={[{ id: 'w1', name: 'Design Team' }]} currentId="w1">
            <Sidebar.WorkspaceSwitcher name="Design Team" />
          </WorkspaceMenu>
          <HelpMenu releases={[{ id: 'r1', title: 'Release 3.8', isNew: true }, { id: 'r2', title: 'Release 3.7' }]}>
            <Sidebar.BarButton icon="questionMarkCircle" label="Help, contact, more…" dot />
          </HelpMenu>
          <AppsMenu trash={[{ id: 't1', title: 'Old meeting notes', path: 'Team wiki' }]}>
            <Sidebar.BarButton icon="circleGrid2X2" label="Library, Trash, more…" />
          </AppsMenu>
        </Sidebar.BottomBar>
      }
    >
      <Sidebar.Group>
        <Sidebar.Item icon="chatBubblePlus" actions={<Sidebar.Action icon="ellipsisSmall" label="Open shortcut menu" />}>New chat</Sidebar.Item>
        <Sidebar.Item icon="bookshelf">Library</Sidebar.Item>
      </Sidebar.Group>
      <Sidebar.Sections>
        <Sidebar.Section title="Shared" actions={<Sidebar.Action icon="plusSmall" label="Add a page" />}>
          <Sidebar.Item tone="secondary" icon={<Icon name="plus" size={16} />}>Start collaborating</Sidebar.Item>
        </Sidebar.Section>
        <Sidebar.Section title="Private" actions={<Sidebar.Action icon="plusSmall" label="Add a page" />}>
          <Sidebar.Tree label="Private pages">
            <Sidebar.TreeItem label="Team wiki" active defaultExpanded>
              <Sidebar.TreeItem label="Tasks" icon={<Icon name="viewTable" size={20} />} />
            </Sidebar.TreeItem>
            <Sidebar.TreeItem label="Q4 roadmap" />
            <Sidebar.TreeItem label="Meeting notes" />
            <Sidebar.TreeItem label="Product specs" />
          </Sidebar.Tree>
        </Sidebar.Section>
      </Sidebar.Sections>
      <Sidebar.ScrollFooter>
        <Sidebar.Item tone="muted" icon="sliders">Customize sidebar</Sidebar.Item>
      </Sidebar.ScrollFooter>
    </Sidebar.Root>
  );
}

function TaskPeek() {
  return (
    <PeekBody
      header={<PageHeader size="peek" title="Sidebar rebuild" extraControls={null} />}
      properties={
        <PeekProperties>
          <PeekProperty icon="calendarDate15" label="Due">October 9, 2026</PeekProperty>
          <PeekProperty icon="arrowInCircleDown" label="Priority">High</PeekProperty>
          <PeekProperty icon="checkmarkCircle" label="Status" />
        </PeekProperties>
      }
      comments={<PeekComments />}
    >
      <div className="pl-2 text-n-text-tertiary">Press ‘enter’ to continue with an empty page.</div>
    </PeekBody>
  );
}

export function App() {
  const [peek, setPeek] = useState(false);
  return (
    <div className="motion-root h-dvh w-full">
      <AppFrame
        sidebar={<AppSidebar />}
        topbar={
          <Topbar
            breadcrumbs={<BreadcrumbTrail items={[{ title: 'Team wiki' }, { title: 'Q4 roadmap', icon: '🗺️' }]} />}
            edited="Edited 2m ago"
            onShare={() => setPeek(true)}
          />
        }
        peek={<SidePeek open={peek} onClose={() => setPeek(false)}><TaskPeek /></SidePeek>}
      >
        <PageLayout
          toc={[
            { id: 'goals', label: 'Goals', level: 1 },
            { id: 'milestones', label: 'Milestones', level: 2 },
            { id: 'risks', label: 'Risks', level: 2 },
          ]}
          cover={
            <PageCover>
              <div className="size-full" style={{ background: 'linear-gradient(120deg, var(--n-blue-bg-accent-secondary), var(--n-bg-tertiary))' }} />
            </PageCover>
          }
          header={<PageHeader title="Q4 roadmap" />}
        >
          <div className="flex flex-col gap-2 px-2 text-[16px] leading-6">
            <div id="goals" className="pt-6 text-[30px] font-semibold leading-[39px]">Goals</div>
            <p>Ship the redesigned onboarding, the new billing page and the Q4 roadmap review.</p>
            <div id="milestones" className="pt-4 text-[24px] font-semibold leading-[31px]">Milestones</div>
            <p>Design review on Oct 20, beta on Nov 3, general availability on Dec 1.</p>
          </div>
        </PageLayout>
      </AppFrame>
    </div>
  );
}

Examples

Collapsed sidebar

Starts collapsed. Hover the » button in the topbar to see the floating sidebar, click it to dock. On a phone the menu button opens the drawer.

Edited 2m ago

Q4 roadmap

Goals

Ship the redesigned onboarding, the new billing page and the Q4 roadmap review.

Milestones

Design review on Oct 20, beta on Nov 3, general availability on Dec 1.

Controlled

sidebarOpen is the docked state on desktop and the drawer state on phones (so it starts closed here).

Home

Everything you follow, in one place.

With side peek

Edited 2m ago

Q4 roadmap

Goals

Ship the redesigned onboarding, the new billing page and the Q4 roadmap review.

Milestones

Design review on Oct 20, beta on Nov 3, general availability on Dec 1.

Sidebar rebuild

Due
October 9, 2026
Priority
High
Status
Empty
Comments
A
Press ‘enter’ to continue with an empty page.

Preview scaling (docs)

Previews on this page render the app at 1100x640 and scale it to fit:

Edited 2m ago

Q4 roadmap

Goals

Ship the redesigned onboarding, the new billing page and the Q4 roadmap review.

Milestones

Design review on Oct 20, beta on Nov 3, general availability on Dec 1.

Composition

anatomy
<AppFrame sidebar topbar peek sidebarOpen onSidebarOpenChange>
  sidebar   → <Sidebar.Root/>   rendered docked (≥ 768px), floating (collapsed hover-peek) or in the phone drawer
  main      → topbar (<Topbar/>: phone menu button + » when collapsed) · children (<PageLayout/>) · peek (<SidePeek/>)
</AppFrame>

useAppFrame() → { mobile, sidebarOpen, setSidebarOpen, collapsed, toggleSidebar, peeking, setPeeking, placement }

API Reference

AppFrame

Based on <div> (children replaced by the slot). Slots accept a node or (state: AppFrameState) => ReactNode.

PropTypeDefault
sidebar
ReactNode | ((state: AppFrameState) => ReactNode)
topbar
ReactNode | ((state: AppFrameState) => ReactNode)
children
ReactNode | ((state: AppFrameState) => ReactNode)
peek
ReactNode | ((state: AppFrameState) => ReactNode)
sidebarOpen
boolean
defaultSidebarOpen
boolean
true
onSidebarOpenChange
(open: boolean) => void
drawerLabel
string
"Sidebar"
className
string

AppFrameState

Passed to slot functions and returned by useAppFrame() (useOptionalAppFrame() returns null outside a frame).

PropTypeDefault
mobile
boolean
sidebarOpen
boolean
setSidebarOpen
(open: boolean) => void
collapsed
boolean
setCollapsed
(collapsed: boolean) => void
toggleSidebar
() => void
peeking
boolean
setPeeking
(peeking: boolean) => void
placement
"docked""floating""drawer"
floating
boolean

Accessibility

  • ⌘\ (Ctrl+\) collapses / expands the sidebar on desktop and opens / closes the drawer on phones.
  • Phones (< 768px): the sidebar is a modal dialog named "Sidebar" (drawerLabel). The topbar menu button opens it; focus moves into it and is trapped; Escape, the backdrop or choosing an item closes it and focus returns to the menu button.
  • Desktop: the collapsed sidebar is inert (out of the tab order). Hovering the topbar » button or the left edge floats it for pointer users; the » button (Enter) docks it again.
  • Landmarks come from the parts: nav (sidebar), header (topbar), main (page frame), non-modal dialog (side peek).
  • Width, drawer and backdrop animations are disabled under prefers-reduced-motion.
© 2026 Motion. Internal documentation.