docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를 Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류. - 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로 자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거, 동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거. - 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming, Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business, Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로, 나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는 title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백). 원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지. - 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서. - 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는 지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지. - Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경. - 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
---
|
||||
id: frontend-container-queries
|
||||
title: Container Queries — Component-level Responsive
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [frontend, css, responsive, vibe-coding]
|
||||
tech_stack: { language: "CSS", applicable_to: ["Frontend"] }
|
||||
applied_in: []
|
||||
aliases: [container query, @container, container-type, cqw, cqi, intrinsic sizing]
|
||||
---
|
||||
|
||||
# Container Queries
|
||||
|
||||
> Media query = viewport. **Container query = parent element**. Component 가 자기 container size 따라 변형. 2023+ 모든 modern browser 지원.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- @container: 가장 가까운 named container 의 size.
|
||||
- container-type: size / inline-size / normal.
|
||||
- cqw / cqi / cqh / cqb: container 단위.
|
||||
- Style query (실험): variable 값 따라.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### 기본
|
||||
```css
|
||||
.card-container {
|
||||
container-type: inline-size;
|
||||
container-name: card;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@container card (min-width: 400px) {
|
||||
.card {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<div class="card-container">
|
||||
<div class="card">
|
||||
<img src="..." />
|
||||
<div>Content</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
→ Container 가 400px 이상 = horizontal. 미만 = vertical. Viewport 무관.
|
||||
|
||||
### 사용 예 — 같은 component, 다른 layout
|
||||
```html
|
||||
<!-- Sidebar (작음) -->
|
||||
<aside class="card-container" style="width: 300px">
|
||||
<ProductCard /> <!-- vertical -->
|
||||
</aside>
|
||||
|
||||
<!-- Main (큼) -->
|
||||
<main class="card-container" style="width: 800px">
|
||||
<ProductCard /> <!-- horizontal -->
|
||||
</main>
|
||||
```
|
||||
|
||||
→ 같은 component 가 위치별 다른 layout.
|
||||
|
||||
### Container types
|
||||
```css
|
||||
.container-1 {
|
||||
container-type: inline-size; /* width 만 (most common) */
|
||||
}
|
||||
|
||||
.container-2 {
|
||||
container-type: size; /* width + height */
|
||||
}
|
||||
|
||||
.container-3 {
|
||||
container-type: normal; /* default — query target X */
|
||||
}
|
||||
```
|
||||
|
||||
### Container units
|
||||
```css
|
||||
.text {
|
||||
font-size: 5cqi; /* 5% of container's inline size */
|
||||
padding: 2cqw; /* width % */
|
||||
margin: 1cqh; /* height % (size container 만) */
|
||||
}
|
||||
```
|
||||
|
||||
→ Container 따라 자동 scale.
|
||||
|
||||
### Multiple containers
|
||||
```css
|
||||
.grid { container-type: inline-size; container-name: grid; }
|
||||
.card { container-type: inline-size; container-name: card; }
|
||||
|
||||
@container grid (min-width: 600px) {
|
||||
.card { background: blue; }
|
||||
}
|
||||
|
||||
@container card (min-width: 300px) {
|
||||
.card { padding: 2rem; }
|
||||
}
|
||||
```
|
||||
|
||||
### Ranges
|
||||
```css
|
||||
@container card (300px <= width <= 600px) {
|
||||
.card { background: yellow; }
|
||||
}
|
||||
|
||||
@container card (width > 600px) {
|
||||
.card { background: green; }
|
||||
}
|
||||
```
|
||||
|
||||
### React component
|
||||
```tsx
|
||||
function ProductCard({ product }: ...) {
|
||||
return (
|
||||
<div className="product-card-wrapper" style={{ containerType: 'inline-size', containerName: 'product' }}>
|
||||
<div className="product-card">
|
||||
<img src={product.image} />
|
||||
<div className="product-info">
|
||||
<h3>{product.name}</h3>
|
||||
<p>{product.price}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.product-card { display: flex; flex-direction: column; gap: 8px; }
|
||||
.product-card img { width: 100%; }
|
||||
|
||||
@container product (min-width: 400px) {
|
||||
.product-card { flex-direction: row; }
|
||||
.product-card img { width: 40%; }
|
||||
}
|
||||
|
||||
@container product (min-width: 700px) {
|
||||
.product-card { padding: 2rem; gap: 2rem; }
|
||||
}
|
||||
```
|
||||
|
||||
### Tailwind 4 (built-in support)
|
||||
```html
|
||||
<div class="@container">
|
||||
<div class="flex flex-col @md:flex-row">
|
||||
<img class="w-full @md:w-2/5" />
|
||||
<div class="@md:p-8">...</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
→ `@container` 만 + Tailwind 가 처리.
|
||||
|
||||
### vs Media query
|
||||
```
|
||||
Media query: viewport
|
||||
@media (min-width: 768px) {
|
||||
// 모든 컴포넌트가 같은 breakpoint
|
||||
}
|
||||
|
||||
Container query: parent
|
||||
@container (min-width: 400px) {
|
||||
// 이 component 의 parent 가 400px+ 면
|
||||
}
|
||||
```
|
||||
|
||||
→ Component-driven.
|
||||
|
||||
### 사용 시나리오
|
||||
```
|
||||
- Sidebar 안 card 가 다르게 보임
|
||||
- Modal 안 vs page 안 같은 form 다르게
|
||||
- Dashboard widget — 어디 둬도 OK
|
||||
- Email-style nested layout
|
||||
- 큰 화면에 multi-column page
|
||||
```
|
||||
|
||||
### Style queries (실험)
|
||||
```css
|
||||
.parent {
|
||||
container-name: theme-container;
|
||||
--theme: dark;
|
||||
}
|
||||
|
||||
@container theme-container style(--theme: dark) {
|
||||
.button { background: white; color: black; }
|
||||
}
|
||||
```
|
||||
|
||||
→ CSS variable 따라 styling. 매우 새. Limited support.
|
||||
|
||||
### Browser support
|
||||
```
|
||||
Chrome 105+ (2022)
|
||||
Safari 16.0+ (2022)
|
||||
Firefox 110+ (2023)
|
||||
|
||||
→ 2024+ 거의 안전.
|
||||
Polyfill: container-query-polyfill (legacy)
|
||||
```
|
||||
|
||||
### Performance
|
||||
```
|
||||
Container query = element 의 layout 변경.
|
||||
Layout invalidation 트리거.
|
||||
큰 nested = 비싸 수 있음.
|
||||
|
||||
→ 매번 측정 — 보통 OK, 큰 페이지는 주의.
|
||||
```
|
||||
|
||||
### Anti-pattern: 모든 곳 container
|
||||
```css
|
||||
/* ❌ */
|
||||
* { container-type: inline-size; }
|
||||
|
||||
/* layout cost — 의미 없음 */
|
||||
```
|
||||
|
||||
```css
|
||||
/* ✅ — 명시적 */
|
||||
.card-container { container-type: inline-size; }
|
||||
```
|
||||
|
||||
### Inheritance
|
||||
```css
|
||||
/* Container 가 nested */
|
||||
@container outer (min-width: 800px) {
|
||||
@container inner (min-width: 300px) {
|
||||
.item { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ 두 조건 모두 OK.
|
||||
|
||||
### JS 통합
|
||||
```ts
|
||||
// 동적 변경
|
||||
element.style.containerType = 'inline-size';
|
||||
|
||||
// MutationObserver / ResizeObserver 와 같이 안 필요 — CSS 자동.
|
||||
```
|
||||
|
||||
→ JS 보다 CSS 가 빠름.
|
||||
|
||||
### Modal / popover
|
||||
```css
|
||||
.modal {
|
||||
container-type: inline-size;
|
||||
width: clamp(300px, 80vw, 800px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@container (min-width: 600px) {
|
||||
.modal-content {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ Modal size 변경 시 자동 layout 변경.
|
||||
|
||||
### Card grid + container query
|
||||
```css
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.card-grid > * {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
@container (min-width: 350px) {
|
||||
.card { ... } /* 큰 column 안 card 만 */
|
||||
}
|
||||
```
|
||||
|
||||
→ Grid 의 column width 따라 card 다름.
|
||||
|
||||
### Subgrid + container query (modern)
|
||||
```css
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
@container (max-width: 600px) {
|
||||
.layout { grid-template-columns: 1fr; }
|
||||
}
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 상황 | 추천 |
|
||||
|---|---|
|
||||
| Component-driven UI | Container query |
|
||||
| Page-level layout | Media query |
|
||||
| Reusable card / widget | Container query |
|
||||
| Print | @media print |
|
||||
| Theme switch | Style query (실험) |
|
||||
| Old browser support | Polyfill 또는 media query fallback |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **모든 element container**: layout cost.
|
||||
- **Container query + media query 혼용 같은 결과**: media 만으로 충분 자주.
|
||||
- **container-type: size + inline 만 필요**: inline-size 가 충분.
|
||||
- **Tailwind 3 가정**: `@container` 는 4+.
|
||||
- **Polyfill 무 modern**: 거의 모든 browser 지원.
|
||||
- **Nested container 깊음**: layout 비싸.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- container-type: inline-size + @container 표준.
|
||||
- Component-level responsive.
|
||||
- cqw / cqi 단위 활용.
|
||||
- Tailwind 4 의 @container 친화.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Frontend_Tailwind_Architecture]]
|
||||
- [[React_Component_Composition]]
|
||||
- [[Frontend_A11y_Testing]]
|
||||
Reference in New Issue
Block a user