Layout Framework Skill
Overview
This skill ensures consistent, responsive layouts across PierceDesk using MUI v7 components. The core principles:
- Grid for columns, Stack for vertical rhythm - never use flex/Box for form layout
- fullWidth on ALL form inputs - always
- Correct surface for the job - Paper for containers, Card for structured content, Box for utility only
- Copy from exemplars - never invent page structure from scratch
The Iron Rules (MANDATORY)
Rule 1: Grid for Columns, Stack for Vertical Rhythm
// CORRECT: Grid for column layout
<Grid container spacing={{ xs: 2, md: 3 }}>
<Grid size={{ xs: 12, md: 6 }}>
<TextField fullWidth label="First Name" />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<TextField fullWidth label="Last Name" />
</Grid>
</Grid>
// CORRECT: Stack for vertical spacing between sections
<Stack spacing={3}>
<Typography variant="h6">Section 1</Typography>
<Typography variant="h6">Section 2</Typography>
</Stack>
Rule 2: fullWidth on ALL Form Inputs
// WRONG
<TextField label="Description" />
// CORRECT
<TextField fullWidth label="Description" />
<Select fullWidth />
<Autocomplete fullWidth />
<DatePicker slotProps={{ textField: { fullWidth: true } }} />
Rule 3: MUI v7 Grid Uses size Prop
// WRONG - old MUI v5/v6 syntax
<Grid item xs={12} md={6}>
// CORRECT - MUI v7 syntax
<Grid size={{ xs: 12, md: 6 }}>
Rule 4: No Hardcoded Widths on Inputs
// WRONG
<TextField sx={{ width: 200 }} />
<TextField sx={{ width: '50%' }} />
// CORRECT
<Grid size={{ xs: 12, md: 6 }}>
<TextField fullWidth />
</Grid>
Rule 5: Correct Surface Component
Paper → General container, form wrapper, page section, sidebar, data table wrapper
Card → Structured content: header/body/actions pattern (KPIs, products, profiles)
Box → Utility ONLY: semantic HTML, positioning, decorative elements
Surface Selection Guide
Paper (General Surface)
Use for page sections, sidebars, form wrappers, data tables, filter bars.
// Page section
<Paper background={1} sx={{ p: { xs: 3, md: 5 }, borderRadius: 6 }}>
<Stack spacing={3}>
<Typography variant="h6">Section Title</Typography>
<Grid container spacing={2}>
{/* form fields */}
</Grid>
</Stack>
</Paper>
// Sticky sidebar
<Paper background={1} sx={{ p: { xs: 3, md: 4 }, position: { md: 'sticky' }, top: { md: 80 } }}>
{/* sidebar */}
</Paper>
Card (Structured Content)
Use when content has a distinct header + body + actions structure.
// KPI Card
<Card background={1} variant="outlined">
<CardHeader title="Revenue" action={<IconButton>...</IconButton>} />
<CardContent>
<Typography variant="h4">$42,500</Typography>
</CardContent>
<CardActions>
<Button size="small">View Report</Button>
</CardActions>
</Card>
// Media Card
<Card background={1} variant="outlined" sx={{ maxWidth: 345 }}>
<CardMedia component="img" image="/path.webp" sx={{ height: 200, objectFit: 'cover' }} />
<CardContent>
<Typography variant="h6">Product Name</Typography>
</CardContent>
<CardActions>
<Button size="small">Details</Button>
</CardActions>
</Card>
// Clickable Card
<Card background={1} variant="outlined">
<CardActionArea
<CardContent>
<Typography variant="h6">Clickable card</Typography>
</CardContent>
</CardActionArea>
</Card>
Card sub-components:
| Component | Purpose |
|---|---|
CardHeader |
Title, subtitle, avatar, action button |
CardContent |
Main body content (always wrap content) |
CardActions |
Buttons and action controls |
CardMedia |
Images, videos |
CardActionArea |
Makes entire card clickable |
Box (Utility Only)
Box is NOT a surface. Use only for:
// Semantic HTML
<Box component="section" sx={{ py: 4 }}>...</Box>
// Positioning
<Box sx={{ position: 'relative' }}>...</Box>
// Decorative
<Box sx={{ width: 40, height: 40, borderRadius: '50%', bgcolor: 'primary.main' }} />
// Button alignment
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
<Button>Cancel</Button>
<Button variant="contained">Save</Button>
</Box>
NEVER use Box for: form column layout, surface/cards, display: 'flex' with children, flexDirection: 'row' (use Stack)
Page Structure Templates
Settings/Form Page
<Container maxWidth={false} sx={{ px: { xs: 0 } }}>
<Stack spacing={3}>
<Typography variant="h5">Settings</Typography>
<Paper background={1} sx={{ p: { xs: 3, md: 5 }, borderRadius: 6 }}>
<Stack spacing={3}>
<Typography variant="h6">Section</Typography>
<Grid container spacing={{ xs: 2, md: 3 }}>
{/* fields */}
</Grid>
</Stack>
</Paper>
</Stack>
</Container>
Note: Do NOT add
maxWidth: 660or similar hard limits on Container — this restricts ALL child tab panels/content. Let child components control their own max-width if needed.
Dashboard Page
<Grid container spacing={{ xs: 2, md: 3 }}>
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
<Card background={1} variant="outlined">KPI</Card>
</Grid>
<Grid size={{ xs: 12, md: 8 }}>
<Paper background={1} sx={{ p: { xs: 2, md: 3 } }}>Chart</Paper>
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Paper background={1} sx={{ p: { xs: 2, md: 3 } }}>Widget</Paper>
</Grid>
</Grid>
Detail Page (Main + Sidebar)
<Grid container spacing={{ xs: 2, md: 3 }}>
<Grid size={{ xs: 12, md: 8, xl: 9 }}>
<Paper sx={{ p: { xs: 3, md: 5 } }}>Main content</Paper>
</Grid>
<Grid size={{ xs: 12, md: 4, xl: 3 }}>
<Paper background={1} sx={{ p: { xs: 3, md: 4 }, position: { md: 'sticky' }, top: { md: 80 } }}>
Sidebar
</Paper>
</Grid>
</Grid>
Account Tab Panel Content
Tab panels in the Account section use simple vertical stacking — NOT Grid containers. The parent AccountTabPanel.jsx already handles the title/icon header. Tab panel content goes directly below.
// CORRECT: Simple Stack for vertical sections within a tab panel
const MyTabPanel = () => (
<Stack direction="column" spacing={3}>
{/* Header/stats section */}
<Paper background={1} sx={{ p: { xs: 3, md: 4 }, borderRadius: 6 }}>
<Stack spacing={2}>
<Typography variant="h6">Section Title</Typography>
{/* metadata, legend, actions */}
</Stack>
</Paper>
{/* Main content section */}
<Paper background={1} sx={{ borderRadius: 6, minHeight: 400, overflow: 'auto' }}>
{/* visualization, table, or form content */}
</Paper>
{/* Optional details panel */}
{selectedItem && (
<Paper background={1} sx={{ p: 3, borderRadius: 6 }}>
{/* detail content */}
</Paper>
)}
</Stack>
);
IMPORTANT: Do NOT use Grid container for simple vertical stacking in tab panels. Stack is the correct choice. Match the pattern used by PersonalInfoTabPanel, UsersPermissionsTabPanel, and other existing tab panels.
Visualization/Tree Layout (Pure React/CSS)
For tree or hierarchy visualizations, use pure React/CSS with recursive components. Do NOT use d3-based libraries (react-d3-tree, etc.) as they have Turbopack compatibility issues.
// Recursive tree node with CSS connector lines
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
{/* Node card */}
<Box sx={{
bgcolor: 'background.paper',
borderLeft: `4px solid ${color}`,
borderRadius: 2,
p: 1.5,
minWidth: 160,
maxWidth: 220,
boxShadow: 1,
}}>
{/* node content */}
</Box>
{/* Children with connector lines */}
{hasChildren && (
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', mt: 2 }}>
{/* Vertical line */}
<Box sx={{ width: 2, height: 16, bgcolor: 'divider', mb: 1 }} />
{/* Horizontal connector + children */}
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 3 }}>
{children.map(child => <TreeNode key={child.id} node={child} />)}
</Box>
</Box>
)}
</Box>
List/Table Page
<Stack spacing={3}>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography variant="h5">Items</Typography>
<Button variant="contained">Add</Button>
</Stack>
<Paper background={1} sx={{ p: 2 }}>
<Grid container spacing={2}>
{/* filter fields */}
</Grid>
</Paper>
<Paper sx={{ overflow: 'auto' }}>
<DataGrid />
</Paper>
</Stack>
Dialog Form Layout
<Dialog open={open} maxWidth="sm" fullWidth>
<DialogTitle>Add New Item</DialogTitle>
<DialogContent>
<Grid container spacing={2} sx={{ mt: 1 }}>
<Grid size={12}>
<TextField fullWidth label="Title" required autoFocus />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<TextField fullWidth select label="Category">
<MenuItem value="a">Option A</MenuItem>
</TextField>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<TextField fullWidth select label="Status">
<MenuItem value="active">Active</MenuItem>
</TextField>
</Grid>
<Grid size={12}>
<TextField fullWidth multiline rows={3} label="Description" />
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button
<Button variant="contained" type="submit">Save</Button>
</DialogActions>
</Dialog>
Dialog rules:
maxWidth="sm"simple forms,"md"complex multi-columnfullWidthprop on Dialogsx={{ mt: 1 }}on Grid (prevents label overlap)fullWidthon every inputautoFocuson first field
Grid Patterns
Two-Column Form
<Grid container spacing={{ xs: 2, md: 3 }}>
<Grid size={{ xs: 12, md: 6 }}>Field 1</Grid>
<Grid size={{ xs: 12, md: 6 }}>Field 2</Grid>
</Grid>
Three-Column Row
<Grid container spacing={2}>
<Grid size={{ xs: 12, md: 4 }}>A</Grid>
<Grid size={{ xs: 12, md: 4 }}>B</Grid>
<Grid size={{ xs: 12, md: 4 }}>C</Grid>
</Grid>
Primary + Sidebar (8/4)
<Grid container spacing={3}>
<Grid size={{ xs: 12, md: 8 }}>Main</Grid>
<Grid size={{ xs: 12, md: 4 }}>Sidebar</Grid>
</Grid>
Address Block
<Grid container spacing={2}>
<Grid size={12}><TextField fullWidth label="Street" /></Grid>
<Grid size={{ xs: 12, md: 6 }}><TextField fullWidth label="City" /></Grid>
<Grid size={{ xs: 6, md: 3 }}><TextField fullWidth label="State" /></Grid>
<Grid size={{ xs: 6, md: 3 }}><TextField fullWidth label="ZIP" /></Grid>
</Grid>
Auto-Growing Item (MUI v7)
<Grid container spacing={2}>
<Grid size="grow"><TextField fullWidth /></Grid>
<Grid size="auto"><Button variant="contained">Go</Button></Grid>
</Grid>
Anti-Patterns (NEVER DO)
Flex for Form Columns
// WRONG
<Box sx={{ display: 'flex', gap: 2 }}><TextField /><TextField /></Box>
// CORRECT
<Grid container spacing={2}>
<Grid size={6}><TextField fullWidth /></Grid>
<Grid size={6}><TextField fullWidth /></Grid>
</Grid>
Stack direction="row" for Forms
// WRONG
<Stack direction="row" spacing={2}><TextField /><TextField /></Stack>
// CORRECT
<Grid container spacing={2}>
<Grid size={6}><TextField fullWidth /></Grid>
<Grid size={6}><TextField fullWidth /></Grid>
</Grid>
Hardcoded Widths
// WRONG
<TextField sx={{ width: 200 }} />
// CORRECT
<Grid size={{ xs: 12, md: 6 }}><TextField fullWidth /></Grid>
Box as Flex Container for Children
// WRONG
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<Section1 /><Section2 />
</Box>
// CORRECT
<Stack spacing={3}>
<Section1 /><Section2 />
</Stack>
Paper Where Card Fits Better
// WRONG - manually building header+body+actions
<Paper background={1} sx={{ p: 3 }}>
<Stack direction="row" justifyContent="space-between">
<Typography variant="h6">Revenue</Typography>
<IconButton><MoreVertIcon /></IconButton>
</Stack>
<Typography variant="h4">$42,500</Typography>
<Button size="small">View Details</Button>
</Paper>
// CORRECT - use Card's built-in structure
<Card background={1} variant="outlined">
<CardHeader title="Revenue" action={<IconButton><MoreVertIcon /></IconButton>} />
<CardContent>
<Typography variant="h4">$42,500</Typography>
</CardContent>
<CardActions>
<Button size="small">View Details</Button>
</CardActions>
</Card>
sx flexDirection Instead of Stack Direction
// WRONG
<Stack sx={{ flexDirection: 'row' }}>
// CORRECT
<Stack direction="row">
Grid Container for Simple Vertical Stacking
// WRONG - Grid is for columns, not vertical stacking
<Grid container spacing={3}>
<Grid size={12}><Paper>Header Section</Paper></Grid>
<Grid size={12}><Paper>Content Section</Paper></Grid>
</Grid>
// CORRECT - Stack for vertical rhythm
<Stack spacing={3}>
<Paper>Header Section</Paper>
<Paper>Content Section</Paper>
</Stack>
Ignoring Parent Container Constraints
// WRONG - Building a full-width component without checking parent
// The Account page Container had maxWidth: 660 that constrained ALL tab panels
// CORRECT - Always check the parent component chain:
// 1. What Container/Paper wraps this content?
// 2. Does it have maxWidth, width, or overflow constraints?
// 3. Will my component's min-width fit within the parent?
Spacing Reference
| Context | Value |
|---|---|
| Grid container (forms) | spacing={{ xs: 2, md: 3 }} |
| Grid container (compact) | spacing={2} |
| Stack sections | spacing={3} |
| Stack items | spacing={2} |
| Paper padding (page sections) | p: { xs: 3, md: 5 } |
| Paper padding (compact) | p: { xs: 2, md: 3 } |
| Paper border radius | borderRadius: 6 |
| DialogContent Grid | sx={{ mt: 1 }} |
PierceDesk-Specific
background Prop
<Paper background={1} /> // Standard secondary surface
<Card background={1} variant="outlined" /> // Standard card
Default Paper variant is "outlined" (PierceDesk theme override)
IconifyIcon
import IconifyIcon from 'components/base/IconifyIcon';
<IconifyIcon icon="material-symbols-light:edit" />
MUI MCP Server (ALWAYS USE)
| Tool | When to Use |
|---|---|
mcp__mui-mcp__get_component_info |
Verify props and v7 syntax before using a component |
mcp__mui-mcp__search_components |
Find the right component for a use case |
mcp__mui-mcp__get_customization_guide |
sx prop, theming, breakpoints |
mcp__mui-mcp__get_mui_guide |
General best practices |
Always cross-reference MUI MCP with PierceDesk patterns. PierceDesk patterns take precedence.
Process Checklist
- Identify layout context (page, card, dialog, dashboard)
- Select correct page structure template (above)
- Choose correct surface (Paper vs Card vs Box)
- Wrap form fields in
Grid container spacing={{ xs: 2, md: 3 }} - Each field in
Grid size={{ xs: 12, md: X }}where X = column width - Add
fullWidthto every TextField, Select, Autocomplete, DatePicker - Use Stack only for vertical spacing between major sections
- Self-review against anti-pattern list
- Test at mobile and desktop breakpoints
References
- mui-component-guide.md - Full decision trees, all patterns, exemplar files
- layout-patterns.md - Common layout patterns
- form-dialog-patterns.md - Dialog form examples
- component-docs-map.md - Component docs file map
src/docs/component-docs/- Live code examples (CardDoc, PaperDoc, BoxDoc, GridDoc, StackDoc)