Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tailor-platform/app-shell/llms.txt
Use this file to discover all available pages before exploring further.
This guide walks you through building a complete admin dashboard application with navigation, pages, and routing using App Shell.
What You’ll Build
A multi-page dashboard with:
- A dashboard home page
- An orders list page
- Order detail pages with dynamic routing
- Settings page
- Automatic sidebar navigation
Prerequisites
Before starting, make sure you have:
- Node.js 18+ installed
- Basic knowledge of React and TypeScript
- A React project set up (Next.js or Vite)
Installation
Install App Shell
Install the package from npm:npm install @tailor-platform/app-shell
Or with other package managers:pnpm add @tailor-platform/app-shell
yarn add @tailor-platform/app-shell
Create Your Module Structure
Define your application modules and resources. Create a file called modules.tsx:import { defineModule, defineResource } from "@tailor-platform/app-shell";
import { Package, Settings } from "lucide-react";
// Dashboard module with home page
const dashboardModule = defineModule({
path: "dashboard",
meta: {
title: "Dashboard",
icon: <Package />,
},
component: ({ title }) => (
<div style={{ padding: "1.5rem" }}>
<h1>{title}</h1>
<p>Welcome to your dashboard!</p>
</div>
),
resources: [],
});
// Settings module
const settingsModule = defineModule({
path: "settings",
meta: {
title: "Settings",
icon: <Settings />,
},
component: ({ title }) => (
<div style={{ padding: "1.5rem" }}>
<h1>{title}</h1>
<p>Manage your application settings here.</p>
</div>
),
resources: [],
});
export const modules = [dashboardModule, settingsModule];
Set Up AppShell Component
Create your main App component with AppShell and SidebarLayout:import { AppShell, SidebarLayout } from "@tailor-platform/app-shell";
import { modules } from "./modules";
const App = () => {
return (
<AppShell
title="My Dashboard"
modules={modules}
>
<SidebarLayout />
</AppShell>
);
};
export default App;
The SidebarLayout component automatically generates navigation from your modules. No additional configuration needed!
Add the App to Your Entry Point
For Vite, update your main.tsx:import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "@tailor-platform/app-shell/globals.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
For Next.js, update your app/layout.tsx:import "@tailor-platform/app-shell/globals.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Adding Nested Pages
Now let’s add more pages to make your app functional.
Create an Orders List Page
Add a resource to the dashboard module:const ordersResource = defineResource({
path: "orders",
meta: {
title: "Orders",
},
component: () => {
const orders = [
{ id: "order-001", name: "Office Supplies", status: "Shipped" },
{ id: "order-002", name: "Electronics", status: "Processing" },
{ id: "order-003", name: "Furniture", status: "Delivered" },
];
return (
<div style={{ padding: "1.5rem" }}>
<h1>Orders</h1>
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
{orders.map((order) => (
<div key={order.id}>
<strong>{order.name}</strong> - {order.status}
</div>
))}
</div>
</div>
);
},
});
Add Dynamic Routes for Order Details
Create a detail page with a dynamic :id parameter:import { useParams } from "@tailor-platform/app-shell";
const orderDetailResource = defineResource({
path: ":id",
meta: {
title: "Order Detail",
},
component: () => {
const { id } = useParams<{ id: string }>();
return (
<div style={{ padding: "1.5rem" }}>
<h1>Order Detail: {id}</h1>
<div style={{
padding: "1rem",
backgroundColor: "hsl(var(--muted))",
borderRadius: "0.5rem"
}}>
<p><strong>Order ID:</strong> {id}</p>
<p><strong>Status:</strong> Processing</p>
<p><strong>Created:</strong> {new Date().toLocaleDateString()}</p>
</div>
</div>
);
},
});
Dynamic segments use the :paramName syntax, just like React Router. Access them with the useParams hook.
Connect Resources to Module
Update your dashboard module to include the orders resource with its nested detail page:const dashboardModule = defineModule({
path: "dashboard",
meta: {
title: "Dashboard",
icon: <Package />,
},
component: ({ title }) => (
<div style={{ padding: "1.5rem" }}>
<h1>{title}</h1>
<p>Welcome to your dashboard!</p>
</div>
),
// Add orders resource with nested detail page
resources: [
defineResource({
path: "orders",
meta: { title: "Orders" },
component: OrdersPage,
// Nested resource for order details
subResources: [orderDetailResource],
}),
],
});
Adding Navigation Links
Make your pages interactive with the Link component:
import { Link } from "@tailor-platform/app-shell";
const OrdersPage = () => {
const orders = [
{ id: "order-001", name: "Office Supplies", status: "Shipped" },
{ id: "order-002", name: "Electronics", status: "Processing" },
];
return (
<div style={{ padding: "1.5rem" }}>
<h1>Orders</h1>
{orders.map((order) => (
<Link
key={order.id}
to={`/dashboard/orders/${order.id}`}
style={{ display: "block", marginBottom: "0.5rem" }}
>
{order.name} - {order.status}
</Link>
))}
</div>
);
};
Testing Your Application
Start your development server:
You should see:
- A sidebar with “Dashboard” and “Settings” modules
- Clicking “Dashboard” shows your home page
- “Orders” appears under Dashboard in the sidebar
- Clicking an order navigates to the detail page
- The URL updates to reflect the current page
Next Steps
Now that you have a working app, explore these guides:
Common Patterns
Module Without a Landing Page
If you don’t need a landing page for a module, use a guard to redirect:
import { redirectTo } from "@tailor-platform/app-shell";
const reportsModule = defineModule({
path: "reports",
meta: { title: "Reports" },
// Redirect to first resource
guards: [() => redirectTo("/reports/sales")],
resources: [
defineResource({
path: "sales",
component: SalesReport,
}),
],
});
Using Data from Context
Pass data to all pages via contextData:
const App = () => {
const apiClient = useApiClient();
const currentUser = useCurrentUser();
return (
<AppShell
title="My Dashboard"
modules={modules}
contextData={{ apiClient, currentUser }}
>
<SidebarLayout />
</AppShell>
);
};
// Access in any component
const MyPage = () => {
const { contextData } = useAppShell();
return <div>Welcome, {contextData.currentUser.name}</div>;
};