# Cowork UI Kit — Layout System

> Grid, containers, spacing scale, stacking rules. Đây là phần "skeleton" mọi page phải tuân theo.

Layout là tầng xương sống dưới mọi component. Nếu skeleton sai, không CSS atom nào cứu được. Document này cứng hoá rule về breakpoint, container, grid, spacing, stacking để 7 brand × 6 archetype share chung 1 base layout, chỉ swap tokens semantic ở trên.

**Cross-link:** Đọc kèm 📄 [USAGE-GUIDELINES.md](file:///C:/Users/DANG/AI-Cowork/00-templates/ui-kit/USAGE-GUIDELINES.md) (khi nào dùng token gì) và 📄 [NAMING](file:///C:/Users/DANG/AI-Cowork/00-templates/ui-kit/tokens/INDEX.md) (convention đặt tên token).

---

## 1. Breakpoint scale

| Token | Min width | Device class | Use case |
|---|---|---|---|
| `xs` | 0–374px | iPhone SE và nhỏ hơn | Edge case, hỗ trợ tối thiểu |
| `sm` | 375–639px | iPhone median (mobile) | Primary mobile target |
| `md` | 640–767px | Large phones, small tablets portrait | Hybrid |
| `lg` | 768–1023px | iPad portrait, small laptop | Tablet |
| `xl` | 1024–1279px | Laptop, iPad landscape | Desktop hybrid |
| `2xl` | 1280px+ | Desktop large | Primary desktop |
| `3xl` | 1536px+ | Extra large monitor | Optional, wide design |

**Mobile-first triết lý:** Mọi style mặc định viết cho mobile. Mọi media query là `min-width` (`@media (min-width: ...)`), KHÔNG `max-width`. Càng lớn càng "add", không "subtract".

**Thêm vào `foundation.css` (`@layer tokens`):**

```css
:root {
  /* Breakpoints — for reference in JS / container queries */
  --bp-sm:   375px;
  --bp-md:   640px;
  --bp-lg:   768px;
  --bp-xl:   1024px;
  --bp-2xl:  1280px;
  --bp-3xl:  1536px;
}
```

**Tailwind config equivalent (`tailwind.preset.js`):**

```js
screens: {
  sm: '375px',
  md: '640px',
  lg: '768px',
  xl: '1024px',
  '2xl': '1280px',
  '3xl': '1536px',
}
```

**Rule cứng:**
- KHÔNG hardcode `@media (min-width: 781px)` random trong component → luôn pick từ scale
- KHÔNG dùng `max-width` query trừ khi thật cần hide-on-mobile (rare)

---

## 2. Container widths

Mỗi context content có max-width riêng để giữ readability và visual rhythm.

| Context | Max-width | Padding (mobile) | Padding (desktop) |
|---|---|---|---|
| **Full bleed** | 100vw | 0 | 0 |
| **Edge-to-edge mobile** | 100% | space-4 (16px) sides | space-6 (24px) |
| **Reading prose** | 65ch (~640px) | space-6 (24px) | space-8 (32px) |
| **Form column** | 480px | space-6 | space-6 |
| **Card grid default** | 1200px | space-6 | space-12 (48px) |
| **Hero text** | 720px | space-6 | space-8 |
| **Dashboard / app** | 1400px | space-4 | space-8 |
| **Marketing wide** | 1280px | space-6 | space-12 |

**Utility classes (thêm vào `semantic.css`):**

```css
.container-prose {
  max-width: 65ch;
  margin-inline: auto;
  padding-inline: var(--space-6);
}

.container-form {
  max-width: 480px;
  margin-inline: auto;
  padding-inline: var(--space-6);
}

.container-default {
  max-width: 1200px;
  margin-inline: auto;
  padding-inline: var(--space-6);
}

.container-wide {
  max-width: 1280px;
  margin-inline: auto;
  padding-inline: var(--space-6);
}

.container-app {
  max-width: 1400px;
  margin-inline: auto;
  padding-inline: var(--space-4);
}

@media (min-width: 768px) {
  .container-default,
  .container-wide { padding-inline: var(--space-12); }
  .container-app  { padding-inline: var(--space-8); }
  .container-prose { padding-inline: var(--space-8); }
}
```

**Khi pick container:**
- **prose** → blog post, ADR, article (D Editorial archetype)
- **form** → login, single CTA form, settings panel
- **default** → marketing site, feature section
- **wide** → landing page hero, pricing grid
- **app** → dashboard, admin tool (A/B archetype)

---

## 3. Grid system — 12-column

**Pattern:** CSS Grid (preferred) hoặc Tailwind `grid-cols-12`.

```css
.grid-12 {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-4);  /* 16px mobile */
}

@media (min-width: 768px) {
  .grid-12 { gap: var(--space-6); }  /* 24px tablet+ */
}

@media (min-width: 1280px) {
  .grid-12 { gap: var(--space-8); }  /* 32px desktop */
}
```

**Span variants:** `.col-span-1` đến `.col-span-12` (dùng Tailwind hoặc tạo utility tay).

### 3.1 Common grid patterns

**Single column (mobile default):**
- Mọi col span full → `col-span-12`
- Stack vertically từ trên xuống

**Sidebar layout (tablet+):**
```html
<div class="grid-12">
  <main class="col-span-12 lg:col-span-9">...</main>
  <aside class="col-span-12 lg:col-span-3">...</aside>
</div>
```

**Hero landing (text + visual):**
```html
<div class="grid-12">
  <div class="col-span-12 md:col-span-7">Text</div>
  <div class="col-span-12 md:col-span-5">Visual</div>
</div>
```

**Dashboard 4-up KPI:**
```html
<div class="grid-12">
  <div class="col-span-12 sm:col-span-6 lg:col-span-3">KPI 1</div>
  <div class="col-span-12 sm:col-span-6 lg:col-span-3">KPI 2</div>
  <div class="col-span-12 sm:col-span-6 lg:col-span-3">KPI 3</div>
  <div class="col-span-12 sm:col-span-6 lg:col-span-3">KPI 4</div>
</div>
```

**Pricing 3-tier:**
```html
<div class="grid-12">
  <div class="col-span-12 md:col-span-4">Basic</div>
  <div class="col-span-12 md:col-span-4">Pro</div>
  <div class="col-span-12 md:col-span-4">Enterprise</div>
</div>
```

### 3.2 Pitfall — grid item content overflow

Grid track có thể grow beyond declared max nếu content (vd `<pre>`, long URL) wider than track. Fix:

```css
.grid-12 > * {
  min-width: 0;  /* CRITICAL — cho phép grid item shrink */
}
```

Áp dụng default cho mọi grid item. Memory pattern: 🔗 [CSS Grid minmax max KHÔNG enforce](file:///C:/Users/DANG/.claude/projects/C--Users-DANG-AI-Cowork/memory/gotcha_css_grid_minmax_max_not_enforced.md).

---

## 4. Section spacing

Padding-block (top/bottom) của section là tín hiệu visual rhythm chính của trang.

| Context | Mobile padding-block | Desktop padding-block | Token |
|---|---|---|---|
| **Hero (landing)** | space-12 (48px) | space-24 (96px) | hero |
| **Feature section** | space-12 (48px) | space-20 (80px) | section-lg |
| **Pricing section** | space-12 (48px) | space-20 (80px) | section-lg |
| **Standard section** | space-8 (32px) | space-16 (64px) | section |
| **Footer** | space-8 (32px) | space-12 (48px) | footer |
| **Modal padding** | space-6 (24px) | space-8 (32px) | modal |
| **Card padding** | space-4 (16px) | space-6 (24px) | card |
| **Inline group** | space-2 (8px) | space-3 (12px) | inline |

**Section utility:**

```css
.section {
  padding-block: var(--space-8);
}

.section-lg {
  padding-block: var(--space-12);
}

.section-hero {
  padding-block: var(--space-12);
}

@media (min-width: 768px) {
  .section     { padding-block: var(--space-16); }
  .section-lg  { padding-block: var(--space-20); }
  .section-hero { padding-block: var(--space-24); }
}
```

**Rule cứng:**
- KHÔNG random padding (`padding: 47px` ❌)
- Section spacing ≥48px mobile, ≥80px desktop (breathing room rule, theo § "UI craft" CLAUDE.md)
- Card padding ≥16px mobile, ≥24px desktop
- Mọi giá trị PHẢI pick từ `--space-*` scale (4px grid)

---

## 5. Stacking rules — mobile-first

**Default behavior:**
- Mobile: stack vertical (1 column, flex column)
- Tablet+ (≥768px): side-by-side (multi-column)

**Pattern utility:**

```css
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.stack-lg {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}

@media (min-width: 768px) {
  .stack-md-row {
    flex-direction: row;
    gap: var(--space-6);
  }
}

@media (min-width: 1024px) {
  .stack-lg-row {
    flex-direction: row;
    gap: var(--space-6);
  }
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}
```

**Stack utilities tóm tắt:**
- `.stack` — vertical with gap 16px (default)
- `.stack-lg` — vertical with gap 32px (sparse content)
- `.stack-md-row` — switch to row at md+ (768px)
- `.stack-lg-row` — switch to row at lg+ (1024px)
- `.cluster` — wrap với gap (cho tag list, button group horizontal)

**Ví dụ:**

```html
<!-- Mobile stack, desktop side-by-side -->
<div class="stack stack-md-row">
  <div>Text</div>
  <div>Image</div>
</div>

<!-- Tag cluster (always horizontal, wrap on overflow) -->
<div class="cluster">
  <span>Tag 1</span>
  <span>Tag 2</span>
  <span>Tag 3</span>
</div>
```

---

## 6. Max text width

**Rule cứng:** Body text PHẢI giới hạn width để giữ readability. Line length tối ưu ~45-75 ký tự.

| Text type | Max width | Lý do |
|---|---|---|
| Article body | 65ch (~640px) | Optimal reading line |
| Hero subtitle | 540px | Hero không xoá tâm point chính |
| Card description | 400px | Card không quá dài |
| Form helper text | 320px | Match input column |
| Toast/alert message | 480px | Notification compact |
| Label text | 280px | Form label ngắn |

**Utility:**

```css
.prose-max     { max-width: 65ch; }
.text-hero     { max-width: 540px; }
.text-card     { max-width: 400px; }
.text-helper   { max-width: 320px; }
.text-toast    { max-width: 480px; }
```

Khi extend full width → break readability. Always wrap content trong container có max-width.

**Anti-pattern:** Hero text section trải dài 100% desktop = mắt khó parse, mỏi mắt khi scan.

---

## 7. Aspect ratios

Standard ratios cho images, cards, video, hero visual:

```css
:root {
  --ar-square:  1 / 1;
  --ar-4-3:     4 / 3;     /* product card landscape */
  --ar-3-4:     3 / 4;     /* portrait card, mobile hero */
  --ar-16-9:    16 / 9;    /* video, desktop hero */
  --ar-21-9:    21 / 9;    /* ultrawide banner */
  --ar-golden:  1.618 / 1; /* hero, feature image */
  --ar-photo:   3 / 2;     /* photography */
}
```

**Apply utility:**

```css
.aspect-square { aspect-ratio: var(--ar-square); }
.aspect-video  { aspect-ratio: var(--ar-16-9); }
.aspect-photo  { aspect-ratio: var(--ar-photo); }
.aspect-card   { aspect-ratio: var(--ar-4-3); }
.aspect-hero   { aspect-ratio: var(--ar-golden); }
```

**Khi dùng:** Mọi image placeholder, video embed, hero visual PHẢI khai báo aspect-ratio để reserve space trước khi load → prevent Cumulative Layout Shift (CLS).

```html
<div class="aspect-video">
  <img src="..." class="w-full h-full object-cover">
</div>
```

---

## 8. Negative space

**Rule:** ≥30% diện tích viewport là empty space (excluding content).

**Cách measure (subjective):**
1. Screenshot trang
2. Đếm tỷ lệ pixel "colored" (có content/element) vs "empty" (background)
3. <30% empty = cramped, cần fix

**Cách fix khi vi phạm:**
- Tăng container padding (space-6 → space-8 → space-12)
- Tăng gap giữa elements (stack gap)
- Bỏ decoration không cần (icon thừa, border thừa)
- Giảm density (chia 8 KPI thành 2 row 4 KPI thay vì 1 row 8 KPI)
- Tăng section padding-block

**Tham chiếu craft rule** § "UI craft — design discipline" CLAUDE.md root.

---

## 9. Sticky elements

| Element | Position | Z-index token | Z-index value |
|---|---|---|---|
| Header navbar | `sticky` top: 0 | `--z-sticky` | 40 |
| Sidebar | `sticky` top: 0 | `--z-sticky` | 40 |
| Filter toolbar | `sticky` top: navbar-height | `--z-sticky` | 40 |
| Bottom CTA mobile | `sticky` bottom: 0 + safe-area | `--z-fixed` | 50 |
| Tab bar mobile | `fixed` bottom: 0 + safe-area | `--z-fixed` | 50 |
| Modal/Overlay | `fixed` | `--z-modal` | 100 |
| Dropdown menu | `absolute` | `--z-dropdown` | 60 |
| Tooltip | `absolute` | `--z-tooltip` | 70 |
| Toast | `fixed` | `--z-toast` | 80 |

**Thêm vào `foundation.css`:**

```css
:root {
  --z-base:     0;
  --z-dropdown: 60;
  --z-sticky:   40;
  --z-fixed:    50;
  --z-tooltip:  70;
  --z-toast:    80;
  --z-modal:    100;
  --z-popover:  90;
}
```

**Mobile safe-area (cho PWA archetype F):**

```css
.tab-bar-fixed {
  position: fixed;
  bottom: 0;
  inset-inline: 0;
  padding-bottom: env(safe-area-inset-bottom);
  z-index: var(--z-fixed);
}

.sticky-bottom {
  position: sticky;
  bottom: 0;
  padding-bottom: env(safe-area-inset-bottom);
  z-index: var(--z-fixed);
}
```

---

## 10. Scroll containers

**Always (set ở `<body>` hoặc root):**

```css
body {
  overflow-x: hidden;  /* prevent horizontal scroll bug */
  overflow-y: auto;
}
```

**Sub-containers với nested scroll:**

```css
.scroll-y {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;       /* momentum on iOS */
  overscroll-behavior: contain;            /* prevent body scroll when nested reaches end */
  scrollbar-width: thin;                   /* Firefox */
}

.scroll-x {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;           /* optional: snap to children */
}
```

**Scrollbar styling (optional, cho dark archetype):**

```css
.scroll-y::-webkit-scrollbar {
  width: 8px;
}
.scroll-y::-webkit-scrollbar-thumb {
  background: var(--color-border);
  border-radius: 4px;
}
```

**Anti-pattern:**
- ❌ Body bị horizontal scroll do 1 element width > viewport (thường do absolute positioned hoặc 100vw + padding)
- ❌ Nested scroll không có `overscroll-behavior: contain` → bug body scroll chain

---

## 11. Responsive image handling

```html
<img
  src="image.jpg"
  alt="Mô tả ngắn"
  class="w-full h-auto"
  loading="lazy"
  decoding="async"
  srcset="image-sm.jpg 640w, image-md.jpg 1024w, image-lg.jpg 1920w"
  sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
>
```

**Rule cứng:**
- `loading="lazy"` cho images below fold (above fold dùng `eager` + preload)
- `decoding="async"` để không block render
- `srcset` + `sizes` cho responsive (serve right resolution per device)
- `aspect-ratio` CSS để reserve space (prevent CLS)
- `object-fit: cover` hoặc `contain` tuỳ ý đồ (cover = crop, contain = letterbox)

**Hero image pattern:**

```html
<div class="aspect-hero relative">
  <img
    src="hero-1920.jpg"
    alt="..."
    class="w-full h-full object-cover"
    loading="eager"
    fetchpriority="high"
    srcset="hero-768.jpg 768w, hero-1280.jpg 1280w, hero-1920.jpg 1920w"
    sizes="100vw"
  >
</div>
```

---

## 12. Layout debugging utility

Toggle on-demand để verify 8px grid alignment:

```css
.debug-grid {
  background-image:
    repeating-linear-gradient(0deg, rgba(255, 0, 0, 0.05) 0 1px, transparent 1px 8px),
    repeating-linear-gradient(90deg, rgba(255, 0, 0, 0.05) 0 1px, transparent 1px 8px);
}

.debug-outline * {
  outline: 1px solid rgba(255, 0, 0, 0.2) !important;
}

.debug-baseline {
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(0, 100, 255, 0.08) 0 1px,
    transparent 1px 24px
  );
}
```

**Cách dùng:**
- Add class `debug-grid` lên `<body>` trong DevTools → check element có align 8px grid không
- `debug-outline` → highlight mọi DOM box
- `debug-baseline` → check vertical rhythm (24px line-height)

Remove trước khi ship.

---

## 13. Common anti-patterns

| Anti-pattern | Sai ở đâu | Fix |
|---|---|---|
| ❌ Fixed pixel widths (`width: 320px`) | Break responsive | Dùng `max-width` + `width: 100%` |
| ❌ Hardcoded breakpoint trong component (`@media (min-width: 854px)`) | Inconsistent | Pick từ `--bp-*` scale |
| ❌ Desktop-first → patch mobile | Bug khó debug, CSS specificity nặng | Mobile-first, add từ nhỏ → lớn |
| ❌ Section padding < 32px | Cramped, không breathing room | ≥48px mobile, ≥80px desktop |
| ❌ Container không max-width | Text dài 1900px → khó đọc | Wrap mọi content trong `.container-*` |
| ❌ Grid item grow beyond max | Forgot `min-width: 0` | Set default `min-width: 0` lên grid children |
| ❌ Horizontal scroll trên mobile | 1 element overflow viewport | `body { overflow-x: hidden }` + tìm element thủ phạm |
| ❌ Sticky element không z-index | Bị overlap content khi scroll | Pick token `--z-sticky` (40) |
| ❌ Random spacing (`padding: 17px`) | Phá 4px grid | Pick từ `--space-*` scale |
| ❌ Image không aspect-ratio | CLS khi load | Khai báo `aspect-ratio` trên container |
| ❌ Modal `position: absolute` | Scroll bug | Dùng `position: fixed` + backdrop |
| ❌ Sidebar dùng `position: fixed` width 100% mobile | Block content | Dùng `sticky` hoặc drawer pattern |

---

## 14. Per-archetype layout reference

Mỗi archetype có default layout pattern. Khi build page mới, pick archetype trước, layout theo bảng:

| Archetype | Container | Grid | Section padding (mobile / desktop) | Notes |
|---|---|---|---|---|
| **A · Dashboard** | `container-app` (1400px) | grid-12, gap-6 | space-8 / space-12 | Sidebar 280px fixed, main fluid. KPI 4-up `lg:col-span-3` |
| **B · Tool** | `container-app` (1400px) | grid-12, gap-6 | space-8 / space-12 | Sidebar 240px + list 320px + detail fluid (3-col tablet+) |
| **C · Landing** | `container-default` (1200px) hoặc `container-wide` (1280px) | grid-12, gap-6 | space-12 / space-20 (section), space-12 / space-24 (hero) | Hero text 7-col + visual 5-col tablet+ |
| **D · Editorial** | `container-prose` (65ch) | Single column | space-12 / space-16 | Drop cap, blockquote inset, image full-bleed exception |
| **E · Deck** | 960×540 fixed viewport | Per-slide custom | Full bleed mỗi slide | 16:9 ratio, no scroll, key-arrow navigation |
| **F · PWA** | viewport (100vw) | Single column hoặc grid-12 with col-span-12 | space-4 sides, safe-area top/bottom | Tab bar fixed bottom, header sticky top, no horizontal scroll |

**Reference pages:** 📁 [00-templates/ui-kit/pages/](file:///C:/Users/DANG/AI-Cowork/00-templates/ui-kit/pages/) chứa `page-a.html` đến `page-f.html` skeleton.

---

## 15. Layout component reference

**React components (TBD trong `00-templates/ui-kit/react/`):**

```tsx
<Container variant="prose | form | default | wide | app">
  {children}
</Container>

<Grid cols={12} gap="6">
  <GridItem span={{ base: 12, md: 6, lg: 3 }}>...</GridItem>
</Grid>

<Stack direction="column" gap="4" mdDirection="row">
  {children}
</Stack>

<Cluster gap="3">
  {tags}
</Cluster>

<AspectRatio ratio="16/9">
  <img ... />
</AspectRatio>
```

**Utility classes (`semantic.css`):**

| Class | Mục đích |
|---|---|
| `.container-prose` / `.container-form` / `.container-default` / `.container-wide` / `.container-app` | Container variants |
| `.grid-12` | 12-col grid với gap responsive |
| `.col-span-{1..12}` | Span variants |
| `.stack` / `.stack-lg` / `.stack-md-row` / `.stack-lg-row` | Vertical/horizontal stack |
| `.cluster` | Wrap horizontal với gap |
| `.aspect-{square,video,photo,card,hero}` | Aspect ratio |
| `.section` / `.section-lg` / `.section-hero` | Section padding-block |
| `.scroll-y` / `.scroll-x` | Nested scroll containers |
| `.sticky-bottom` / `.tab-bar-fixed` | Mobile sticky/fixed elements |
| `.prose-max` / `.text-hero` / `.text-card` / `.text-helper` / `.text-toast` | Max text width |
| `.debug-grid` / `.debug-outline` / `.debug-baseline` | Debugging utilities |

---

## Changelog

| Date | Change |
|---|---|
| 2026-05-20 | Initial draft. Define 7-tier breakpoint scale, 5 container variants, 12-col grid, section padding system, stacking utilities, sticky z-index map, archetype reference. |

---

**Maintainer:** Cowork UI Kit (Dang Phan)
**Related:** 📄 [USAGE-GUIDELINES.md](file:///C:/Users/DANG/AI-Cowork/00-templates/ui-kit/USAGE-GUIDELINES.md) · 📄 [A11Y-RULES.md](file:///C:/Users/DANG/AI-Cowork/00-templates/ui-kit/A11Y-RULES.md) · 📄 [INTERACTION-RULES.md](file:///C:/Users/DANG/AI-Cowork/00-templates/ui-kit/INTERACTION-RULES.md) · 📁 [tokens/](file:///C:/Users/DANG/AI-Cowork/00-templates/ui-kit/tokens/)
