7.0 KiB
id: web-anchor-positioning-css title: CSS Anchor Positioning / @scope / Speculation Rules category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [web, css, vibe-coding] tech_stack: { language: "CSS", applicable_to: ["Frontend"] } applied_in: [] aliases: [CSS anchor, anchor positioning, @scope, speculation rules, prerender, modern CSS]
Modern CSS Features
Floating UI / scoped CSS / prerender 의 native. Anchor (Chrome 125+), @scope (Chrome 118+), Speculation Rules (Chrome 110+).
📖 핵심 개념
- Floating element 의 native (anchor).
- CSS-in-JS 의 scoped CSS native.
- Speculative prerender / preload.
💻 코드 패턴
Anchor positioning
<button id='trigger'>Open</button>
<div id='tooltip' class='tooltip'>Hello</div>
#trigger {
anchor-name: --my-trigger;
}
.tooltip {
position: absolute;
position-anchor: --my-trigger;
top: anchor(bottom); /* 매 anchor 의 bottom */
left: anchor(center);
/* 또는 */
position-area: bottom;
}
→ JS / Floating UI 없이 popover position.
position-area (단순)
.tooltip {
position-anchor: --trigger;
position-area: bottom;
}
→ "anchor 의 bottom" 식 declarative.
Try alternatives (fallback)
.tooltip {
position-anchor: --trigger;
position-area: bottom;
position-try-fallbacks:
bottom right,
top left,
top right;
}
→ Viewport edge 시 다른 position.
Popover + anchor
<button popovertarget='my-pop' style='anchor-name: --btn'>Open</button>
<div id='my-pop' popover='auto' style='position-anchor: --btn; position-area: bottom'>
Content
</div>
→ Popover + anchor = native modal.
@scope (CSS scoping)
<div class='card'>
<h2>Title</h2>
<p>Body</p>
</div>
@scope (.card) {
h2 { color: blue; }
p { font-size: 14px; }
}
→ .card 안 만 적용. CSS-in-JS 없이.
@scope to (limit)
@scope (.card) to (.card-children) {
h2 { color: blue; }
}
→ .card-children 의 descendant 가 안 받음.
CSS nesting (related)
.card {
padding: 16px;
& h2 {
font-size: 1.5rem;
}
}
Speculation Rules (prerender)
<script type='speculationrules'>
{
"prerender": [{
"where": { "href_matches": "/products/*" },
"eagerness": "moderate"
}]
}
</script>
→ Hover 시 prerender. Click = instant.
Eagerness
- conservative: explicit only (anchor click).
- moderate: hover.
- eager: visible link.
- immediate: 매 link.
→ Cost vs UX trade-off.
Prefetch (lighter)
<script type='speculationrules'>
{
"prefetch": [{
"where": { "href_matches": "/articles/*" }
}]
}
</script>
→ HTML / resource preload (no execute).
Container queries
.card {
container-type: inline-size;
}
@container (min-width: 400px) {
.card-content {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
→ Component 의 size 따라 layout.
Container query units
.card {
container-type: inline-size;
container-name: card;
}
.card h2 {
font-size: 5cqw; /* 5% of card width */
}
→ cqw, cqh, cqi, cqb 가 container 의 width / height.
View timeline
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.fade {
animation: fade-in linear;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
→ Scroll 위치 따라 animation. JS 없이.
Scroll timeline
.progress {
animation: progress-bar linear;
animation-timeline: scroll();
}
@keyframes progress-bar {
from { width: 0%; }
to { width: 100%; }
}
→ Page scroll 따라 progress bar.
Color modern
:root {
--primary: oklch(0.7 0.15 270);
}
.button:hover {
background: oklch(from var(--primary) 0.6 c h); /* darker */
}
→ OKLCH = perceptual uniform color.
color-mix()
.button {
background: color-mix(in oklch, var(--primary) 70%, white);
}
→ Mix 2 color.
relative colors
.button:hover {
background: oklch(from var(--primary) calc(l * 0.8) c h);
}
→ 기존 color 의 lighter / darker 자동.
:has() selector
.card:has(img) {
padding-top: 0;
}
form:has(input:invalid) {
border-color: red;
}
→ Parent 가 child 따라 style. Game-changing.
subgrid
.parent {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
.child {
display: grid;
grid-template-columns: subgrid;
}
→ Child 가 parent 의 grid 사용.
Browser support
- Anchor: Chrome 125+, FF flag.
- @scope: Chrome 118+.
- Speculation Rules: Chrome 110+.
- Container queries: 모든 modern.
- :has(): 모든 modern.
- View Timeline: Chrome 115+.
- OKLCH: 모든 modern.
- Subgrid: 모든 modern (Safari 16+).
→ Caniuse 가 reality.
Polyfill / fallback
.tooltip {
/* Fallback */
position: absolute;
top: 100%;
left: 50%;
/* Modern */
position-anchor: --trigger;
position-area: bottom;
}
→ Progressive enhancement.
Use case
Anchor: tooltip, popover, dropdown.
@scope: component-scoped CSS.
Speculation: navigation 빠름.
Container: responsive component.
:has(): conditional layout.
View Timeline: scroll-based animation.
vs Floating UI library
Floating UI:
- JavaScript (3 KB).
- 모든 browser.
- 정밀 control.
Native anchor:
- 0 JS.
- Modern browser only.
- CSS declarative.
→ Modern only = native.
Cross-browser = library.
vs CSS-in-JS
CSS-in-JS:
- JavaScript runtime.
- Dynamic style.
- 큰 bundle.
@scope:
- Native CSS.
- Build-time.
- 작은.
→ Modern = @scope.
Production examples
- Astro / Next: View Transitions.
- Tailwind 4: Lightning CSS + container.
- Vercel docs: Speculation Rules.
- Apple: scroll animation.
LLM / Cursor 도움
Modern CSS 가 LLM 의 baked.
- Generate anchor positioning.
- @scope 작성.
- :has() pattern.
→ 작은 bundle + clean code.
함정
- Anchor 가 Chrome 만: fallback 필수.
- Container 의 layout 가 ambiguous: type 명시.
- :has() 의 performance: 매우 큰 tree 가 slow.
- View Timeline 의 timing 정밀 X.
- Speculation Rules cost: prerender 가 page render.
🤔 의사결정 기준
| 작업 | 추천 |
|---|---|
| Floating UI | Anchor (modern) / Floating UI |
| Component CSS | @scope |
| Page navigation | Speculation Rules |
| Responsive component | Container queries |
| Conditional layout | :has() |
| Scroll animation | View Timeline |
| Color | OKLCH |
❌ 안티패턴
- Anchor + no fallback: Safari 깨짐.
- 모든 페이지 prerender: cost.
- Container 가 type 안: ignore.
- :has() 큰 tree: performance.
- CSS-in-JS + @scope 둘 다: redundant.
🤖 LLM 활용 힌트
- Anchor positioning 가 Chrome 125+.
- @scope 가 native CSS-in-JS.
- Speculation Rules 가 prerender.
- :has() 가 game-changing parent selector.