선택자
기본 선택자
CSS 선택자는 스타일링을 위해 HTML 요소를 대상으로 합니다. 타입 선택자는 요소 이름을 매칭합니다(p, div). 클래스 선택자(.)는 class 속성을 매칭하고 재사용할 수 있습니다. ID 선택자(#)는 단일 요소를 매칭하고 고유해야 합니다. 범용 선택자(*)는 모든 것을 매칭합니다. 쉼표로 선택자를 그룹화하여 스타일을 공유하세요.
/* Type selector - all <p> elements */
p { color: #333; }
/* Class selector - elements with class="btn" */
.btn { padding: 8px 16px; }
/* ID selector - element with id="header" */
#header { background: #fff; }
/* Universal selector - all elements */
* { box-sizing: border-box; }
/* Grouping selector */
h1, h2, h3 { font-family: Arial, sans-serif; }결합자
결합자는 요소 간의 관계를 정의합니다. 자손(공백)은 중첩된 모든 요소를 매칭합니다. 자식(>)은 직계 자식만 매칭합니다. 인접 형제(+)는 바로 다음 요소를 매칭합니다. 일반 형제(~)는 이후의 모든 형제를 매칭합니다. 추가 클래스 없이 정밀한 스타일링을 위해 결합자를 이해하는 것이 필수적입니다.
/* Descendant - any <a> inside .nav */
.nav a { text-decoration: none; }
/* Child - direct <li> children of <ul> */
ul > li { list-style: none; }
/* Adjacent sibling - <p> right after <h1> */
h1 + p { font-size: 1.2em; }
/* General sibling - all <p> after <h1> */
h1 ~ p { color: gray; }속성 선택자
속성 선택자는 속성 값을 기반으로 요소를 매칭합니다. [attr]은 존재를 매 칭합니다. [attr=val]은 정확한 값을 매칭합니다. ^=는 접두사, $=는 접미사, *=는 부분 문자열을 매칭합니다. 이는 추가 클래스 없이 폼 입력, 유형별 링크, 또는 data 속성이 있는 요소를 스타일링하는 데 강력합니다.
/* Elements with type attribute */
[type] { border: 1px solid #ccc; }
/* Exact match */
[type="text"] { padding: 4px; }
/* Starts with */
a[href^="https"] { color: green; }
/* Ends with */
a[href$=".pdf"] { color: red; }
/* Contains */
a[href*="example"] { font-weight: bold; }의사 클래스
의사 클래스는 상태나 위치를 기반으로 요소를 선택합니다. 대화형: :hover, :focus, :active, :visited, :disabled. 구조적: :first-child, :last-child, :nth-child(n), :nth-of-type(n). :not()은 선택자를 부정합니다. :nth-child(odd/even)는 얼룩무늬 줄을 만듭니다. :nth-child(3n)은 3번째마다 선택합니다. 이는 추가 클래스의 필요성을 줄입니다.
/* Interactive states */
a:hover { color: red; }
a:visited { color: purple; }
input:focus { border-color: blue; }
input:disabled { background: #eee; }
button:active { transform: scale(0.98); }
/* Structural */
li:first-child { font-weight: bold; }
li:last-child { border: none; }
li:nth-child(odd) { background: #f9f9f9; }
li:nth-child(3n) { color: blue; }
p:not(.highlight) { color: #333; }의사 요소
의사 요소(::콜론 두 개)는 요소의 특정 부분을 스타일링합니다. ::before와 ::after는 생성된 콘텐츠를 삽입합니다(content 속성 필요). ::first-letter와 ::first-line은 텍스트 부분을 스타일링합니다. ::selection은 강조된 텍스트를 스타일링합니다. ::placeholder는 입력 플레이스홀더를 스타일링합니다. 참고: ::before/::after는 기본적으로 인라인입니다 - 블록 동작을 위해 display:block을 설정하세요.
/* First line of a paragraph */
p::first-line { font-weight: bold; }
/* First letter (drop cap) */
p::first-letter { font-size: 3em; float: left; }
/* Insert content before/after */
.quote::before { content: "\201C"; }
.quote::after { content: "\201D"; }
/* Selection styling */
::selection { background: yellow; color: black; }
/* Placeholder styling */
input::placeholder { color: #999; }특이성 & !important
특이성은 선택자가 충돌할 때 어떤 규칙이 적용되는지 결정합니다. 인라인 스타일 > ID > 클래스 > 타입. 특이성이 동점일 때, 마지막 규칙이 이깁니다. !important는 모든 것을 재정의하지만 캐스케이드를 깨뜨립니다 - 피하세요. DevTools로 특이성을 검사하세요. 유지보수성을 위해 클래스 기반 선택자를 선호하세요. 클래스 반복(.btn.btn)으로 특이성을 높이는 것은 해킹입니다 - 피하세요.
/* Specificity hierarchy (low to high):
1. Type selectors (p, div) = 0,0,0,1
2. Class selectors (.btn) = 0,0,1,0
3. ID selectors (#header) = 0,1,0,0
4. Inline styles (style="...") = 1,0,0,0
5. !important = overrides all
*/
/* ID beats class */
#nav .link { color: red; } /* wins */
.nav .link { color: blue; }
/* Avoid !important unless necessary */
.critical { color: red !important; }박스 모델
Margin & Padding
모든 요소는 content, padding, border, margin을 가진 박스입니다. padding은 border 내부에 있고(배경에 영향), margin은 외부에 있습니다(투명). 축약: 1값 = 모든 면, 2값 = 상/하 좌/우, 3 = 상 좌/우 하, 4 = 상 우 하 좌(시계 방향). margin: 0 auto는 정의된 너비로 블록 요소를 가운데 정렬합니다.
.box {
/* TRBL: top right bottom left */
margin: 10px 20px 10px 20px;
/* Shorthand: top/bottom left/right */
margin: 10px 20px;
/* All sides */
padding: 15px;
/* Individual sides */
margin-top: 0;
padding-left: 24px;
/* Center horizontally (block) */
margin: 0 auto;
}Border & Outline
border는 padding 주위에 선을 추가하여 레이아웃에 영향을 줍니다. outline은 레이아웃에 영향을 주지 않고 border 외부에 그립니다 - 포커스 표시기에 유용합니다. border-radius는 모서리를 둥글게 합니다(단일 값 또는 모서리별: 좌상 우상 우하 좌하). outline-offset은 border와 outline 사이에 공간을 추가합니다. 접근성을 위해 항상 보이는 포커스 스타일을 제공하세요.
.box {
/* Shorthand: width style color */
border: 2px solid #333;
/* Individual sides */
border-bottom: 3px dashed red;
border-radius: 8px;
/* Outline (doesn't affect layout) */
outline: 2px solid blue;
outline-offset: 4px;
}
/* Remove default focus, add custom */
button:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(0,123,255,0.5);
}Box Sizing
box-sizing: content-box(기본값)는 width/height가 content에만 적용됨을 의미합니다 - padding과 border가 전체 크기에 추가됩니다. box-sizing: border-box는 padding과 border를 width/height에 포함시켜 크기를 예측 가능하게 합니다. 일관된 레이아웃을 위해 항상 전역에 border-box를 설정하세요. 이는 가장 중요한 CSS reset 중 하나입니다.
/* Default: width = content only */
.content-box {
box-sizing: content-box;
width: 200px;
padding: 20px;
border: 5px solid;
/* Total width: 200 + 40 + 10 = 250px */
}
/* Recommended: width includes padding+border */
.border-box {
box-sizing: border-box;
width: 200px;
padding: 20px;
border: 5px solid;
/* Total width: 200px */
}
/* Apply globally */
*, *::before, *::after {
box-sizing: border-box;
}Box Shadow
box-shadow는 그림자 효과를 추가합니다: offset-x offset-y blur-radius spread-radius color. 양수 오프셋은 그림자를 우/하로 이동합니다. inset은 내부 그림자를 만듭니다. 여러 그림자가 레이어링됩니다(첫 번째 = 맨 위). 자연스럽게 혼합되는 반투명 그림자에 rgba를 사용하세요. 그림자는 깊이에 좋지만 과용하면 성능을 해칩니다 - 미묘한 그림자를 선호하세요.
/* offset-x offset-y blur spread color */
.card {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* Multiple shadows */
.button {
box-shadow:
0 1px 2px rgba(0,0,0,0.1),
0 4px 8px rgba(0,0,0,0.05);
}
/* Inset shadow */
.input:focus {
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
}
/* No shadow */
.no-shadow { box-shadow: none; }Display & Visibility
display: none은 요소를 레이아웃에서 완전히 제거합니다(공간 없음). visibility: hidden은 숨기지만 공간을 유지합니다. opacity: 0은 투명하게 만들지만 여전히 대화형입니다. block 요소는 전체 너비를 차지하고 줄을 바꿉니다. inline 요소는 텍스트와 함께 흐릅니다. inline-block은 인라인 흐름과 블록 차원(width, height)을 결합합니다. 콘텐츠 토글에 display: none을 사용하세요.
/* Display types */
div { display: block; } /* full width, line break */
span { display: inline; } /* content width, no break */
.inline-block { display: inline-block; } /* inline but block props */
.flex { display: flex; } /* flex container */
.grid { display: grid; } /* grid container */
.none { display: none; } /* removed from layout */
/* Visibility (keeps space) */
.hidden { visibility: hidden; }
/* Opacity (keeps space, can interact) */
.fade { opacity: 0; }색상 & 단위
색상 형식
CSS는 다양한 색상 형식을 지원합니다. Hex(#rrggbb)가 가장 일반적입니다. RGB/RGBA는 알파 투명도를 추가합니다. HSL(색조 0-360, 채도%, 밝기%)는 직관적입니다 - 색조를 변경하여 색을 이동하고, 밝기로 음영을 조정하세요. currentColor는 요소의 color 속성을 상속합니다. 최신 CSS는 또한 더 넓은 색역을 위해 oklch()와 color()를 지원합니다. 투명도에 rgba/hsla를 사용하세요.
/* Named colors */
.color { color: red; background: tomato; }
/* Hexadecimal */
.hex { color: #ff6600; } /* 6-digit */
.hex-short { color: #f60; } /* 3-digit shorthand */
/* RGB / RGBA */
.rgb { color: rgb(255, 102, 0); }
.rgba { background: rgba(0, 0, 0, 0.5); }
/* HSL / HSLA (hue, saturation, lightness) */
.hsl { color: hsl(20, 100%, 50%); }
.hsla { background: hsla(120, 100%, 50%, 0.3); }
/* Current color keyword */
.box { color: blue; border: 2px solid currentColor; }길이 단위
px는 절대적이고 예측 가능합니다. em은 부모의 font-size에 상대적입니다(중첩 시 합성). rem은 root(html) font-size에 상대적입니다 - 일관되고 접근성을 위해 선호됩니다. %는 부모에 상대적입니다. vh/vw는 viewport에 상대적입니다(100vh = 전체 높이). font 크기에는 rem을, 레이아웃에는 %를, border/세부 사항에는 px를 사용하세요.
/* Absolute units */
.px { font-size: 16px; } /* 1px = 1/96 inch */
.pt { font-size: 12pt; } /* 1pt = 1/72 inch */
/* Relative to parent font-size */
.em { font-size: 1.5em; } /* 1.5x parent */
/* Relative to root font-size (html) */
.rem { font-size: 1.2rem; } /* 1.2x root (usually 16px) */
/* Relative to parent dimensions */
.pct { width: 50%; } /* 50% of parent */
/* Viewport units */
.vh { height: 100vh; } /* 100% of viewport height */
.vw { width: 50vw; } /* 50% of viewport width */
.vmin { font-size: 2vmin; } /* 2% of smaller dimension */CSS 함수
calc()는 혼합 단위로 수학을 수행합니다(예: 100% - 250px). min()은 가장 작은 값을 선택합니다(반응형 max-width에 좋음). max()는 가장 큰 값을 선택합니다. clamp(min, preferred, max)는 경계 사이에서 확장되는 유동 값을 만듭니다 - 반응형 타이포그래피에 완벽합니다. var()는 선택적 폴백과 함께 사용자 정의 속성을 참조합니다. 이 함수들은 media query 없이 CSS를 동적으로 만듭니다.
/* calc() - mathematical calculations */
.sidebar { width: calc(100% - 250px); }
.gap { margin: calc(1rem + 10px); }
/* min() / max() - choose smaller/larger */
.responsive { width: min(90%, 1200px); }
.min-width { width: max(300px, 50%); }
/* clamp() - fluid sizing with bounds */
.title { font-size: clamp(1.5rem, 4vw, 3rem); }
/* var() - use custom properties */
.btn { color: var(--primary, #007bff); }그라데이션
그라데이션은 부드러운 색상 전환을 만듭니다. linear-gradient(방향, 색상1, 색상2)는 방향으로 이동합니다(to right, 45deg). radial-gradient는 중심점에서 확장합니다. conic-gradient는 중심 주위로 회전합니다. 위치로 색상 정지를 추가 할 수 있습니다: linear-gradient(to right, red 0%, blue 50%, green 100%). 그라데이션은 색상이 아닌 이미지이며 background-image가 허용되는 어디서든 사용할 수 있습니다.
/* Linear gradient */
.bg1 {
background: linear-gradient(to right, #ff0000, #0000ff);
}
/* Diagonal with angle */
.bg2 {
background: linear-gradient(45deg, #ff0000, #00ff00, #0000ff);
}
/* Radial gradient */
.bg3 {
background: radial-gradient(circle, #ff0000, #0000ff);
}
/* Conic gradient */
.bg4 {
background: conic-gradient(red, yellow, green, blue, red);
}필터 & 블렌드 모드
filter는 시각적 효과를 적용합니다: blur(), brightness(), contrast(), grayscale(), sepia(), hue-rotate(), invert(), opacity(), saturate(). 여러 필터가 연결됩니다. mix-blend-mode는 요소를 뒤에 있는 것과 혼합합니다(multiply, screen, overlay 등). backdrop-filter는 요소 뒤 영역에 필터를 적용합니다(glassmorphism에 좋음). 필터는 성능에 영향을 미칠 수 있습니다 - 드물게 사용하세요.
/* Filters */
img.bw { filter: grayscale(100%); }
img.blur { filter: blur(5px); }
img.bright { filter: brightness(1.5); }
img.sepia { filter: sepia(0.8); }
/* Multiple filters */
img.vintage {
filter: sepia(0.5) contrast(1.2) brightness(0.9);
}
/* Blend mode */
.overlay {
background: rgba(255,0,0,0.5);
mix-blend-mode: multiply;
}타이포그래피
폰트 속성
font-family는 폴백 목록을 받습니다 - 브라우저가 사용 가능한 첫 번째 것을 사용합니다. 항상 일반 패밀리(serif, sans-serif, monospace)로 끝내세요. font-weight는 100(얇음)에서 900(굵음)까지입니다. line-height: 1.5는 font 크기의 1.5배를 의미합니다 - 본문 텍스트 가독성에 1.4-1.6이 이상적입니다. font 축약은 최소한 size와 family를 포함해야 합니다.
body {
/* Font shorthand: style weight size/line-height family */
font: italic bold 16px/1.5 Arial, sans-serif;
/* Individual properties */
font-family: "Helvetica Neue", Arial, sans-serif;
font-size: 16px;
font-weight: 400; /* 100-900, normal=400, bold=700 */
font-style: italic; /* normal, italic, oblique */
line-height: 1.5;
}텍스트 스타일링
text-align은 수평 정렬을 제어합니다. text-decoration은 선을 추가합니다(underline, overline, line-through). text-transform은 대소문자를 변경합니다. letter-spacing(자간)과 word-spacing은 문자와 단어 간 간격을 조정합니다. white-space: nowrap은 텍스트 줄바꿈을 방지합니다. 수직 정렬에는 vertical-align(인라인) 또는 flexbox/grid 정렬(블록)을 사용하세요.
p {
text-align: justify; /* left, right, center, justify */
text-decoration: underline; /* none, overline, line-through */
text-transform: capitalize; /* uppercase, lowercase, capitalize */
text-indent: 2em; /* indent first line */
letter-spacing: 0.05em; /* tracking */
word-spacing: 0.1em;
white-space: nowrap; /* prevent wrapping */
}
a {
text-decoration: none;
color: #0066cc;
}텍스트 오버플로우 & 줄바꿈
말줄임표로 텍스트를 자르려면 세 가지 속성이 필요합니다: white-space: nowrap, overflow: hidden, text-overflow: ellipsis. overflow-wrap: break-word는 오버플로우를 방지하기 위해 긴 단어를 끊습니다. word-break: break-all은 어떤 문자에서든 끊습니다. text-wrap: balance(최신)은 제목의 줄 길이를 균형 있게 합니다. 자르기가 작동하려면 항상 max-width나 width를 설정하세요.
/* Truncate with ellipsis */
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
/* Break long words */
.break-word {
overflow-wrap: break-word;
word-break: break-all;
}
/* Balance text wrapping (newer) */
h1 {
text-wrap: balance;
}웹 폰트 (@font-face)
@font-face는 사용자 정의 폰트를 로드합니다. woff2가 최신 형식입니다(최상의 압축). 폴백으로 woff를 제공하세요. font-display: swap은 폰트 로드 시 대체 텍스트를 즉시 표시한 다음 교체합니다(보이지 않는 텍스트 방지). 항상 폴백 font-family를 선언하세요. Google Fonts는 @import나 <link>를 통해 호스팅된 폰트를 제공합니다. 성능을 위해 중요 폰트를 preload하세요.
@font-face {
font-family: 'MyCustomFont';
src: url('fonts/custom.woff2') format('woff2'),
url('fonts/custom.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
body {
font-family: 'MyCustomFont', Arial, sans-serif;
}
/* Google Fonts import */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');리스트 & 링크 스타일링
list-style: none은 사용자 정의 내비게이션이나 스타일된 리스트를 위해 기본 글머리를 제거합니다. list-style-type은 글머리를 변경합니다(disc, circle, square, decimal, none). list-style-position: inside는 글머리를 콘텐츠 영역 내부에 넣습니다. 링크의 경우 LVHA 순서가 중요합니다: :link, :visited, :hover, :active. 이는 링크 상태의 적절한 캐스케이드를 보장합니다.
/* Reset list styles */
ul {
list-style: none;
padding: 0;
margin: 0;
}
/* Custom bullets */
ul.custom {
list-style-type: square;
list-style-position: inside;
}
/* Image bullets */
ul.image {
list-style-image: url('bullet.png');
}
/* Link states (order matters!) */
a:link { color: blue; }
a:visited { color: purple; }
a:hover { color: red; text-decoration: underline; }
a:active { color: orange; }Flexbox 레이아웃
Flex 컨테이너
display: flex는 flex 컨테이너를 만듭니다. justify-content는 주축을 따라 항목을 정렬합니다(행에서 수평). align-items는 교차축을 따라 정렬합니다(수직). flex-direction은 주축을 변경합니다. flex-wrap은 항목이 새 줄로 줄바꿈을 허용합니다. gap은 항목 간 간격을 설정합니다(margin 해킹 대체). Flexbox는 1D 레이아웃(행 또는 열)에 이상적입니다.
.container {
display: flex;
/* Main axis alignment */
justify-content: space-between;
/* flex-start, center, flex-end, space-around, space-evenly */
/* Cross axis alignment */
align-items: center;
/* flex-start, center, flex-end, stretch, baseline */
/* Direction */
flex-direction: row;
/* row, row-reverse, column, column-reverse */
/* Wrapping */
flex-wrap: wrap;
/* nowrap, wrap, wrap-reverse */
/* Gap between items */
gap: 16px;
}Flex 항목
flex-grow는 항목이 여분의 공간을 공유하는 방법을 제어합니다(0 = 늘어나지 않음, 1 = 동등하게 늘어남). flex-shrink는 공간이 제한될 때 항목이 축소하는 방법을 제어합니다. flex-basis는 초기 크기를 설정합니다. 축약 flex: 1은 flex-grow:1, flex-shrink:1, flex-basis:0%를 의미합니다. align-self는 컨테이너의 align-items를 한 항목에 대해 재정의합니다. order는 DOM을 변경하지 않고 항목을 시각적으로 재정렬합니다.
.item {
/* Grow: how much space to take (0 = don't grow) */
flex-grow: 1;
/* Shrink: how much to shrink when space is tight */
flex-shrink: 0;
/* Basis: initial size before growing/shrinking */
flex-basis: 200px;
/* Shorthand: flex: grow shrink basis */
flex: 1 0 200px;
/* Override align-items for this item */
align-self: flex-end;
/* Order (default 0, lower = earlier) */
order: -1;
}일반적인 Flexbox 패턴
Flexbox는 가운데 정렬을 간단하게 만듭니다: justify-content: center + align-items: center. sticky footer의 경우 body를 min-height: 100vh로 flex 열로 만들고 메인 콘텐츠를 flex: 1로 설정하세요. navbar의 경우 justify-content: space-between이 첫 번째와 마지막 항목을 반대쪽 끝으로 밀어냅니다. 이 패턴들은 flexbox 이전에 어려웠던 일반적인 레이아웃 문제를 해결합니다.
/* Center anything */
.center {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
/* Sticky footer */
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.main { flex: 1; }
/* Navbar: logo left, links right */
.nav {
display: flex;
justify-content: space-between;
align-items: center;
}Flex Direction & Wrap
flex-direction: column은 수직 레이아웃을 만듭니다(쌓기에 유용). flex-wrap: wrap은 공간이 부족할 때 항목이 새 줄로 흐르게 합니다. flex: 1 1 300px은 항목이 300px에서 시작하고 공간을 채우기 위해 늘어나며 필요에 따라 축소됨을 의미합니다. align-content는 줄바꿈된 줄 간 간격을 제어합니다(wrap에서만 작동). 이 패턴은 반응형 카드 그리드를 만듭니다.
/* Horizontal (default) */
.row { display: flex; flex-direction: row; }
/* Vertical */
.column { display: flex; flex-direction: column; }
/* Reverse */
.row-rev { display: flex; flex-direction: row-reverse; }
/* Wrap to new lines */
.cards {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.card { flex: 1 1 300px; } /* grow, shrink, basis */
/* Align wrapped lines */
.cards { align-content: space-between; }Flexbox 정렬 심층
flex-direction: column에서 주축은 수직이고 교차축은 수평입니다 - justify-content와 align-items가 역할을 교환합니다. flex 항목의 margin: auto는 사용 가능한 모든 공간을 흡수하여 완벽하게 가운데 정렬합니다. 이는 justify-content/align-items의 대안입니다. 어느 축이 '주'이고 '교차'인지 이해하는 것이 flexbox 정렬 마스터의 핵심입니다.
.container {
display: flex;
flex-direction: column;
/* Main axis = vertical (column) */
justify-content: flex-start;
/* push items to top, center, bottom, or spread */
/* Cross axis = horizontal (column) */
align-items: stretch;
/* stretch items to full width */
}
/* Center a single item perfectly */
.wrapper {
display: flex;
min-height: 100vh;
}
.centered {
margin: auto; /* centers in both axes */
}CSS Grid 레이아웃
Grid 컨테이너
display: grid는 2D 레이아웃을 만듭니다. grid-template-columns은 열 크기를 정의합니다: fr 단위가 여유 공간을 비례적으로 분배합니다. repeat(3, 1fr)은 3개의 동일한 열을 만듭니다. repeat(auto-fit, minmax(250px, 1fr))은 열 수를 자동 조정하는 반응형 grid를 만듭니다 - 이것이 반응형 grid의 '성배'입니다. gap이 간격을 위해 margin을 대체합니다.
.grid {
display: grid;
/* Define columns */
grid-template-columns: 200px 1fr 200px;
/* fixed, flexible, fixed */
/* Or repeat */
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
/* Define rows */
grid-template-rows: auto 1fr auto;
/* Gap (formerly grid-gap) */
gap: 20px;
row-gap: 10px;
column-gap: 20px;
}Grid 항목 배치
Grid 항목은 grid-column: span N으로 여러 셀을 확장할 수 있습니다. 선 번호를 사용할 수도 있습니다: grid-column: 1 / 3은 선 1에서 시작하여 선 3에서 끝남을 의미합니다. 선은 1(좌/상)에서 N+1(우/하)까지 번호가 매겨집니다. grid-area는 항목을 명명된 영역에 할당합니다. Grid는 정밀한 2D 배치를 가능하게 합니다 - 항목이 행과 열을 동시에 확장할 수 있습니다.
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
/* Span multiple columns */
.featured {
grid-column: span 2;
}
/* Explicit placement */
.sidebar {
grid-column: 1 / 3; /* start at line 1, end at line 3 */
grid-row: 1 / 4;
}
/* Named areas */
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }Grid Template Areas
grid-template-areas는 명명된 문자열을 사용하여 레이아웃의 시각적 맵을 만듭니다. 각 문자열은 행을 나타내고, 각 이름은 열을 나타냅니다. 항목은 grid-area를 일치하는 이름으로 설정하여 배치됩니다. 이것이 복잡한 레이아웃을 만드는 가장 읽기 쉬운 방법입니다. 빈 셀에는 '.'을 사용하세요. 같은 이름이 여러 셀을 확장할 수 있습니다. 반응형 레이아웃을 위해 media query에서 영역을 변경하세요.
.layout {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
gap: 10px;
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }Grid 정렬
Grid에는 6개의 정렬 속성이 있습니다. align-items/justify-items는 grid 셀 내에서 항목을 정렬합니다. align-content/justify-content는 컨테이너 내에서 전체 grid track을 정렬합니다(grid가 더 작은 경우에만 보임). align-self/justify-self는 항목별로 재정의합니다. place-items: center는 align-items + justify-items의 축약입니다. place-content: center는 두 content 정렬을 결합합니다.
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
/* Align items within their cell (cross axis) */
align-items: center; /* start, center, end, stretch */
/* Justify items within their cell (main axis) */
justify-items: center;
/* Align the entire grid (if smaller than container) */
align-content: center;
justify-content: center;
}
/* Override for individual item */
.item {
align-self: end;
justify-self: start;
}반응형 Grid (Auto-fit)
repeat(auto-fit, minmax(300px, 1fr))은 사용 가능한 공간에 따라 열 수를 자동으로 조정하는 grid를 만듭니다 - 항목은 최소 300px이고 채우기 위해 늘어납니다. 이는 카드 레이아웃의 media query를 제거합니다. auto-fill은 빈 열을 유지하고(항목은 왼쪽에 남음), auto-fit은 접습니다(항목이 늘어남). 대부분의 경우 auto-fit을 사용하세요. 최적의 결과를 위해 컨테이너에 max-width를 결합하세요.
/* Auto-responsive grid - no media queries needed! */
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
/* Fixed number of columns per breakpoint */
@media (min-width: 768px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
/* Auto-fill vs auto-fit:
auto-fit collapses empty tracks
auto-fill keeps empty tracks */포지셔닝
Position 유형
static이 기본값입니다(일반 문서 흐름). relative는 다른 요소에 영향을 주지 않고 일반 위치에서 오프셋합니다(포지셔닝 컨텍스트 생성). absolute는 흐름에서 제거하고 가장 가까운 positioned 조상에 상대적으로 배치합니다. fixed는 viewport에 상대적으로 배치합니다(스크롤 시 유지). sticky는 relative와 fixed 사이를 토글합니다 - 임계값에서 고정될 때까지 일반적으로 스크롤합니다.
/* Static (default) - normal flow */
.static { position: static; }
/* Relative - offset from normal position */
.relative {
position: relative;
top: 10px;
left: 20px;
}
/* Absolute - positioned relative to nearest positioned ancestor */
.absolute {
position: absolute;
top: 0;
right: 0;
}
/* Fixed - positioned relative to viewport */
.fixed {
position: fixed;
bottom: 0;
width: 100%;
}
/* Sticky - scrolls then sticks */
.sticky {
position: sticky;
top: 0;
}Absolute 포지셔닝
absolute 포지셔닝은 positioned 조상(relative, absolute, fixed, 또는 sticky)이 필요합니다. 없으면 viewport에 상대적으로 배치합니다. top/right/bottom/left 속성이 해당 가장자리에서 오프셋합니다. absolute로 배치된 요소를 가운데 정렬하려면 top:50%, left:50%와 transform: translate(-50%,-50%)을 사용하세요. absolute 포지셔닝은 요소를 일반 흐름에서 제거합니다.
/* Parent must be positioned */
.container {
position: relative;
width: 400px;
height: 300px;
}
/* Position relative to container */
.badge {
position: absolute;
top: 10px;
right: 10px;
}
/* Center absolutely positioned element */
.modal {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}Sticky 포지셔닝
position: sticky는 하이브리드입니다 - 요소가 임계값(예: top: 0)에 도달할 때까지 일반적으로 스크롤한 다음 고정됩니다. sticky 헤더, 사이드바, 테이블 헤더에 좋습니다. 요소는 부모 컨테이너 내에서 고정됩니다(부모가 스크롤 지나가면 고정 멈춤). 조상 중 하나가 overflow: hidden/auto/scroll을 가지면 sticky가 작동하지 않습니다. 겹침 문제를 방지하기 위해 항상 z-index를 설정하세요.
/* Sticky header */
.header {
position: sticky;
top: 0;
background: white;
z-index: 100;
padding: 1rem;
}
/* Sticky sidebar */
.sidebar {
position: sticky;
top: 80px; /* offset for fixed header */
height: calc(100vh - 80px);
overflow-y: auto;
}
/* Note: sticky doesn't work if a parent has
overflow: hidden or overflow: auto */Z-index & 스태킹 컨텍스트
z-index는 positioned 요소의 스태킹 순서를 제어합니다(높음 = 위). positioned 요소에서만 작동합니다(static 제외). 스태킹 컨텍스트는 z-index가 있는 positioned 요소, opacity < 1, transform, 또는 filter로 생성됩니다. 스태킹 컨텍스트 내에서 자식 z-index 값은 해당 컨텍스트에 상대적입니다 - 자식은 더 높은 z-index를 가진 부모의 형제 위에 나타날 수 없습니다. 이는 일반적인 혼란의 원인입니다.
/* Higher z-index = closer to viewer */
.modal-overlay {
position: fixed;
z-index: 9999;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
}
.modal {
position: fixed;
z-index: 10000; /* above overlay */
top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
/* Stacking context: z-index only works
within the same stacking context */
.parent { position: relative; z-index: 1; }
.child { position: absolute; z-index: 9999; }
/* child can't exceed parent's stacking level */Float & Clear (레거시)
float는 flexbox/grid 이전의 주요 레이아웃 방법이었습니다. 요소를 일반 흐름에서 제거하고 좌/우로 밀며, 텍스트가 주위를 감쌉니다. clear는 요소가 float 옆에 나타나는 것을 방지합니다. clearfix hack은 컨테이너가 float된 자식을 둘러싸도록 강제합니다. 오늘날 float는 원래 목적(이미지 주위 텍스트 감싸기)에만 사용하세요. 레이아웃에는 flexbox나 grid를 사용하세요.
/* Float text around an image */
img.float-left {
float: left;
margin: 0 1rem 1rem 0;
}
/* Clear floats */
.clear { clear: both; }
.clear-left { clear: left; }
.clear-right { clear: right; }
/* Clearfix hack for containers */
.clearfix::after {
content: "";
display: table;
clear: both;
}
/* Modern alternative: use flexbox or grid */
/* Float is mainly for text wrapping now */반응형 디자인
Media Query
media query는 조건에 따라 스타일을 적용합니다. min-width(모바일 우선)가 선호됩니다 - 기본 스타일은 모바일을 대상으로 하고, 더 큰 화면을 위해 향상합니다. max-width(데스크톱 우선)는 역입니다. 다른 조건: prefers-color-scheme(다크/라이트), print, orientation(세로/가로), prefers-reduced-motion. media query가 모바일에서 작동하도록 항상 HTML에 viewport 메타 태그를 설정하세요.
/* Mobile-first: base styles first, then min-width */
/* Base (mobile) */
.container { flex-direction: column; }
/* Tablet and up */
@media (min-width: 768px) {
.container { flex-direction: row; }
}
/* Desktop and up */
@media (min-width: 1024px) {
.container { max-width: 1200px; margin: 0 auto; }
}
/* Print styles */
@media print {
.no-print { display: none; }
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
body { background: #1a1a1a; color: #eee; }
}유동 타이포그래피
clamp(min, preferred, max)는 viewport 크기에 따라 부드럽게 확장되는 유동 타이포그래피를 만듭니다 - media query가 필요 없습니다. 선호 값(종종 vw 기반)이 min과 max 경계 사이에서 확장됩니다. min()은 더 작은 값을 선택합니다(반응형 max-width에 좋음). 이 접근 방식은 필요한 media query 수를 줄이고 더 부드러운 확장을 만듭니다. 가독성을 위해 항상 합리적인 min/max를 설정하세요.
/* clamp() for fluid font sizes */
h1 {
font-size: clamp(1.5rem, 5vw, 3rem);
/* min: 1.5rem, preferred: 5vw, max: 3rem */
}
/* Fluid spacing */
.container {
padding: clamp(1rem, 3vw, 3rem);
}
/* Fluid width */
.content {
width: min(90%, 1200px);
margin: 0 auto;
}
/* Viewport-based sizing */
.hero {
height: 100vh;
font-size: clamp(2rem, 8vw, 5rem);
}컨테이너 쿼리
컨테이너 쿼리(CSS Containment Module Level 3)를 사용하면 viewport가 아닌 부모 컨테이너의 크기를 기반으로 요소를 스타일링할 수 있습니다. container-type: inline-size는 컨테이너를 선언합니다. @container는 컨테이너가 조건을 만족할 때 스타일을 적용합니다. 이는 media query보다 모듈식입니다 - 컴포넌트가 화면이 아닌 컨테이너에 적응합니다. 다른 레이아웃 컨텍스트의 재사용 가능한 컴포넌트에 좋습니다.
/* Define a container */
.card-container {
container-type: inline-size;
/* or: container: sidebar / inline-size; */
}
/* Query the container's size */
@container (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
}
}
@container (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
}반응형 이미지 & 미디어
max-width: 100% + height: auto로 이미지를 반응형으로 만듭니다(축소는 하지만 확대는 안 함). padding-bottom hack은 반응형 16:9 비디오 포함을 만듭니다. 최신 aspect-ratio 속성이 더 깔끔합니다 - 비율을 설정하면 브라우저가 높이를 계산합니다. 레이아웃 이동(CLS)을 방지하기 위해 항상 이미지에 width와 height 속성을 설정하세요. 고정 차원 내에서 이미지를 자르려면 object-fit: cover를 사용하세요.
/* Responsive image */
img {
max-width: 100%;
height: auto;
}
/* Responsive video (16:9 aspect ratio) */
.video-wrapper {
position: relative;
padding-bottom: 56.25%; /* 9/16 */
height: 0;
}
.video-wrapper iframe {
position: absolute;
top: 0; left: 0;
width: 100%;
height: 100%;
}
/* Aspect ratio property */
.box {
aspect-ratio: 16 / 9;
width: 100%;
}모바일 우선 전략
모바일 우선은 모바일 스타일을 기본으로 작성하고, min-width media query로 더 큰 화면을 위해 점진적으로 향상시키는 것을 의미합니다. 이는 모바일 사용자(종종 느린 연결)가 더 적은 CSS를 다운로드하게 합니다. 일반적인 중단점: 768px(태블릿), 1024px(데스크톱), 1440px(대형). 더 나은 확장성을 위해 고정 px 대신 상대 단위(rem, %, vw)를 사용하세요. 브라우저 DevTools뿐만 아니라 실제 기기에서 테스트하세요.
/* 1. Start with mobile base styles */
.nav { display: none; } /* hidden on mobile */
.menu-toggle { display: block; }
/* 2. Enhance for larger screens */
@media (min-width: 768px) {
.nav { display: flex; }
.menu-toggle { display: none; }
}
/* 3. Common breakpoints */
/* Mobile: < 768px */
/* Tablet: 768px - 1023px */
/* Desktop: 1024px - 1439px */
/* Large: >= 1440px */
/* 4. Use relative units */
body { font-size: 1rem; } /* not 16px */전환 & 애니메이션
전환
transition은 속성 값 사이의 부드러운 변경을 만듭니다. 문법: transition: property duration timing-function delay. 타이밍 함수: ease(기본값), linear, ease-in, ease-out, ease-in-out, cubic-bezier(). 애니메이션 가능한 속성만 전환됩니다. transition: all을 피하세요(성능). 전환은 의사 클래스 변경(:hover, :focus)이나 JavaScript를 통한 클래스 변경 시 트리거됩니다.
.button {
background: #007bff;
transition: background 0.3s ease, transform 0.2s ease;
}
.button:hover {
background: #0056b3;
transform: translateY(-2px);
}
/* Transition all properties (use sparingly) */
.card {
transition: all 0.3s ease;
}
/* Shorthand: property duration timing-function delay */
.box {
transition: opacity 0.5s ease-in 0.2s;
}Transform
transform은 레이아웃에 영향을 주지 않고 요소를 수정합니다(margin/position과 달리). translate는 이동하고(translateX, translateY, 또는 translate(x,y)), scale은 크기를 조정하고(1 = 100%), rotate는 회전하고(deg, rad, turn), skew는 왜곡합니다. 여러 transform이 순서대로 연결됩니다. transform은 GPU 가속됩니다 - 성능에 좋습니다. 부드러운 효과를 위해 항상 transition과 짝지으세요. transform-origin이 피벗 점을 변경합니다.
.box {
transition: transform 0.3s ease;
}
/* Translate (move) */
.box:hover { transform: translate(10px, 20px); }
.box:hover { transform: translateX(50%); }
/* Scale (resize) */
.box:hover { transform: scale(1.1); }
/* Rotate */
.box:hover { transform: rotate(45deg); }
/* Skew */
.box:hover { transform: skew(10deg, 5deg); }
/* Combine transforms */
.box:hover {
transform: translate(10px, 0) scale(1.1) rotate(5deg);
}키프레임 애니메이션
@keyframes는 애니메이션 단계를 정의합니다. 0%/from이 시작, 100%/to가 끝입니다. 중간 단계(25%, 50%, 75%)를 추가할 수 있습니다. animation 축약: name duration timing-function delay iteration-count direction fill-mode. infinite는 영원히 반복합니다. direction: alternate는 짝수 반복에서 역전합니다. fill-mode: forwards는 최종 상태를 유지합니다. 전환과 달리 애니메이션은 자동으로 실행됩니다.
@keyframes bounce {
0% { transform: translateY(0); }
50% { transform: translateY(-20px); }
100% { transform: translateY(0); }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.ball {
animation: bounce 0.5s ease infinite;
}
.element {
animation: fadeIn 1s ease forwards;
/* name duration timing delay iteration direction fill-mode */
}애니메이션 속성
개별 애니메이션 속성은 미세 제어를 제공합니다. animation-iteration-count는 숫자나 infinite일 수 있습니다. animation-direction: alternate는 앞으로 재생한 다음 뒤로 재생합니다(핑퐁 효과에 좋음). animation-fill-mode: forwards는 종료 후 끝 상태를 유지합니다. animation-play-state: paused는 애니메이션을 멈춥니다 - hover-to-pause에 유용. 여러 애니메이션을 쉼표로 구분: animation: spin 1s, fade 2s;.
.spinner {
animation-name: spin;
animation-duration: 1s;
animation-timing-function: linear;
animation-delay: 0s;
animation-iteration-count: infinite;
animation-direction: normal; /* alternate, reverse */
animation-fill-mode: none; /* forwards, backwards, both */
animation-play-state: running; /* paused */
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Pause on hover */
.spinner:hover {
animation-play-state: paused;
}성능 & 움직임 감소
부드러운 60fps 애니메이션을 위해 transform과 opacity만 애니메이션하세요 - GPU 가속되고 레이아웃(reflow)을 트리거하지 않습니다. width, margin, top 등을 애니메이션하면 브라우저가 매 프레임마다 레이아웃을 재계산하여 버벅거림을 유발합니다. 항상 prefers-reduced-motion: reduce를 존중하세요 - 일부 사용자는 멀미를 경험하거나 전정기 질환이 있습니다. 이 사용자를 위해 즉시 전환하거나 애니메이션을 비활성화하세요.
/* Only animate transform and opacity for performance */
.good {
transition: transform 0.3s, opacity 0.3s;
}
/* Avoid animating these (cause reflow/repaint): */
/* margin, padding, width, height, top, left */
/* Respect user's motion preference */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}변수 & 고급 함수
사용자 정의 속성 (변수)
사용자 정의 속성(CSS 변수)은 재사용 가능한 값을 저장합니다. 전역 액세스를 위해 :root에 정의하세요. var(--name)으로 참조하세요. 캐스케이드되고 모든 범위에서 재정의될 수 있습니다 - 테마에 완벽합니다(다크/라이트 모드). 전처리기 변수와 달리 동적입니다(런타임에 변경)이고 JavaScript로 조작할 수 있습니다. 항상 폴백을 제공하세요: var(--primary, #007bff). 이것이 디자인 토큰을 관리하는 최신 방법입니다.
:root {
/* Colors */
--primary: #007bff;
--primary-dark: #0056b3;
--text: #333;
--bg: #fff;
/* Spacing scale */
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 2rem;
/* Font sizes */
--font-base: 16px;
--font-lg: 1.5rem;
}
.button {
background: var(--primary);
padding: var(--space-sm) var(--space-md);
font-size: var(--font-base);
color: white;
}
/* Override in a scope */
.dark-theme {
--bg: #1a1a1a;
--text: #eee;
}calc() & 수학 함수
calc()는 px, %, em, rem, vw를 혼합하여 어떤 단위로도 수학을 수행합니다. 반응형 레이아웃에 필수적입니다. calc()를 중첩할 수 있지만 불필요합니다 - calc(100% - 2rem) / 3이 내부 calc 없이 작동합니다. min()은 가장 작은 값을, max()는 가장 큰 값을, clamp()는 값을 min과 max 사이에 둡니다. 이 함수들은 JavaScript나 media query 없이 CSS를 동적으로 만듭니다.
/* Mixed unit calculations */
.sidebar {
width: calc(100% - 250px);
}
/* With variables */
.box {
padding: calc(var(--space-md) + 10px);
}
/* Nested calc */
.responsive {
width: calc(calc(100% - 2rem) / 3);
}
/* min(), max(), clamp() */
.fluid {
width: min(90%, 1200px);
font-size: clamp(1rem, 2vw + 1rem, 2rem);
height: max(200px, 50vh);
}Object Fit & Position
object-fit은 이미지/비디오가 컨테이너를 채우는 방법을 제어합니다(<img>에 대한 background-size 같은). cover는 컨테이너를 채우고 오버플로우를 자릅니다(아바타/썸네일에 좋음). contain은 자르지 않고 완전히 맞춥니다(빈 공간 남길 수 있음). fill은 늘립니다(왜곡). object-position은 정렬을 조정합니다(background-position 같은). 이는 반응형 이미지를 위해 background-image hack의 필요성을 대체합니다.
/* Cover: fill container, crop overflow */
.avatar {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 50%;
}
/* Contain: fit entirely, may letterbox */
.preview {
width: 200px;
height: 200px;
object-fit: contain;
background: #eee;
}
/* Position the image within the frame */
.image {
object-fit: cover;
object-position: top center;
}Background 속성
background는 강력한 축약입니다. background-size: cover는 컨테이너를 채웁니다(자르기); contain은 자르지 않고 맞춥니다. background-attachment: fixed는 패럴랙스 효과를 만듭니다(스크롤 중 이미지가 정지). 여러 background가 쉼표로 레이어링됩니다(첫 번째 = 맨 위 레이어). 오버레이를 위해 linear-gradient를 background로 사용하세요: background: linear-gradient(rgba(0,0,0,0.5), transparent), url('image.jpg').
.hero {
/* Shorthand */
background: url('hero.jpg') center/cover no-repeat fixed;
/* Individual properties */
background-image: url('hero.jpg');
background-size: cover; /* cover, contain, or px/% */
background-position: center; /* top, bottom, left, right, or x y */
background-repeat: no-repeat;
background-attachment: fixed; /* scroll, fixed, local */
/* Multiple backgrounds */
background:
url('overlay.png') no-repeat center,
url('hero.jpg') center/cover;
}CSS 중첩 (최신)
최신 CSS는 네이티브 중첩을 지원합니다(Sass/Less 같은). &로 부모 선택자를 참조하세요. 중첩은 가독성을 향상하고 반복을 줄입니다. media query를 규칙 내부에 직접 중첩할 수 있습니다. 최신 브라우저(2023+)에서 브라우저 지원이 좋습니다. &는 의사 클래스(&:hover)와 결합자(& > .child)에 필수입니다. 특이성을 높이고 유지보수성을 줄이므로 깊은 중첩(3+ 수준)을 피하세요.
/* Native CSS nesting (no preprocessor needed) */
.card {
background: white;
border-radius: 8px;
& .title {
font-size: 1.5rem;
font-weight: bold;
}
& .body {
padding: 1rem;
& p {
line-height: 1.6;
}
}
&:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
@media (max-width: 768px) {
padding: 0.5rem;
}
}CSS 사용자 정의 속성 (변수) 심층
변수 정의 & 사용
CSS 사용자 정의 속성(변수)은 --name으로 정의되고 var()로 사용됩니다. 테마 값을 위해 :root에 전역 변수를 정의하세요. Sass 변수와 달리 CSS 변수는 라이브입니다 - 변경하면 모든 사용이 즉시 업데이트됩니다. 정의된 요소에 범위가 지정되고 자손에 의해 상속됩니다. var()는 두 번째 인수로 폴백 값을 받고, 폴백은 연결될 수 있습니다. 이는 동적 테마에 대해 전처리기 변수보다 훨씬 더 강력하게 만듭니다.
:root {
--primary: #3498db;
--spacing: 16px;
--radius: 8px;
--max-width: 1200px;
}
.button {
background: var(--primary);
padding: var(--spacing);
border-radius: var(--radius);
max-width: var(--max-width);
}
/* Fallback values */
color: var(--text-color, #333);
/* Chained fallbacks */
color: var(--text, var(--default, black));JavaScript로 동적 테마
CSS 변수는 전처리기가 할 수 없는 런타임 테마를 가능하게 합니다. <html>에 data-theme를 설정하고 테마별로 변수를 재정의하세요. JavaScript는 런타임에 변수를 읽고(getComputedStyle) 설정(style.setProperty)하여 동적 색상 픽커, 사용자 기본 설정, 실시간 미리보기를 가능하게 합니다. 이것이 다크 모드, 브랜드 사용자 정의, 사용자 선택 가능 테마의 표준 접근 방식입니다. 변수는 캐스케이드되고 상속되므로 자식 요소에서 재정의하면 해당 하위 트리만 영향을 받습니다.
/* Define theme variables */
:root {
--bg: #ffffff;
--text: #333333;
}
[data-theme="dark"] {
--bg: #1a1a1a;
--text: #e0e0e0;
}
body {
background: var(--bg);
color: var(--text);
}
/* Toggle theme with JS */
<script>
document.documentElement.setAttribute(
"data-theme",
isDark ? "dark" : "light"
);
/* Or set a single variable directly */
document.documentElement.style
.setProperty("--primary", "#e74c3c");
</script>범위 변수 & 상속
CSS 변수는 다른 속성처럼 상속됩니다. :root에 정의된 변수는 어디서나 사용 가능하고, .card에 정의된 것은 .card와 그 자손에만 영향을 줍니다. 이 범위 지정은 컴포넌트 수준 사용자 정의를 가능하게 합니다. 반응형 변수 값을 만들기 위해 media query에서 변수를 재정의할 수 있습니다 - 변수 자체는 @media 조건에서 사용될 수 없지만, media query 블록 내에서 값을 변경할 수 있습니다. 이 패턴은 속성 선언 을 반복하지 않고 반응형 디자인에 강력합니다.
:root {
--padding: 20px;
}
.card {
--padding: 12px; /* overrides only for .card subtree */
padding: var(--padding);
}
.card .inner {
padding: var(--padding); /* inherits 12px from .card */
}
/* Variables in media queries don't work, but... */
.sidebar {
--width: 250px;
width: var(--width);
}
@media (max-width: 768px) {
.sidebar { --width: 100%; }
}calc()와 변수
CSS 변수를 calc()와 결합하면 강력한 디자인 시스템을 만듭니다. 기본 크기와 배율을 정의하고 파생 크기를 계산하세요. 이는 일관된 타이포그래피 스케일과 간격 시스템을 가능하게 합니다. calc()는 혼합 단위(px, %, em, vw)와 변수와 작동합니다. 폴백 없이 변수를 사용할 수도 있습니다(var(--nav-h, 60px)가 기본값 제공). 이 접근 방식은 최신 디자인 시스템의 기반입니다 - 하나의 변수를 변경하면 전체 스케일이 비례적으로 조정됩니다.
:root {
--base-size: 16px;
--scale: 1.25;
}
h1 { font-size: calc(var(--base-size) * var(--scale) * 2); }
h2 { font-size: calc(var(--base-size) * var(--scale) * 1.5); }
p { font-size: var(--base-size); }
/* Spacing scale */
:root {
--space: 8px;
}
.margin { margin: calc(var(--space) * 2); }
.padding { padding: calc(var(--space) * 3); }
/* Mixed units */
.header { height: calc(var(--nav-h, 60px) + var(--content-h)); }사용자 정의 속성 등록 (@property)
@property는 타입(syntax), 초기 값, 상속 플래그로 사용자 정의 속성을 등록합니다. 이는 CSS 변수를 애니메이션하고 전환할 수 있게 합니다 - @property 없이 변수는 문자열로 처리되어 보간할 수 없습니다. syntax는 <angle>, <color>, <length>, <number>, <percentage>, 또는 '*' 같은 타입을 받습니다. 이는 변수로 구동되는 그라데이션, transform, 색상의 부드러운 애니메이션을 가능하게 합니다. 브라우저 지원은 Chromium과 Safari에서 좋고, Firefox가 지원을 추가하고 있습니다. CSS 애니메이션 기능의 주요 발전입니다.
/* Type-safe custom properties */
@property --angle {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
@property --color {
syntax: "<color>";
initial-value: #3498db;
inherits: false;
}
/* Now animatable! */
@keyframes spin {
to { --angle: 360deg; }
}
.loader {
--angle: 0deg;
animation: spin 1s linear infinite;
background: conic-gradient(
from var(--angle),
var(--color), transparent
);
}3D Transform
3D Transform 함수
3D transform은 요소에 깊이를 추가합니다. transform 함수로서 perspective()는 단일 요소에 적용되고, 부모의 perspective 속성은 모든 자식에게 균일하게 적용됩니다. rotateX/rotateY/rotateZ는 축을 중심으로 회전하고, translateZ는 Z축을 따라 이동합니다(뷰어를 향해/멀어짐). 낮은 perspective 값은 더 극적인 3D 효과를 만듭니다(가까운 시점). 여러 transform된 자식에 걸쳐 일관된 3D 공간을 위해 항상 부모 컨테이너에 perspective를 설정하세요. 복잡한 3D 포지셔닝을 위해 여러 transform을 결합하세요.
.card {
transform: perspective(1000px) rotateY(45deg) rotateX(15deg);
}
/* Using the perspective property instead */
.container {
perspective: 1000px;
}
.container .card {
transform: rotateY(45deg) rotateX(15deg);
}
/* translateZ moves toward/away from viewer */
.layer {
transform: translateZ(100px);
}
/* scale3d for 3D scaling */
.box {
transform: scale3d(1.5, 1.5, 1.5);
}transform-style & backface-visibility
transform-style: preserve-3d는 자식 요소를 위해 3D 공간을 유지합니다(flat이 기본값, 자식을 평면화). backface-visibility: hidden은 회전 시 요소의 뒷면을 숨깁니다 - 플립 카드 효과에 필수적. 고전적인 플립 카드는 두 개의 absolute로 배치된 면을 사용합니다: 앞면은 앞을 향하고, 뒷면은 미리 180deg 회전됩니다. hover 시 부모가 180deg 회전하여 어떤 면이 보이는지 교체합니다. 이는 가장 인기 있는 3D CSS 패턴 중 하나입니다.
.container {
perspective: 1000px;
}
.flip-card {
transform-style: preserve-3d;
transition: transform 0.6s;
position: relative;
}
.flip-card:hover {
transform: rotateY(180deg);
}
.flip-card .front,
.flip-card .back {
position: absolute;
inset: 0;
backface-visibility: hidden;
}
.flip-card .back {
transform: rotateY(180deg);
}3D 큐브
3D 큐브는 각각 rotate + translateZ로 배치된 6개의 면으로 구축됩니다. translateZ(100px)는 면을 큐브 너비의 절반 바깥으로 밉니다. 각 면은 밀려나기 전에 해당 방향을 향하도록 미리 회전됩니다. 큐브 컨테이너의 transform-style: preserve-3d가 필수적입니다 - 없으면 면이 평면화됩니다. 애니메이션은 큐브를 X와 Y축 모두에서 회전시킵니다. 이는 CSS 3D transform의 모든 힘을 보여줍니다. 적절한 기하학을 위해 큐브 크기의 절반과 일치하도록 translateZ를 조정하세요.
.scene {
perspective: 800px;
width: 200px; height: 200px;
}
.cube {
position: relative;
width: 100%; height: 100%;
transform-style: preserve-3d;
animation: rotate 8s infinite linear;
}
@keyframes rotate {
from { transform: rotateX(0) rotateY(0); }
to { transform: rotateX(360deg) rotateY(360deg); }
}
.face {
position: absolute;
width: 200px; height: 200px;
opacity: 0.85;
}
.front { transform: translateZ(100px); background: red; }
.back { transform: rotateY(180deg) translateZ(100px); background: blue; }
.right { transform: rotateY(90deg) translateZ(100px); background: green; }
.left { transform: rotateY(-90deg) translateZ(100px); background: yellow; }
.top { transform: rotateX(90deg) translateZ(100px); background: purple; }
.bottom { transform: rotateX(-90deg) translateZ(100px); background: orange; }3D 카드 틸트 효과
카드 틸트 효과는 마우스 위치를 추적하고 커서 오프셋을 기반으로 3D에서 카드를 회전시킵니다. 수학은 오프셋을 인수(10)로 나누어 회전 각도를 제한합니다. mouseleave 시 카드가 평면으로 재설정됩니다. transition이 부드러운 움직임을 만듭니다. 마우스를 따라가는 방사형 그라데이션으로 glare/shine 오버레이를 추가하면 효과가 향상됩니다. 이는 제품 카드와 히어로 섹션에 인기 있는 대화형 UI 패턴입니다. transform 문자열의 perspective는 부모 perspective 속성 없이도 3D 렌더링을 보장합니다.
.tilt-card {
transition: transform 0.1s ease-out;
transform-style: preserve-3d;
}
/* JavaScript-driven tilt */
<script>
const card = document.querySelector(".tilt-card");
card.addEventListener("mousemove", (e) => {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const rotateX = (y - centerY) / 10;
const rotateY = (centerX - x) / 10;
card.style.transform =
`perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
});
card.addEventListener("mouseleave", () => {
card.style.transform = "perspective(1000px) rotateX(0) rotateY(0)";
});
</script>perspective-origin & 3D 간격
perspective-origin은 소멸점 위치를 설정합니다(머리를 움직이는 것 같은). 기본값은 50% 50%(중심)입니다. 이동하면 동적인 시청 각도를 만듭니다. 패럴랙스 효과의 경우, 다른 translateZ 값의 요소가 컨테이너 스크롤이나 회전 시 다른 속도로 이동합니다. 더 뒤에 있는 요소(음수 translateZ)는 perspective 수축을 보상하기 위해 scale()이 필요합니다. 이 기법은 JavaScript 없이 UI에 깊이를 만듭니다 - 순수 CSS 패럴랙스. 스크롤 구동 애니메이션과 결합하면 몰입형 스크롤 경험을 만듭니다.
.stage {
perspective: 1200px;
perspective-origin: 50% 30%; /* x y position of vanishing point */
transform-style: preserve-3d;
}
.layer-1 { transform: translateZ(0); }
.layer-2 { transform: translateZ(50px); }
.layer-3 { transform: translateZ(100px); }
/* Parallax-like depth */
.depth-bg { transform: translateZ(-200px) scale(1.2); }
.depth-mid { transform: translateZ(-100px) scale(1.1); }
.depth-fg { transform: translateZ(0); }CSS 필터 & Backdrop 필터
필터 함수
CSS 필터는 요소에 시각적 효과를 적용합니다. grayscale, sepia, invert는 색상을 변경하고, blur는 부드럽게 하고, brightness/contrast/saturate는 강도를 조정하고, hue-rotate는 색상을 이동시킵니다. 여러 필터가 좌에서 우로 연결됩니다. drop-shadow는 알파 모양을 따르므로(투명한 영역이 그림자가 되지 않음) PNG/SVG 이미지에 대해 box-shadow보다 우수합니다. 필터는 GPU 가속되고 성능이 좋습니다. 일반적인 사용: 이미지 갤러리 hover 시 grayscale, 빈티지 사진 효과, 접근성(고대비 모드).
img.grayscale { filter: grayscale(100%); }
img.blur { filter: blur(5px); }
img.bright { filter: brightness(1.5); }
img.contrast { filter: contrast(200%); }
img.sepia { filter: sepia(80%); }
img.saturate { filter: saturate(2); }
img.hue { filter: hue-rotate(90deg); }
img.invert { filter: invert(100%); }
/* Combine multiple filters */
img.vintage {
filter: sepia(50%) contrast(110%) brightness(90%) saturate(120%);
}
/* Drop shadow (follows alpha shape) */
img.png-shadow {
filter: drop-shadow(2px 4px 6px rgba(0,0,0,0.4));
}backdrop-filter (Glassmorphism)
backdrop-filter는 요소 자체가 아닌 요소 뒤 영역에 필터를 적용하여 glassmorphism 효과를 만듭니다. 효과가 보이려면 요소가 반투명 배경이 필요합니다. blur가 서리 유리를 위한 가장 일반적인 backdrop 필터입니다. Safari 지원을 위해 항상 -webkit- 접두사를 포함하세요. 세련된 유리 모양을 위해 반투명 테두리과 미묘한 box-shadow와 결합하세요. 큰 블러 영역으로 성능 문제가 있을 수 있습니다 - 드물게 사용하세요. 이는 최신 UI 디자인의 정의 트렌드입니다(iOS, macOS, Windows Acrylic).
.glass {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px) saturate(180%);
-webkit-backdrop-filter: blur(10px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 16px;
padding: 24px;
}
.glass-dark {
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
}SVG 필터 (url 참조)
CSS filter: url(#id)는 내장 함수 이상의 효과를 위해 SVG 필터를 참조합니다. 'gooey' 필터는 요소의 블롭 같은 병합을 만듭니다(로딩 애니메이션과 유기적 UI에 인기). feGaussianBlur + 높은 alpha 승수의 feColorMatrix가 흐려진 가장자리를 날카롭게 하여 gooey 효과를 만듭니다. feTurbulence + feDisplacementMap은 물결/왜곡 효과를 만듭니다. SVG 필터는 강력하지만 성능 집약적일 수 있습니다. CSS만으로는 불가능한 효과를 가능하게 합니다: 액체 변형, 변위, 조명, 사용자 정의 합성.
<!-- Define SVG filter -->
<svg style="display:none">
<filter id="gooey">
<feGaussianBlur in="SourceGraphic" stdDeviation="10" />
<feColorMatrix values="
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 20 -10" />
</filter>
</svg>
<!-- Apply via CSS -->
.gooey-container {
filter: url(#gooey);
}
<!-- Displacement map for wavy text -->
<filter id="wavy">
<feTurbulence baseFrequency="0.02" numOctaves="3" />
<feDisplacementMap in="SourceGraphic" scale="10" />
</filter>필터 애니메이션
필터는 부드럽게 애니메이션되고 전환될 수 있습니다. pulse-glow 효과는 neon glow를 위해 brightness와 drop-shadow를 결합합니다. hover 시 grayscale-to-color는 고전적인 갤러리 상호작용입니다. 필터 전환은 GPU 가속되고 box-shadow나 background-color를 애니메이션하는 것보다 성능이 좋습니다. 하지만 blur나 복잡한 필터를 애니메이션하는 것은 비용이 많이 들 수 있습니다 - 모바일에서 테스트하세요. 스크롤 구동 hue-rotate는 재미있는 효과지만 긴 페이지에서 성능 문제를 일으킬 수 있습니다; 최적화를 위해 브라우저에 힌트를 주기 위해 will-change: filter를 사용하세요.
@keyframes pulse-glow {
0%, 100% {
filter: brightness(1) drop-shadow(0 0 5px #3498db);
}
50% {
filter: brightness(1.3) drop-shadow(0 0 20px #3498db);
}
}
.glow-button {
animation: pulse-glow 2s ease-in-out infinite;
}
/* Hover filter transition */
.gallery img {
filter: grayscale(100%);
transition: filter 0.3s ease;
}
.gallery img:hover {
filter: grayscale(0%);
}
/* Color shift on scroll (with JS) */
window.addEventListener("scroll", () => {
const hue = window.scrollY * 0.5;
document.body.style.filter = `hue-rotate(${hue}deg)`;
});mix-blend-mode & background-blend-mode
mix-blend-mode는 요소의 픽셀이 뒤에 있는 콘텐츠와 어떻게 혼합되는지 결정합니다. multiply는 어둡게 하고(그림자와 워터마크에 좋음), screen은 밝게 하고(glow에 좋음), difference는 사이키델릭 효과를 만들고, overlay는 대비를 위해 multiply와 screen을 결합합니다. background-blend-mode는 같은 요소에서 여러 background 레이어(이미지와 그라데이션)를 혼합합니다. 블렌드 모드는 크리에이티브 합성, 듀오톤 효과, 이미지 위 텍스트 오버레이에 필수적입니다. isolation: isolate는 블렌드 효과를 포함하기 위해 새로운 블렌딩 컨텍스트를 만듭니다.
/* mix-blend-mode: how element blends with backdrop */
.overlay {
mix-blend-mode: multiply; /* darken */
}
.overlay-screen {
mix-blend-mode: screen; /* lighten */
}
.overlay-difference {
mix-blend-mode: difference; /* invert */
}
/* Common blend modes: multiply, screen, overlay,
darken, lighten, color-dodge, color-burn,
hard-light, soft-light, difference, exclusion,
hue, saturation, color, luminosity */
/* background-blend-mode: blend background layers */
.gradient-texture {
background-image: url(texture.png),
linear-gradient(45deg, red, blue);
background-blend-mode: multiply;
}컨테이너 쿼리
컨테이너 쿼리 기본
컨테이너 쿼리를 사용하면 컴포넌트가 viewport가 아닌 컨테이너의 크기에 응답할 수 있습니다. container-type: inline-size는 요소를 inline(너비) 차원을 기반으로 쿼리 컨테이너로 만듭니다. @container 규칙은 컨테이너가 조건을 만족할 때 스타일을 적용합니다. 이는 컴포넌트 기반 디자인에 혁명적입니다 - 카드 컴포넌트가 화면 크기와 관계없이 사이드바나 전체 너비 메인 영역에 있는지 적응합니다. 이는 viewport 크기에만 응답하는 media query의 근본적 한계를 해결합니다.
/* Define a container */
.card-container {
container-type: inline-size;
/* or: container-type: size; (both dimensions) */
}
/* Query the container's size */
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
@container (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
}명명된 컨테이너
명명된 컨테이너를 사용하면 여러 컨테이너가 중첩될 때 특정 컨테이너를 대상으로 할 수 있습니다. container-name이 컨테이너에 레이블을 제공하고, @container name (condition)이 해당 컨테이너만 쿼리합니다. 이는 컴포넌트가 다른 컨테이너 컨텍스트와 중첩될 때 충돌을 방지합니다. container 축약은 name과 type을 결합합니다: container: panel / inline-size. 명명된 컨테이너는 여러 독립적인 컨테이너 쿼리가 공존하는 복잡한 레이아웃에 필수적입니다. 이름 없이 @container는 가장 가까운 조상 컨테이너를 쿼리합니다.
/* Name a container for specific queries */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
.main-content {
container-type: inline-size;
container-name: main;
}
/* Query specific container by name */
@container sidebar (min-width: 300px) {
.widget { display: grid; grid-template-columns: 1fr 1fr; }
}
@container main (min-width: 600px) {
.article { columns: 2; }
}
/* Shorthand: container: name / type */
.panel {
container: panel / inline-size;
}컨테이너 쿼리 단위
컨테이너 쿼리 단위(cqw, cqh, cqmin, cqmax)는 viewport 단위(vw, vh) 같지만 viewport가 아닌 쿼리 컨테이너에 상대적입니다. 1cqw = 컨테이너 너비의 1%. 이는 진정으로 컴포넌트 반응형 타이포그래피와 간격을 가능하게 합니다 - 텍스트가 화면이 아닌 컴포넌트의 너비에 따라 확장됩니다. clamp()와 결합하면 min/max 경계로 컨테이너 크기에 적응하는 유동 타이포그래피를 얻습니다. 이는 컴포넌트가 다양한 레이아웃 컨텍스트에서 작동해야 하는 디자인 시스템에 이상적입니다. 참고: cqh/cqmin/cqmax는 container-type: size(양 차원)가 필요합니다.
.card {
container-type: inline-size;
}
.card-title {
/* cqw: 1% of container width */
font-size: 5cqw;
/* cqh: 1% of container height (needs size type) */
padding: 2cqh;
/* cqmin: 1% of smaller container dimension */
margin: 2cqmin;
/* cqmax: 1% of larger container dimension */
border-radius: 1cqmax;
}
/* Fluid typography that responds to container */
.headline {
font-size: clamp(1rem, 8cqw, 3rem);
}컨테이너 스타일 쿼리
컨테이너 스타일 쿼리(실험적, 2025년 기준 Chromium 전용)를 사용하면 차원뿐만 아니라 컨테이너의 사용자 정의 속성 값을 쿼리할 수 있습니다. 이는 스타일 기반 조건부 렌더링을 가능하게 합니다: 컴포넌트가 컨테이너에 설정된 --theme 변수를 기반으로 모양을 변경할 수 있습니다. container-type: style(또는 스타일 쿼리에 container-type 불필요)이 이를 가능하게 합니다. 이는 테마에 강력합니다 - 부모에 변수를 설정하면 모든 자식이 적응합니다. 전체 브라우저 지원은 아직 보류 중입니다; progressive enhancement나 @supports 검사와 함께 사용하세요.
/* Query custom property values on container */
.card-wrapper {
container-type: style;
container-name: card;
}
@container card style(--theme: dark) {
.card {
background: #1a1a1a;
color: #fff;
}
}
@container card style(--theme: light) {
.card {
background: #fff;
color: #333;
}
}
/* Set the theme variable */
<div class="card-wrapper" style="--theme: dark">
<div class="card">...</div>
</div>반응형 컴포넌트 패턴
이 패턴은 배치에 적응하는 진정으로 재사용 가능한 컴포넌트를 만듭니다. 제품 카드는 좁은 컨테이너에서는 세로 레이아웃, 중간에서는 가로, 넓은 컨테이너에서는 3열 grid를 표시합니다. 같은 컴포넌트가 사이드바(좁음), grid(중간), 또는 히어로(넓음)에서 prop 기반 조건부 로직 없이 작동합니다. 이것이 컨테이너 쿼리의 킬러 사용 사례입니다 - 진정으로 컨텍스트 독립적인 컴포넌트. 타이포그래피를 위한 컨테이너 쿼리 단위와 결합하면 완전히 자가 적응하는 컴포넌트를 얻습니다.
/* Component that adapts to any container */
.product-card {
container-type: inline-size;
container-name: product;
}
.product-card .layout {
display: flex;
flex-direction: column;
gap: 1rem;
}
/* Medium container: horizontal layout */
@container product (min-width: 350px) {
.product-card .layout {
flex-direction: row;
}
.product-card .image {
width: 40%;
}
}
/* Large container: full feature layout */
@container product (min-width: 600px) {
.product-card .layout {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
}
.product-card .actions {
display: flex;
flex-direction: column;
}
}Scroll Snap & 스크롤 구동 애니메이션
Scroll Snap 기본
scroll snap은 요소를 snap 지점에 정렬하는 자기 스크롤을 만듭니다. scroll-snap-type: x/y가 축을 설정하고, mandatory는 강제 snap(항상 지점에 착지), proximity는 가까울 때만 snap합니다. scroll-snap-align: start/center/end가 컨테이너 내에서 자식이 snap하는 위치를 정의합니다. 전체 페이지 스크롤 섹션(프레젠테이션 슬라이드 같은)은 100vh 섹션과 y mandatory를 사용합니다. 수평 캐러셀은 x mandatory를 사용합니다. 항상 터치 기기에서 테스트하세요 - mandatory snap은 콘텐츠가 viewport보다 길면 제한적으로 느껴질 수 있습니다. 더 관대한 동작을 위해 proximity를 사용하세요.
/* Container with scroll snapping */
.gallery {
scroll-snap-type: x mandatory;
overflow-x: auto;
display: flex;
gap: 1rem;
}
/* Children snap to positions */
.gallery section {
scroll-snap-align: start;
flex: 0 0 100%;
height: 300px;
}
/* Vertical snapping */
.full-page {
scroll-snap-type: y mandatory;
height: 100vh;
overflow-y: scroll;
}
.full-page section {
scroll-snap-align: start;
height: 100vh;
}Padding과 Scroll Snap
scroll-padding이 snap 지점을 컨테이너 가장자리에서 오프셋합니다 - 고정 헤더가 있거나 snap된 항목 주위에 여백이 필요할 때 유용합니다. scroll-snap-stop: always는 빠른 스크롤이 여러 항목을 건너뛰는 것을 방지합니다(한 번에 하나씩 snap 강제). scroll-snap-align: center는 항목을 컨테이너 중심에 snap하여 cover-flow 효과를 만듭니다. 이 속성들은 snap 동작에 미세 제어를 제공합니다. 이미지 캐러셀의 경우 snap 간 애니메이션 전환을 위해 scroll-behavior: smooth와 결합하세요.
.carousel {
scroll-snap-type: x mandatory;
scroll-padding: 0 20px; /* offset snap points */
overflow-x: auto;
display: flex;
gap: 1rem;
padding: 0 20px;
}
.carousel .item {
scroll-snap-align: start;
scroll-snap-stop: always; /* don't skip items */
flex: 0 0 80%;
}
/* Center-aligned snapping */
.carousel-center .item {
scroll-snap-align: center;
flex: 0 0 60%;
}스크롤 구동 애니메이션 (animation-timeline)
스크롤 구동 애니메이션(CSS 전용, JS 불필요!)이 애니메이션을 스크롤 위치에 연결합니다. animation-timeline: scroll()이 애니메이션을 페이지 스크롤 진행에 연결합니다(상단 0%, 하단 100%). animation-timeline: view()가 요소가 viewport에 들어오고 나가는 것에 연결합니다. animation-range가 스크롤에 상대적으로 애니메이션이 시작/종료되는 시점을 정의합니다(entry, exit, cover, contain). 이는 JavaScript나 scroll 이벤트 리스너 없이 스크롤 진행 바, reveal-on-scroll, 패럴랙스 효과를 가능하게 합니다. Chrome 115+에서 지원, 다른 브라우저를 위해 progressive enhancement 권장.
/* Animate as user scrolls */
@keyframes fade-in {
from { opacity: 0; transform: translateY(50px); }
to { opacity: 1; transform: translateY(0); }
}
.reveal {
animation: fade-in linear;
animation-timeline: view();
animation-range: entry 0% cover 40%;
}
/* Progress bar based on scroll position */
.progress-bar {
position: fixed;
top: 0; left: 0;
height: 4px;
background: #3498db;
width: 100%;
transform-origin: left;
animation: grow linear;
animation-timeline: scroll();
}
@keyframes grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}View Timeline 범위
View timeline 범위는 애니메이션이 정확히 언제 발생하는지 제어합니다. 'entry'는 요소가 viewport에 들어올 때, 'exit'는 나갈 때, 'cover'는 entry 시작에서 exit 끝까지, 'contain'은 요소가 완전히 보일 때입니다. 백분율이 각 범위 내에서 미세 조정합니다. animation-range: entry 10% entry 90%는 애니메이션이 entry의 10%에서 entry의 90%까지 실행됨을 의미합니다. sticky 헤더의 경우 픽셀 범위(0 200px)로 scroll()이 절대 스크롤 거리를 기반으로 애니메이션합니다. 이는 복잡한 JavaScript scroll 핸들러를 선언적 CSS로 대체합니다.
/* Element animates as it enters viewport */
@keyframes slide-up {
from { opacity: 0; transform: translateY(100px); }
to { opacity: 1; transform: translateY(0); }
}
.section {
animation: slide-up linear both;
animation-timeline: view();
/* Ranges: entry, exit, cover, contain */
animation-range: entry 10% entry 90%;
}
/* Sticky header that shrinks on scroll */
.header {
position: sticky;
top: 0;
animation: shrink linear both;
animation-timeline: scroll();
animation-range: 0 200px;
}
@keyframes shrink {
to { padding: 0.5rem 1rem; font-size: 0.9em; }
}scroll-behavior & 부드러운 스크롤
html(또는 모든 scroll 컨테이너)의 scroll-behavior: smooth는 앵커 링크 내비게이션과 scrollIntoView()가 점프 대신 부드럽게 애니메이션되게 합니다. 이는 JavaScript smooth-scroll 라이브러리의 한 줄 대체입니다. 항상 prefers-reduced-motion을 존중하세요 - 일부 사용자는 멀미를 경험하므로 부드러운 스크롤을 비활성화하세요. scroll-snap-type: proximity(vs mandatory)는 자연스러운 느낌을 위해 부드러운 스크롤과 잘 짝합니다. block: 'start'/'center'/'end'와 scrollIntoView가 대상의 수직 정렬을 제어합니다.
/* Smooth scrolling for anchor links */
html {
scroll-behavior: smooth;
}
/* With scroll snap */
html {
scroll-behavior: smooth;
scroll-snap-type: y proximity;
}
/* Target specific elements */
.container {
scroll-behavior: smooth;
overflow-y: auto;
}
/* JavaScript: scroll to element */
<script>
document.querySelector(".section-3")
.scrollIntoView({ behavior: "smooth", block: "start" });
/* Scroll to top */
window.scrollTo({ top: 0, behavior: "smooth" });
</script>
/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
}CSS 카운터
기본 카운터
CSS 카운터는 JavaScript 없이 요소를 자동 번호 매깁니다. counter-reset이 카운터를 초기화합니다(부모나 조상에서). counter-increment가 증가시킵니다(일반적으로 번호 매겨지는 요소에서). counter(name)가 content에 현재 값을 표시합니다. 카운터는 문서 전체에 걸쳐 번호 매김을 자동으로 처리합니다. 이는 제목, 리스트 항목, 그림, 또는 모든 순차적 콘텐츠를 번호 매기에 이상적입니다. 카운터는 재설정된 요소와 그 자손에 범위가 지정됩니다.
/* Initialize counter on parent */
body {
counter-reset: section;
}
/* Increment and display */
h2::before {
counter-increment: section;
content: "Section " counter(section) ": ";
color: #3498db;
font-weight: bold;
}
/* Result: Section 1: Introduction, Section 2: Methods... */중첩 카운터
중첩 카운터는 계층적 번호 매김(1.1, 1.2, 2.1 등)을 만듭니다. 부모 제목에서 하위 카운 터를 재설정하세요(h2에 counter-reset: section). 각 h2가 새 장을 시작하고 section 카운터를 재설정합니다. 각 h3이 현재 장 내의 section 카운터를 증가시킵니다. content 속성이 구분자와 여러 counter() 호출을 결합합니다. 이는 책과 기술 문서가 번호 매겨지는 방식을 반영합니다. 카운터는 DOM 구조와 재설정 지점을 기반으로 자연스럽게 캐스케이드됩니다.
body {
counter-reset: chapter;
}
h2 {
counter-reset: section;
counter-increment: chapter;
}
h2::before {
content: "Chapter " counter(chapter) ". ";
}
h3 {
counter-increment: section;
}
h3::before {
content: counter(chapter) "." counter(section) " ";
}
/* Result: Chapter 1. / 1.1 / 1.2 / Chapter 2. / 2.1 ... */카운터 스타일
counter()는 스타일 인수를 받습니다: decimal(기본값), decimal-leading-zero(01, 02), lower/upper-alpha(a/b 또는 A/B), lower/upper-roman(i/ii 또는 I/II). @counter-style이 순환 기호, 가법 시스템, 또는 기호 표기로 사용자 정의 카운터 스타일을 정의합니다. system: cyclic이 기호를 반복하고, system: additive가 로마 숫자 같은 시스템을 만듭니다. 사용자 정의 카운터 스타일은 국제화된 리스트, 사용자 정의 글머리, 또는 장식적 번호 매김에 강력합니다. @counter-style의 브라우저 지원은 최신 브라우저 에서 좋습니다.
/* Different number styles */
ol {
counter-reset: item;
list-style: none;
}
li::before {
counter-increment: item;
/* decimal, decimal-leading-zero, lower-alpha,
upper-alpha, lower-roman, upper-roman */
content: counter(item, upper-roman) ". ";
}
/* Leading zeros: 01, 02, 03... */
li::before {
content: counter(item, decimal-leading-zero) ". ";
}
/* Custom @counter-style */
@counter-style thumbs {
system: cyclic;
symbols: "👍" "👎";
suffix: " ";
}
.thumbs-list { list-style-type: thumbs; }counters() 함수 (중첩)
counters() 함수(복수)는 지정된 구분자로 수준이 구분된 전체 카운터 경로를 문자열로 반환합니다. 이는 중첩된 카운터 범위를 자동으로 처리합니다 - 각 중첩된 ol이 새 카운터 범위를 만듭니다. counters(nested, '.')은 깊게 중첩된 리스트에 대해 1, 1.1, 1.1.1을 생성합니다. 이는 현재 수준만 표시하는 counter()와 다릅니다. 중첩된 리스트, 목차, 또는 계층적 메뉴의 아웃라인 스타일 번호 매김에 counters()를 사용하세요. 구분자 문자는 어떤 문자든 될 수 있습니다(. , - > 등).
/* counters() creates full path string */
ol {
counter-reset: nested;
list-style: none;
}
li::before {
counter-increment: nested;
/* counters() returns all levels separated by a string */
content: counters(nested, ".") " - ";
font-weight: bold;
}
/* For nested <ol> elements:
1 - Top level
1.1 - Nested
1.1.1 - Deeper
2 - Top level again
*/그림 & 표를 위한 카운터
카운터는 문서에서 그림, 표, 방정식의 자동 번호 매김에 완벽합니다. 각 장마다 번호 매김이 재시작되도록 장별로 재설정하세요(h2에 counter-reset). figcaption/caption의 ::before 의사 요소가 번호를 앞에 붙입니다. 이는 콘텐츠가 재정렬될 때 업데이트되는 일관된 자동 번호 매김을 보장합니다. 상호 참조('Figure 3 참조')의 경우, CSS 카운터는 직접 도울 수 없습니다 - JS나 HTML 앵커가 필요합니다. 하지만 표시 번호 매김의 경우, 이는 깔끔하고 유지보수가 필요 없는 솔루션입니다.
body {
counter-reset: figure table;
}
figure figcaption::before {
counter-increment: figure;
content: "Figure " counter(figure) ": ";
font-weight: bold;
color: #2c3e50;
}
table caption::before {
counter-increment: table;
content: "Table " counter(table) ": ";
font-weight: bold;
color: #2c3e50;
}
/* Reset figure counter per chapter */
h2 {
counter-reset: figure;
}인쇄 스타일
@media print 기본
@media print는 인쇄 시에만 스타일을 적용합니다. 내비게이션, 광고, 사이드바, 용지에 의미 없는 대화형 요소를 숨기세요. 인라인 스타일을 재정의하려면 !important를 사용하세요. print-color-adjust: exact는 브라우저가 배경 색상과 이미지를 인쇄하도록 강제합니다(그렇지 않으면 잉크 절약을 위해 제거됨). 인쇄 font 크기에는 px나 rem이 아닌 pt(포인트)를 사용하세요. 최대 인쇄 가능 영역을 위해 너비를 100%로 설정하고 margin/padding을 제거하세요. 항상 인쇄 미리보기(Ctrl+P)로 테스트하세요.
@media print {
/* Hide non-essential elements */
nav, footer, .sidebar, .ads, .no-print {
display: none !important;
}
/* Ensure colors print */
* {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* Set base font for print */
body {
font-size: 12pt;
color: #000;
background: #fff;
}
/* Expand content to full width */
.content {
width: 100%;
margin: 0;
padding: 0;
}
}페이지 나누기
페이지 나누기 속성은 콘텐츠가 페이지에 걸쳐 분할되는 위치를 제어합니다. break-before: page는 요소 전에 새 페이지를 강제합니다(장에 사용). break-inside: avoid는 표, 그림, 또는 코드 블록이 페이지에 걸쳐 분할되는 것을 방지합니다. break-after: avoid-page는 제목을 다음 콘텐츠와 함께 유지합니다. orphans와 widows는 페이지 경계의 최소 줄 수를 제어합니다(orphans = 하단, widows = 상단). break-* 속성이 최신 표준입니다; page-break-*는 레거시 별칭입니다. 모든 브라우저가 모든 속성을 동등하게 지원하지는 않습니다 - 철저히 테스트하세요.
@media print {
/* Force page break before */
h1, h2 {
page-break-before: always;
break-before: page; /* modern syntax */
}
/* Avoid breaking inside */
table, figure, pre, blockquote {
page-break-inside: avoid;
break-inside: avoid;
}
/* Keep with next (heading stays with content) */
h2, h3, h4 {
page-break-after: avoid;
break-after: avoid-page;
}
/* Avoid orphan/widow lines */
p {
orphans: 3; /* min lines at bottom of page */
widows: 3; /* min lines at top of page */
}
}@page 규칙
@page는 인쇄된 페이지 자체를 구성합니다: size(A4, letter, landscape, 또는 사용자 정의 차원)와 margin. :first, :left, :right 의사 클래스가 특정 페이지를 스타일링합니다 - 제본을 위해 다른 margin으로 책 인쇄에 필수적. 명명된 페이지(@page cover)로 다른 섹션이 다른 페이지 설정을 가질 수 있습니다; 요소에 page 속성으로 할당하세요. 페이지 margin이 인쇄 가능 영역을 만듭니다. 참고: @page 지원은 브라우저마다 다릅니다 - Chrome은 size와 margin을 잘 지원하고, Firefox는 제한적입니다. 전문 인쇄의 경우 Prince XML 같은 전용 도구를 고려하세요.
/* Set page size and margins */
@page {
size: A4; /* or: letter, landscape, 8.5in 11in */
margin: 2cm;
}
/* First page different */
@page :first {
margin-top: 5cm; /* extra space for letterhead */
}
/* Left and right pages (for binding) */
@page :left { margin-left: 3cm; margin-right: 1.5cm; }
@page :right { margin-left: 1.5cm; margin-right: 3cm; }
/* Named pages for different sections */
@page cover {
margin: 0;
}
.cover-page {
page: cover;
}인쇄 친화적 링크
용지 위에서 링크는 URL 없이 쓸모가 없습니다. content의 attr(href) 함수가 각 링크 텍스트 후에 URL을 표시합니다. 내부 앵커 링크(#)와 javascript: 링크를 제외하세요. 마찬가지로 abbr[title]::after가 약어의 전체 확장을 표시합니다. 이는 인쇄된 문서가 화면에서 하이퍼링크가 제공하는 정보를 유지하게 합니다. URL을 본문 텍스트와 구분하기 위해 더 작고 흐린 font를 사용하세요. 긴 URL의 경우 오버플로우를 방지하기 위해 word-break: break-all을 고려하세요.
@media print {
/* Show URL after links */
a[href]::after {
content: " (" attr(href) ")";
font-size: 0.9em;
color: #555;
}
/* Don't show URL for internal links */
a[href^="#"]::after,
a[href^="javascript:"]::after {
content: "";
}
/* Abbreviations: show full title */
abbr[title]::after {
content: " (" attr(title) ")";
}
}인쇄 헤더 & 푸터
@page margin box(@top-center, @bottom-center 등)가 페이지 margin에 콘텐츠를 배치합니다 - 페이지 번호, 헤더, 푸터에 이상적. counter(page)가 현재 페이지이고, counter(pages)가 전체입니다. 하지만 브라우저 지원은 매우 제한적입니다(주로 Prince XML과 WeasyPrint; Chrome/Firefox는 margin box를 지원하지 않음). 폴백은 일부 브라우저에서 매 인쇄 페이지에 반복되는 position: fixed 요소를 사용합니다. 크로스 브라우저 인쇄 헤더/푸터를 위해 서버 측 PDF 생성(Puppeteer, wkhtmltopdf)이 CSS @page보다 더 신뢰할 수 있습니다.
@page {
margin: 2cm 3cm;
/* Page counter in footer */
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
font-size: 10pt;
color: #666;
}
/* Document title in header */
@top-center {
content: "My Document Title";
font-size: 10pt;
color: #666;
}
}
/* Note: @page margin boxes have limited browser
support. Chrome doesn't support them.
Use fixed-position elements as fallback: */
@media print {
.print-footer {
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
font-size: 10pt;
}
}다크 모드 & 색상 구성
prefers-color-scheme
prefers-color-scheme: dark가 사용자의 OS/브라우저 다크 모드 기본 설정을 감지합니다. :root에 라이트 모드를 위한 테마 변수를 정의하고, dark media query에서 재정의하세요. CSS 변수를 사용하면 변수 값만 변경하면 됩니다 - 모든 컴포넌트가 자동으로 업데이트됩니다. 이것이 다크 모드의 표준 접근 방식입니다. media query는 또한 'light'와 'no-preference'를 지원합니다. OS 다크 모드를 토글하거나 Chrome DevTools의 Rendering 탭(Emulate CSS prefers-color-scheme)으로 테스트하세요.
/* Light mode (default) */
:root {
--bg: #ffffff;
--text: #1a1a1a;
--surface: #f5f5f5;
--border: #e0e0e0;
--accent: #3498db;
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a1a;
--text: #e0e0e0;
--surface: #2a2a2a;
--border: #404040;
--accent: #5dade2;
}
}
body {
background: var(--bg);
color: var(--text);
}수동 테마 토글
수동 테마 토글의 경우, 시스템 기본 설정을 재정의하는 <html>의 data-theme 속성을 사용하세요. 사용자의 선택을 localStorage에 저장하여 유지하세요. 페이지 로드 시, 잘못된 테마 플래시(FOUC)를 방지하기 위해 렌더링 전에 localStorage를 확인하세요. color-scheme: light dark는 브라우저에게 네이티브 UI 요소(scrollbar, 폼 컨트롤)를 적절한 구성으로 렌더링하도록 알립니다. 최상의 UX를 위해 저장된 선택이 없을 때 시스템 기본 설정으로 기본 설정하세요: matchMedia('(prefers-color-scheme: dark)')를 폴백으로 확인하세요.
/* Default to system preference, allow override */
:root {
color-scheme: light dark;
}
/* Manual dark mode via data attribute */
[data-theme="dark"] {
--bg: #1a1a1a;
--text: #e0e0e0;
}
[data-theme="light"] {
--bg: #ffffff;
--text: #1a1a1a;
}
/* Toggle button */
<button onclick="toggleTheme()">Toggle Theme</button>
<script>
function toggleTheme() {
const current = document.documentElement.dataset.theme;
const next = current === "dark" ? "light" : "dark";
document.documentElement.dataset.theme = next;
localStorage.setItem("theme", next);
}
// Load saved preference
const saved = localStorage.getItem("theme");
if (saved) document.documentElement.dataset.theme = saved;
</script>color-scheme 속성
color-scheme 속성은 브라우저에게 페이지가 지원하는 색상 구성을 알려주어 네이티브 UI 요소에 영향을 줍니다: scrollbar, 폼 컨트롤(입력, 버튼, 드롭다운), 기본 background/canvas 색상, ::placeholder 색상. 이것이 없으면 폼 컨트롤이 다크 모드에서도 라이트로 나타날 수 있습니다(불협화감). :root에 color-scheme: light dark를 설정하여 네이티브 요소가 자동으로 적응하게 하세요. 이는 다크 모드 세련도를 극적으로 향상하는 한 줄 수정입니다. CSS 변수 테마와 별개입니다 - 브라우저 네이티브 렌더링에만 영향을 줍니다.
:root {
/* Tell browser this page supports both schemes */
color-scheme: light dark;
}
/* Force light only */
.light-only {
color-scheme: light;
}
/* Force dark only */
.dark-only {
color-scheme: dark;
}
/* This affects native UI: scrollbars, form controls,
default colors (canvas, text), and form elements */
input, textarea, select {
/* Will use dark form controls in dark mode */
color-scheme: dark;
}다크 모드 이미지 & 미디어
다크 모드에서 이미지는 특별한 처리가 필요합니다. 흰 배경 다이어그램은 filter: invert(1) hue-rotate(180deg)로 반전될 수 있습니다 - hue-rotate가 색상 왜곡을 방지합니다. 사람 사진은 절대 반전하지 마세요. 눈의 피로를 줄이기 위해 밝기를 약간 낮추세요. 로고와 아이콘의 경우 <picture>와 media-query source로 다크 최적화 버전을 제공하세요. currentColor가 있는 SVG는 자동으로 적응합니다. 배경 이미지의 경우 media query를 통해 다크 모드 대안을 제공하세요. 항상 두 모드에서 이미지의 가독성을 테스트하세요.
@media (prefers-color-scheme: dark) {
/* Invert images that are essentially white-background */
img[src*="logo"],
img[src*="diagram"] {
filter: invert(1) hue-rotate(180deg);
}
/* Reduce brightness of photos */
img.photo {
filter: brightness(0.85);
}
/* Don't invert photos with people */
img[src*="photo"]:not(.invertable) {
filter: none;
}
}
/* Use dark-mode-friendly SVG */
<picture>
<source srcset="logo-dark.svg"
media="(prefers-color-scheme: dark)">
<img src="logo-light.svg" alt="Logo">
</picture>