aderHeight: string;
footerHeight: string;
gap: string;
}
export const appShellTokens: LayoutTokens = {
sidebarWidth: '240px',
headerHeight: '64px',
footerHeight: '48px',
gap: '16px',
};
```css
/* app-shell.css */
.app-shell {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: var(--sidebar-width) 1fr;
grid-template-rows: var(--header-height) 1fr var(--footer-height);
gap: var(--layout-gap);
min-height: 100dvh;
padding: var(--layout-gap);
box-sizing: border-box;
}
.app-header { grid-area: header; }
.app-sidebar { grid-area: sidebar; }
.app-main { grid-area: main; overflow: auto; }
.app-footer { grid-area: footer; }
Architecture Rationale: Named areas decouple HTML structure from visual placement. 1fr on the main column ensures remaining space is allocated predictably. min-height: 100dvh accounts for mobile viewport chrome. Grid handles the 2D coordinate system; components inside never need to calculate their own positioning.
Phase 2: Micro Layout with Flexbox
Flexbox manages one-dimensional content flow. Use it for navigation bars, card internals, form rows, and any component where content length varies. Flexbox distributes space based on intrinsic content size, making it ideal for dynamic data.
/* data-card.css */
.data-card {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.data-card__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.data-card__metrics {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.metric-chip {
flex: 1 1 120px;
min-width: 0;
padding: 8px 12px;
border-radius: 6px;
background: #f8f9fa;
text-align: center;
}
Architecture Rationale: flex-wrap: wrap with flex: 1 1 120px allows metrics to reflow naturally without media queries. min-width: 0 prevents flex items from overflowing their container when text content exceeds available space. Flexbox here handles content-driven sizing; Grid never touches it.
Phase 3: Container-Driven Responsiveness
Viewport media queries force components to adapt to screen size, not their actual available space. Container queries decouple this relationship, enabling true component-level responsiveness.
/* responsive-card.css */
.card-wrapper {
container-type: inline-size;
container-name: widget;
}
.widget-inner {
display: flex;
flex-direction: column;
gap: 16px;
}
@container widget (min-width: 480px) {
.widget-inner {
flex-direction: row;
align-items: center;
}
.widget-visual {
flex: 0 0 40%;
}
.widget-content {
flex: 1;
}
}
Architecture Rationale: container-type: inline-size establishes a containment context. The component now adapts to its parent's width, not the viewport. This eliminates breakpoint collisions in nested layouts and reduces global CSS by 60-70%.
Phase 4: Logical Properties for Internationalization
Physical properties (left, right, top, bottom) break in RTL languages. Logical properties map to writing modes, ensuring layout consistency across locales without duplicate stylesheets.
/* i18n-safe.css */
.nav-item {
margin-inline-end: 16px;
padding-block: 8px;
padding-inline: 12px;
border-inline-start: 3px solid transparent;
}
.nav-item:hover {
border-inline-start-color: #3b82f6;
}
Architecture Rationale: margin-inline-end resolves to margin-right in LTR and margin-left in RTL. padding-block and padding-inline map to vertical and horizontal axes respectively. This approach requires zero JavaScript detection and scales automatically with dir="rtl" attributes.
Pitfall Guide
1. The "Grid for Everything" Fallacy
Explanation: Developers apply Grid to navigation bars, form rows, and card internals because of its explicit control. Grid recalculates track sizes on content change, causing layout thrashing and unexpected overflow.
Fix: Reserve Grid for 2D spatial allocation. Use Flexbox for 1D content flow. If a component only needs row or column alignment, Flexbox is mathematically lighter and more predictable.
2. Flex Overflow Ignorance
Explanation: Flex items default to min-width: auto, which prevents them from shrinking below their content's intrinsic size. Long text or images break out of flex containers, causing horizontal scrollbars.
Fix: Apply min-width: 0 or overflow: hidden to flex children that contain text or media. This allows the flex algorithm to respect the container's boundary and apply truncation or wrapping correctly.
3. auto-fill vs auto-fit Misapplication
Explanation: auto-fill creates empty tracks when space is available, leaving gaps. auto-fit collapses empty tracks and stretches remaining items. Developers often use auto-fill for responsive grids, resulting in misaligned cards on wide screens.
Fix: Use auto-fit for responsive card grids where items should expand to fill available space. Reserve auto-fill for strict grid alignment where empty tracks must preserve column structure.
4. Subgrid Track Mismatch
Explanation: grid-template-rows: subgrid inherits parent track definitions, but only if the child grid's track count matches the parent's. Mismatched counts cause silent fallback to auto-sized tracks, breaking alignment.
Fix: Explicitly define the number of subgrid tracks: grid-template-rows: subgrid / repeat(3, auto). Verify parent and child track counts align before relying on subgrid inheritance.
5. Logical Property Omission in Global Styles
Explanation: Teams apply logical properties selectively, leaving legacy margin-left or padding-right in utility classes. This creates inconsistent spacing in RTL deployments and forces duplicate CSS overrides.
Fix: Enforce logical properties at the design token level. Replace all physical directional properties in global stylesheets. Use linting rules to flag margin-left, padding-right, border-top, etc., in favor of margin-inline-start, padding-block-end, border-block-start.
6. Overusing :nth-child for Layout
Explanation: Developers use :nth-child to force grid columns or flex wrapping at specific breakpoints. This couples layout to DOM order, breaking when content is filtered, sorted, or dynamically injected.
Fix: Use container queries and flex wrapping for responsive reflow. Reserve :nth-child for visual styling (borders, backgrounds), not spatial positioning. Layout should be content-agnostic.
7. Ignoring gap Browser Quirks
Explanation: Flexbox gap support landed in Safari 14.1 (2021). Legacy codebases or internal tools running older WebKit versions silently ignore gap, collapsing spacing and breaking alignment.
Fix: Verify target browser matrix. For legacy support, use margin with negative container padding as a fallback, or implement a CSS @supports (gap: 1rem) progressive enhancement pattern. Modern deployments can safely use gap across both Flexbox and Grid.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Application shell (header, sidebar, main, footer) | CSS Grid with named areas | Explicit 2D control, predictable track allocation, easy responsive collapse | Low (one-time setup) |
| Navigation bar or tab list | Flexbox with justify-content | 1D flow, content-driven sizing, easy spacing with gap | Low |
| Responsive card grid | Grid with auto-fit + minmax() | No media queries, automatic column recalculation, consistent gaps | Medium (requires container testing) |
| Card internals (title, description, actions) | Flexbox column with gap | Handles variable content length, prevents overflow, easy alignment | Low |
| Dashboard widget spanning multiple rows/cols | Grid with grid-column/row: span | Precise 2D placement, maintains alignment with surrounding widgets | Medium |
| Internationalized layout | Logical properties + Grid/Flexbox | Automatic RTL/LTR adaptation, zero JS detection, scalable | Low (initial conversion effort) |
Configuration Template
/* layout-system.css */
:root {
/* Spatial Tokens */
--grid-gap: 16px;
--flex-gap: 12px;
--sidebar-width: 240px;
--header-height: 64px;
--footer-height: 48px;
/* Containment */
--container-padding: 24px;
/* Logical Defaults */
--spacing-inline: 16px;
--spacing-block: 12px;
}
/* Macro Shell */
.layout-shell {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: var(--sidebar-width) 1fr;
grid-template-rows: var(--header-height) 1fr var(--footer-height);
gap: var(--grid-gap);
min-height: 100dvh;
padding: var(--container-padding);
box-sizing: border-box;
}
/* Micro Component */
.layout-flex-row {
display: flex;
flex-wrap: wrap;
gap: var(--flex-gap);
align-items: center;
}
.layout-flex-col {
display: flex;
flex-direction: column;
gap: var(--flex-gap);
}
/* Responsive Wrapper */
.layout-responsive {
container-type: inline-size;
container-name: responsive-zone;
}
@container responsive-zone (min-width: 640px) {
.layout-flex-col {
flex-direction: row;
}
}
/* RTL Safe */
.layout-safe {
margin-inline-end: var(--spacing-inline);
padding-block: var(--spacing-block);
padding-inline: var(--spacing-inline);
}
Quick Start Guide
- Initialize the shell: Create a root container with
display: grid, define grid-template-areas, and set min-height: 100dvh. Assign grid-area to header, sidebar, main, and footer elements.
- Build components with Flexbox: Inside the main area, use
display: flex for navigation, cards, and form rows. Apply gap for spacing and min-width: 0 to text containers.
- Add container responsiveness: Wrap components in a container with
container-type: inline-size. Replace viewport media queries with @container rules that adjust flex-direction or grid-template-columns.
- Enforce logical properties: Convert all
margin-left/right and padding-top/bottom to margin-inline/block equivalents. Test layout by adding dir="rtl" to the root element.
- Validate in production: Run Lighthouse layout stability tests. Verify no horizontal scroll on mobile. Check subgrid inheritance and
gap fallbacks if supporting legacy browsers. Deploy with confidence.