AppShell
AppShell is the root component that wires together routing, navigation, authentication, and theming for your AppShell application. It should wrap your entire application layout.
Import
import { AppShell } from "@tailor-platform/app-shell";Basic Usage
import { AppShell, SidebarLayout, defineModule } from "@tailor-platform/app-shell";
const modules = [
defineModule({
path: "dashboard",
component: DashboardPage,
meta: { title: "Dashboard" },
}),
];
function App() {
return (
<AppShell title="My ERP App" basePath="/app" modules={modules}>
<SidebarLayout />
</AppShell>
);
}Props
title
- Type:
string(optional) - Description: Application title displayed in the sidebar header
<AppShell title="My ERP App" modules={modules}>
{/* ... */}
</AppShell>icon
- Type:
React.ReactNode(optional) - Description: Application icon displayed in the sidebar header
import { Building } from "lucide-react";
<AppShell title="My App" icon={<Building />} modules={modules}>
{/* ... */}
</AppShell>;basePath
- Type:
string(optional) - Default:
"" - Description: Base path prefix for all routes
// Routes will be /app/dashboard, /app/products, etc.
<AppShell basePath="/app" modules={modules}>
{/* ... */}
</AppShell>
// Routes will be /dashboard, /products, etc.
<AppShell basePath="" modules={modules}>
{/* ... */}
</AppShell>modules
- Type:
Module[](required when not using file-based routing) - Description: Array of module definitions that define your application structure
import { defineModule, defineResource } from "@tailor-platform/app-shell";
const modules = [
defineModule({
path: "products",
component: ProductsListPage,
meta: { title: "Products" },
resources: [
defineResource({
path: ":id",
component: ProductDetailPage,
meta: { title: "Product Details" },
}),
],
}),
];
<AppShell modules={modules}>{/* ... */}</AppShell>;Learn more about Modules and Resources →
rootComponent
- Type:
() => React.ReactNode(optional) - Description: Component to render at the root path (e.g.,
/app/)
<AppShell basePath="/app" modules={modules} rootComponent={() => <HomePage />}>
{/* ... */}
</AppShell>Tip: For redirects from the root, use a guard with
redirectTo()instead
import { redirectTo } from "@tailor-platform/app-shell";
<AppShell basePath="/app" modules={modules} rootComponent={() => redirectTo("/app/dashboard")}>
{/* ... */}
</AppShell>;settingsResources
- Type:
Resource[](optional) - Description: Resources to include in the settings menu dropdown
import { defineResource } from "@tailor-platform/app-shell";
const settingsResources = [
defineResource({
path: "profile",
component: ProfileSettingsPage,
meta: { title: "Profile" },
}),
defineResource({
path: "billing",
component: BillingSettingsPage,
meta: { title: "Billing" },
}),
];
<AppShell modules={modules} settingsResources={settingsResources}>
{/* ... */}
</AppShell>;Settings appear in a dropdown menu in the sidebar header, accessible via the settings icon.
locale
- Type:
string(optional) - Default: Auto-detected from browser, falls back to
"en" - Description: Locale code for built-in UI strings
<AppShell locale="ja" modules={modules}>
{/* ... */}
</AppShell>Supported locales: en, ja
Learn more about Internationalization →
errorBoundary
- Type:
ErrorBoundaryComponent(optional) - Description: Global error boundary component applied to all routes
import { useRouteError } from "@tailor-platform/app-shell";
const GlobalErrorBoundary = () => {
const error = useRouteError() as Error;
return (
<div className="astw:p-8">
<h1 className="astw:text-xl astw:font-bold astw:mb-4">Something went wrong</h1>
<p className="astw:text-red-600">{error.message}</p>
</div>
);
};
<AppShell modules={modules} errorBoundary={GlobalErrorBoundary}>
{/* ... */}
</AppShell>;Note: Module and resource-level error boundaries take precedence over the global error boundary.
contextData
- Type:
ContextData(optional) - Description: Custom context data accessible from guards and components via
useAppShell()
First, define your context data type using module augmentation:
// types.d.ts
declare module "@tailor-platform/app-shell" {
interface AppShellRegister {
contextData: {
apiClient: ApiClient;
currentUser: User | null;
tenantId: string;
};
}
}Then pass the data to AppShell:
// App.tsx
<AppShell
modules={modules}
contextData={{
apiClient,
currentUser,
tenantId: "tenant-123",
}}
>
{/* ... */}
</AppShell>Access the context data in your components:
import { useAppShell } from "@tailor-platform/app-shell";
function MyComponent() {
const { context } = useAppShell();
// Fully typed!
const user = context.currentUser;
const client = context.apiClient;
return <div>Welcome, {user?.name}</div>;
}Or in guards:
import { pass, hidden } from "@tailor-platform/app-shell";
const requireAuth: Guard = ({ context }) => {
if (!context.currentUser) {
return redirectTo("/login");
}
return pass();
};children
- Type:
React.ReactNode(required) - Description: Layout component that renders your application content
Typically, you'll use SidebarLayout:
import { SidebarLayout } from "@tailor-platform/app-shell";
<AppShell modules={modules}>
<SidebarLayout />
</AppShell>;Or create a custom layout:
<AppShell modules={modules}>
<CustomLayout />
</AppShell>File-Based Routing Mode
When using the Vite plugin for file-based routing, use the special WithPages helper:
// vite-env.d.ts (auto-generated by vite plugin)
/// <reference types="@tailor-platform/app-shell/vite-plugin" />
// App.tsx
import { AppShell } from "@tailor-platform/app-shell";
// No need to pass modules prop
<AppShell.WithPages title="My App" basePath="/app">
<SidebarLayout />
</AppShell.WithPages>;Learn more about File-Based Routing →
Complete Example
Here's a complete example with all common features:
import {
AppShell,
SidebarLayout,
defineModule,
defineResource,
useRouteError,
} from "@tailor-platform/app-shell";
import { Building } from "lucide-react";
// Define context data type
declare module "@tailor-platform/app-shell" {
interface AppShellRegister {
contextData: {
currentUser: User | null;
};
}
}
// Error boundary
const ErrorBoundary = () => {
const error = useRouteError() as Error;
return (
<div className="astw:p-8">
<h1 className="astw:text-xl astw:font-bold">Error</h1>
<p>{error.message}</p>
</div>
);
};
// Modules
const modules = [
defineModule({
path: "dashboard",
component: DashboardPage,
meta: { title: "Dashboard" },
}),
defineModule({
path: "products",
component: ProductsPage,
meta: { title: "Products" },
resources: [
defineResource({
path: ":id",
component: ProductDetailPage,
}),
],
}),
];
// Settings
const settingsResources = [
defineResource({
path: "profile",
component: ProfilePage,
meta: { title: "Profile" },
}),
];
// App
function App() {
const currentUser = useCurrentUser();
return (
<AppShell
title="My ERP App"
icon={<Building />}
basePath="/app"
modules={modules}
settingsResources={settingsResources}
locale="en"
errorBoundary={ErrorBoundary}
contextData={{ currentUser }}
>
<SidebarLayout />
</AppShell>
);
}
export default App;Related Components
- SidebarLayout - Default layout with sidebar navigation
- CommandPalette - Keyboard-driven navigation
Related Concepts
- Modules and Resources - Application structure
- Authentication - User authentication setup
- Routing and Navigation - Navigation between pages
API Reference
- defineModule - Define a top-level module
- defineResource - Define a nested resource
- useAppShell - Access AppShell context