The Layout component provides a responsive column-based layout system for building application pages. It supports 1, 2, or 3 column layouts with automatic responsive behavior and optional header sections.
Import
import { Layout } from "@tailor-platform/app-shell";
Basic Usage
import { Layout } from "@tailor-platform/app-shell";
const MyPage = () => (
<Layout columns={2}>
<Layout.Column>
<div>Main content area</div>
</Layout.Column>
<Layout.Column>
<div>Sidebar content</div>
</Layout.Column>
</Layout>
);
Props
Number of columns in the layout. Must match the exact number of Layout.Column children.The component will throw a development error if the column count doesn’t match the number of children.
Optional header title displayed above the layout
Array of action components (typically buttons) displayed in the header on the right side.actions={[
<Button key="cancel" variant="secondary">Cancel</Button>,
<Button key="save">Save</Button>,
]}
Gap between columns in Tailwind spacing units:
4 = 16px (1rem)
6 = 24px (1.5rem)
8 = 32px (2rem)
Additional CSS classes for the layout container
Layout.Column components. The number of columns must match the columns prop.
Layout.Column Props
Additional CSS classes for the column
Content to render inside the column
Column Configurations
1 Column Layout
Always displays as a single full-width column at all screen sizes.
<Layout columns={1}>
<Layout.Column>
<div>Full width content</div>
</Layout.Column>
</Layout>
Use cases:
- Simple list views
- Full-width forms
- Dashboard pages with stacked sections
2 Column Layout
Main content area with a fixed-width sidebar.
<Layout columns={2}>
<Layout.Column>
<div>Main content (flexible width)</div>
</Layout.Column>
<Layout.Column>
<div>Sidebar (280px minimum)</div>
</Layout.Column>
</Layout>
Responsive behavior:
- Mobile (< 1024px): Columns stack vertically
- Desktop (≥ 1024px): Side-by-side layout
- First column: Flexible width (grows to fill space)
- Second column: Fixed 280px minimum width
Use cases:
- Detail pages with metadata sidebar
- Forms with related information panel
- Document viewers with properties panel
3 Column Layout
Flexible middle column with fixed-width sidebars on both sides.
<Layout columns={3}>
<Layout.Column>
<div>Left sidebar (320px)</div>
</Layout.Column>
<Layout.Column>
<div>Main content (flexible)</div>
</Layout.Column>
<Layout.Column>
<div>Right sidebar (280px)</div>
</Layout.Column>
</Layout>
Responsive behavior:
- Mobile (< 1280px): All columns stack vertically
- Desktop (≥ 1280px): Three columns side-by-side
- First column: Fixed 320px minimum width
- Second column: Flexible width (grows to fill space)
- Third column: Fixed 280px minimum width
Use cases:
- Complex dashboards
- Advanced editing interfaces
- Multi-panel configuration pages
Add a title and action buttons to the layout:
import { Layout } from "@tailor-platform/app-shell";
import { Button } from "@/components/ui/button";
const EditProductPage = () => (
<Layout
columns={2}
title="Edit Product"
actions={[
<Button key="cancel" variant="secondary">
Cancel
</Button>,
<Button key="save">
Save Changes
</Button>,
]}
>
<Layout.Column>
{/* Product form */}
</Layout.Column>
<Layout.Column>
{/* Product metadata */}
</Layout.Column>
</Layout>
);
Usage in Resources
Use Layout in your resource component definitions:
import { defineResource, Layout } from "@tailor-platform/app-shell";
import { ProductForm } from "./ProductForm";
import { ProductMeta } from "./ProductMeta";
const productEditResource = defineResource({
path: ":productId/edit",
meta: { title: "Edit Product" },
component: () => {
const { productId } = useParams();
const product = useProduct(productId);
return (
<Layout
columns={2}
title={product.name}
actions={[
<Button key="cancel" onClick={() => navigate(-1)}>
Cancel
</Button>,
<Button key="save" onClick={handleSave}>
Save
</Button>,
]}
>
<Layout.Column>
<ProductForm product={product} />
</Layout.Column>
<Layout.Column>
<ProductMeta product={product} />
</Layout.Column>
</Layout>
);
},
});
Common Patterns
Typical pattern for edit/create pages:
const CustomerEditPage = ({ customer }) => (
<Layout columns={2}>
<Layout.Column>
{/* Main form */}
<Card>
<CardHeader>
<CardTitle>Customer Information</CardTitle>
</CardHeader>
<CardContent>
<CustomerForm customer={customer} />
</CardContent>
</Card>
</Layout.Column>
<Layout.Column>
{/* Metadata sidebar */}
<DescriptionCard
data={customer}
title="Metadata"
fields={[
{ key: "createdAt", label: "Created", type: "date" },
{ key: "updatedAt", label: "Updated", type: "date" },
{ key: "createdBy", label: "Created By" },
]}
/>
</Layout.Column>
</Layout>
);
const Dashboard = () => (
<Layout columns={3} gap={6}>
<Layout.Column>
<RecentOrders />
</Layout.Column>
<Layout.Column>
<SalesChart />
<RevenueMetrics />
</Layout.Column>
<Layout.Column>
<ActivityFeed />
</Layout.Column>
</Layout>
);
Stacked Sections
const OrderDetailsPage = ({ order }) => (
<Layout columns={1}>
<Layout.Column>
<DescriptionCard
data={order}
title="Order Information"
fields={orderFields}
/>
<Card>
<CardHeader>
<CardTitle>Line Items</CardTitle>
</CardHeader>
<CardContent>
<OrderItemsTable items={order.items} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Payment History</CardTitle>
</CardHeader>
<CardContent>
<PaymentHistory payments={order.payments} />
</CardContent>
</Card>
</Layout.Column>
</Layout>
);
Custom Column Spacing
// Larger gap for visual separation
<Layout columns={2} gap={8}>
<Layout.Column>
<MainContent />
</Layout.Column>
<Layout.Column>
<Sidebar />
</Layout.Column>
</Layout>
TypeScript
The component exports TypeScript types:
import { type LayoutProps, type ColumnProps } from "@tailor-platform/app-shell";
const MyLayout = (props: Partial<LayoutProps>) => (
<Layout columns={2} {...props}>
<Layout.Column>
<div>Content</div>
</Layout.Column>
<Layout.Column>
<div>Sidebar</div>
</Layout.Column>
</Layout>
);
Validation
In development mode, the Layout component validates that the number of Layout.Column children matches the columns prop:
// ❌ Error: Expected 2 Layout.Column children, but found 1
<Layout columns={2}>
<Layout.Column>Only one column</Layout.Column>
</Layout>
// ✅ Correct
<Layout columns={2}>
<Layout.Column>First column</Layout.Column>
<Layout.Column>Second column</Layout.Column>
</Layout>
Styling
Columns automatically include vertical spacing between child elements:
<Layout columns={1}>
<Layout.Column>
{/* Each child has 1rem (16px) gap */}
<Card>Card 1</Card>
<Card>Card 2</Card>
<Card>Card 3</Card>
</Layout.Column>
</Layout>
Add custom styles to columns:
<Layout columns={2}>
<Layout.Column className="bg-background">
<MainContent />
</Layout.Column>
<Layout.Column className="bg-muted">
<Sidebar />
</Layout.Column>
</Layout>
Best Practices
- Match Column Count: Always ensure the number of
Layout.Column children matches the columns prop
- Use Semantic Structure: Use 2-column layouts for main/sidebar patterns, 3-column for complex dashboards
- Consider Mobile: Remember that columns stack vertically on mobile - order content accordingly
- Consistent Spacing: Use the
gap prop instead of adding margins to columns
- Header Actions: Place primary actions last in the actions array (rightmost position)
Accessibility
The Layout component:
- Uses semantic HTML structure
- Maintains logical reading order when columns stack
- Respects user font size preferences
- Works with keyboard navigation