Layout Patterns — Stack, Inline, Grid
Dành cho: Developer muốn hiểu cách tổ chức layout trong Prisma Prerequisite: Đã đọc Stacking Design Guide Thời gian đọc: ~12 phút
1. Ba Layout Patterns Cơ Bản
Mỗi tier (page/section/group/comp) có thể sử dụng 3 pattern khác nhau:
| Pattern | CSS Model | Hướng | Khi nào dùng |
|---|---|---|---|
| Stack | flex-direction: column + gap |
Dọc ↓ | Danh sách, form fields, sidebar items |
| Inline | flex-direction: row + gap |
Ngang → | Button groups, tabs, breadcrumb |
| Grid | display: grid |
2D ↓→ | Stat cards, icon grid, dashboard panels |
Class Naming Convention
text
.layout-{tier}-{pattern}
Examples:
.layout-block-stack → Page content stacked vertically
.layout-section-grid → Section with grid layout
.layout-module-stack → Card content stacked vertically
.layout-item-inline → Buttons side by side
Token Format
text
Dọc (stack): --{tier}-stack-gap-{size}
Ngang (inline): --{tier}-inline-gap-{size}
Grid:
--{tier}-grid-row-gap
--{tier}-grid-column-gap
Trong đó:
{tier} = page | section | group | comp
{size} = large | default | small
2. Stack Pattern — Xếp Dọc
Dùng phổ biến nhất — mọi thứ xếp từ trên xuống dưới.
Dùng khi nào?
- Form fields
- Sidebar navigation items
- Card content (title → body → footer)
- Page sections
Ví dụ: Card content xếp dọc
html
<article class="container-module layout-module-stack">
<h3>Revenue Report</h3>
<p>Monthly revenue analysis for Q1 2026.</p>
<div class="layout-item-inline">
<button class="btn-primary">View Details</button>
<button class="btn-secondary">Export</button>
</div>
</article>
CSS tương ứng
css
.layout-module-stack {
display: flex;
flex-direction: column;
gap: var(--group-stack-gap-default);
}
3. Inline Pattern — Xếp Ngang
Các element xếp cạnh nhau theo hàng ngang.
Dùng khi nào?
- Button groups
- Tab bars
- Icon + text combos
- Breadcrumb items
- Tag lists
Ví dụ: Button group
html
<div class="layout-item-inline">
<button class="btn btn-primary">Save</button>
<button class="btn btn-secondary">Cancel</button>
<button class="btn btn-ghost">Reset</button>
</div>
Ví dụ: Section header (title + action buttons)
html
<section class="container-section">
<div class="layout-section-inline">
<h2>Recent Activity</h2>
<button class="btn btn-outline">View All</button>
</div>
<!-- section body... -->
</section>
CSS tương ứng
css
.layout-item-inline {
display: flex;
flex-direction: row;
gap: var(--comp-inline-gap-default);
align-items: center;
}
4. Grid Pattern — Lưới 2D
Phân bổ nội dung theo cả dòng và cột.
Dùng khi nào?
- Stat cards (2, 3, 4 cột)
- Service/category grids
- Gallery/thumbnail layouts
- Dashboard panels
Ví dụ: Stats Grid (4 cột)
html
<section class="container-section layout-section-grid grid-cols-4">
<article class="container-module layout-module-stack">
<h3>Revenue</h3>
<span class="stat">$12,450</span>
</article>
<article class="container-module layout-module-stack">
<h3>Users</h3>
<span class="stat">1,234</span>
</article>
<article class="container-module layout-module-stack">
<h3>Orders</h3>
<span class="stat">567</span>
</article>
<article class="container-module layout-module-stack">
<h3>Growth</h3>
<span class="stat">+12%</span>
</article>
</section>
Grid Helper Classes
| Class | Columns |
|---|---|
.grid-cols-2 |
2 cột cố định |
.grid-cols-3 |
3 cột cố định |
.grid-cols-4 |
4 cột cố định |
.grid-auto-sm |
Auto-fill, min 160px |
.grid-auto-md |
Auto-fill, min 240px |
.grid-auto-lg |
Auto-fill, min 320px |
5. Layout Cheat Sheet — Các Screen Phổ Biến
Dashboard Screen
text
Block: .layout-holy-grail
├── Header: navbar (sticky)
├── Main: .layout-sidebar
│ ├── Sidebar: .layout-block-stack (nav items)
│ └── Content: .container-block .layout-block-stack
│ ├── Section "Stats": .layout-section-grid .grid-cols-4
│ │ ├── Module: stat card
│ │ ├── Module: stat card
│ │ ├── Module: stat card
│ │ └── Module: stat card
│ ├── Section "Chart": .container-section
│ │ └── Module: chart container
│ └── Section "Recent": .container-section
│ └── Module: data table
Settings Screen
text
Block: .layout-sidebar
├── Sidebar: .layout-block-stack (settings nav)
└── Content: .container-block .layout-block-stack
├── Section "Profile": .container-section .layout-section-stack
│ ├── Module: avatar + name
│ └── Module: form fields (items)
├── Section "Notifications": .container-section .layout-section-stack
│ ├── Module: toggle group
│ └── Module: email preferences
└── Section "Danger Zone": .container-section
└── Module: delete account card
Auth/Login Screen
text
Block: .layout-centered
└── Section: (none — direct module)
└── Module: .container-module .layout-module-stack
├── Item: logo
├── Item: email input
├── Item: password input
├── Item: login button
└── Item: "forgot password" link
List-Detail Screen
text
Block: .layout-sidebar
├── Left: .layout-block-stack (list of items)
│ ├── Module: list item card
│ ├── Module: list item card (selected)
│ └── Module: list item card
└── Right: .container-block .layout-block-stack
├── Section "Header": .container-section .layout-section-inline
│ ├── Item: title
│ └── Item: actions (buttons)
└── Section "Content": .container-section .layout-section-stack
├── Module: detail info
└── Module: comments/activity
6. Density Adaptation — Tự Động Scale
Một trong những tính năng mạnh nhất: khi density mode thay đổi, TẤT CẢ tokens tự scale — bạn không cần viết media queries!
4 Density Modes
| Mode | --spacing |
Khi nào |
|---|---|---|
compact |
3px | Mobile small, data-heavy tables |
default |
4px | Mặc định cho mọi screen |
comfortable |
5px | Tablet, wider screens |
spacious |
6px | Desktop widescreen, presentation |
Cách Hoạt Động
javascript
// Auto-switch density based on viewport
function adaptDensity() {
const width = window.innerWidth;
const root = document.documentElement;
if (width < 640) {
root.setAttribute('data-style-mode', 'compact');
} else if (width < 1024) {
root.setAttribute('data-style-mode', 'default');
} else if (width < 1536) {
root.setAttribute('data-style-mode', 'comfortable');
} else {
root.setAttribute('data-style-mode', 'spacious');
}
}
Tại Sao Không Viết Media Queries?
Vì mọi tier token đều dùng calc() dựa trên biến --spacing:
css
/* Ví dụ: section padding tự scale */
--section-padding-default: calc(var(--spacing) * 5);
/* Khi --spacing = 3px (compact) → 15px
Khi --spacing = 4px (default) → 20px
Khi --spacing = 6px (spacious) → 30px */
Một lần set data-style-mode, toàn bộ UI tự scale đồng bộ!
7. App Mode vs Web Mode
| App Mode | Web Mode | |
|---|---|---|
| Target | Mobile/tablet app (xPOS, banking) | Responsive web (showcase, dashboard) |
| Density | Fixed — locked per profile | Responsive — auto by viewport |
| Token source | tokens-compact.css (generated) |
variables.css (live) |
| Pipeline | generate-app-tokens.js --app=<name> |
@import variables.css |
8. Ví Dụ Thực Tế: Build Dashboard Từ Zero
html
<!-- Layer 1: Block — Page Shell -->
<body class="layout-holy-grail">
<header>
<nav class="surface-nav">...</nav>
</header>
<div class="main">
<!-- Sidebar -->
<aside class="layout-block-stack">
<div class="layout-item-stack">
<a href="#">Dashboard</a>
<a href="#">Analytics</a>
<a href="#">Settings</a>
</div>
</aside>
<!-- Layer 2: Content -->
<main class="container-block layout-block-stack">
<!-- Layer 2: Section — Stats Grid -->
<section class="container-section layout-section-grid grid-cols-4">
<!-- Layer 3: Module — Stat Card -->
<article class="container-module layout-module-stack">
<span class="stat-label">Revenue</span>
<span class="stat-value">$12,450</span>
<span class="stat-trend">+12%</span>
</article>
<article class="container-module layout-module-stack">
<span class="stat-label">Users</span>
<span class="stat-value">1,234</span>
<span class="stat-trend">+5%</span>
</article>
<!-- ... more cards ... -->
</section>
<!-- Layer 2: Section — Chart -->
<section class="container-section layout-section-stack">
<article class="container-module slotted">
<div class="slot-header">
<h3>Revenue Overview</h3>
<!-- Layer 4: Item — Buttons -->
<div class="layout-item-inline">
<button class="btn btn-ghost">Weekly</button>
<button class="btn btn-ghost">Monthly</button>
<button class="btn btn-primary">Yearly</button>
</div>
</div>
<div class="slot-body">
<!-- chart content -->
</div>
</article>
</section>
<!-- Layer 2: Section — Data Table -->
<section class="container-section layout-section-stack">
<article class="container-module slotted">
<div class="slot-header">
<h3>Recent Transactions</h3>
<button class="btn btn-outline">Export</button>
</div>
<div class="slot-body-flush">
<table><!-- table content --></table>
</div>
<div class="slot-footer-between">
<span>Showing 1-10 of 50</span>
<div><!-- pagination --></div>
</div>
</article>
</section>
</main>
</div>
</body>
Chú ý cách xếp tầng:
Block > Section > Module > Item. Mỗi tầng dùng đúng token tier → khi chuyển density mode, toàn bộ screen tự điều chỉnh!