Prisma

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!