layouts

AppShell

Full-height flex column with header, sidebar, main, and footer slots.


AppShell is the chrome you wrap your routed views in. It doesn't take a router opinion, so you can use any navigator under it.

Try it live

import { useState } from "react";
import { AppShell, Sidebar } from "@plyxui/layouts";
import { Box, Text, Flex } from "@plyxui/primitives";

const ITEMS = [
{ key: "home",    label: "Home",    icon: "home" },
{ key: "members", label: "Members", icon: "users" },
{ key: "reports", label: "Reports", icon: "file-text" },
{ key: "settings",label: "Settings",icon: "settings" },
];

export default function App() {
const [active, setActive] = useState("home");
const items = ITEMS.map((it) => ({ ...it, active: it.key === active, onSelect: () => setActive(it.key) }));
return (
  <AppShell
    sidebar={<Sidebar items={items} header={<Text size="sm" weight="semibold" style={{ padding: 8 }}>plyxui demo</Text>} />}
    header={
      <Flex align="center" justify="between" style={{ height: 56, padding: "0 20px" }}>
        <Text size="md" weight="semibold">{ITEMS.find(i => i.key === active)?.label}</Text>
        <Text size="sm" style={{ opacity: 0.6 }}>plyxui.com</Text>
      </Flex>
    }
  >
    <Box padding="lg">
      <Text size="lg" weight="semibold">{active} content</Text>
      <Text size="sm" style={{ marginTop: 8, opacity: 0.7 }}>Click any sidebar item to switch.</Text>
    </Box>
  </AppShell>
);
}

API

import { AppShell, Sidebar } from "@plyxui/layouts";

<AppShell
  header={<TitleBar />}
  sidebar={<Sidebar items={items} header={<Brand />} />}
>
  <Outlet />
</AppShell>

Props

PropTypeDefault
headerReactNode-
sidebarReactNode-
footerReactNode-
sidebarWidthnumber240
dividerbooleantrue

The sidebar slot is a single render slot, so consumers can put the plyxui Sidebar there or any custom navigation surface.

Native variant

On native the sidebar slot is dropped; tabs are the right pattern. Use OmniNavigator mode="tabs" from @plyxui/navigator/react-navigation and feed it the same OmniRoute[] you'd render in the web Sidebar.

Composing with the navigator

import { defineRoutes } from "@plyxui/navigator";
import { OmniRouter } from "@plyxui/navigator/react-router";

const routes = defineRoutes([
  { name: "home",     path: "/",         icon: "home",     title: "Home",     element: () => <Home /> },
  { name: "subjects", path: "/subjects", icon: "user",     title: "Subjects", element: () => <Subjects /> },
  { name: "settings", path: "/settings", icon: "settings", title: "Settings", element: () => <Settings /> },
]);

function Shell() {
  return (
    <BrowserRouter>
      <AppShell sidebar={<Sidebar items={sidebarItems(routes)} />}>
        <OmniRouter routes={routes} />
      </AppShell>
    </BrowserRouter>
  );
}

The routes table is the single source of truth for both the sidebar items and the routes themselves.