최신 CSS 기능 및 트렌드
- Things You Can Do With CSS Today — Smashing Magazine
- The Future of CSS: Cascade Layers (CSS @layer)
- The Undeniable Utility Of CSS :has • Josh W. Comeau
- An Interactive Guide to CSS Container Queries
- 컨테이너 쿼리 사용 방법 | web.dev
- What if you used Container Units for everything?
레이아웃과 반응형 디자인
- Fluid Sizing Instead Of Multiple Media Queries? — Smashing Magazine
- Complex conditional width using flex-basis with clamp
- Using grid to split a table cell
- TablesNG — Improvements to
<table>rendering in Chromium
CSS 애니메이션 및 효과
- A CSS-only, animated, wrapping underline.
- Cubic Bézier: from math to motion
- Zero Trickery Custom Radios and Checkboxes - CSS-Tricks
- CSS One-Liners to Improve (Almost) Every Project
- 6 CSS Snippets Every Front-End Developer Should Know In 2025
텍스트 및 타이포그래피
스타일 가이드 및 접근성
- Naming Variables In CSS
- Margin considered harmful
- Defensive CSS - Ahmad Shadeed
- The wasted potential of CSS attribute selectors
CSS와 JavaScript의 조합
- Constructable Stylesheets: seamless reusable styles
- Replace JavaScript Dialogs With New HTML Dialog | CSS-Tricks
- How to prevent scrolling the page on iOS Safari 15
CSS 아키텍처 및 모듈화
background
인라인 요소에 bold 스타일이 적용될 경우 레이아웃 시프팅 현상이 발생하기 때문에 해당 이슈를 해결하는 방법들.
- html - Inline elements shifting when made bold on hover - Stack Overflow1
- Bold on Hover… Without the Layout Shift | CSS-Tricks2
Footnotes
텍스트 ellipsis 처리. 컨텍스트(table-cell, flex, multiline)에 따라 패턴이 다름.
table cell
display: table-cell은 width 제약 없이 content 크기에 맞게 늘어남. overflow: hidden이 동작하려면 명시적 width bound가 필요한데, table 기본 레이아웃이 그것을 허용하지 않음.
/* ❌ 아무 효과 없음 */
td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
해법: table-layout: fixed + max-width: 0.
table {
table-layout: fixed;
width: 100%;
}
col:nth-child(1) { width: 30%; }
col:nth-child(2) { width: 40%; }
col:nth-child(3) { width: 30%; }
td {
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
max-width: 0이 실제 0px이 되는 게 아님. table-layout: fixed 컨텍스트에서 “이 셀은 content 기반으로 너비를 주장하지 않는다”는 시그널로 작동하여 colgroup/th에서 받은 너비 안에서 overflow가 동작. 둘은 반드시 함께 있어야 함.
컬럼별 선택적 적용
td:not(.col-action) {
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
td.col-action {
white-space: nowrap;
}
셀 안에 복합 요소
버튼/배지 등이 텍스트와 함께 있을 때 inner wrapper에 위임.
td {
max-width: 0;
}
td > div {
display: flex;
align-items: center;
gap: 6px;
overflow: hidden;
}
td > div > span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
React 컴포넌트 패턴
TanStack Table size 옵션과 조합.
const columns = [
columnHelper.accessor('name', {
size: 200,
cell: ({ getValue }) => (
<span style={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{getValue()}
</span>
),
}),
]
<table style={{ tableLayout: 'fixed', width: '100%' }}>
<colgroup>
{table.getFlatHeaders().map(header => (
<col key={header.id} style={{ width: header.getSize() }} />
))}
</colgroup>
...
</table>
재사용 셀 래퍼:
function EllipsisCell({ children }: { children: React.ReactNode }) {
return (
<td style={{ maxWidth: 0 }}>
<div style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>
{children}
</div>
</td>
)
}
flexbox / multiline 참고
- Using Flexbox and text ellipsis together · Leonardo Faria1
- Multiline truncated text with “show more” button (with just CSS) - Paul Bakaus’ blog2
- Recreating MDN’s Truncated Text Effect
Footnotes
Building a multi-select component
다중 선택 UI를 구현하기위해서 checkbox, select 두가지 방법으로 작업하는 방식을 소개하고 있다. 그외 선택된 상태값을 얻기위한 counter() 함수, 모바일 체크를 위한 미디어쿼리도 알려주고 있다.
aside {
counter-reset: filters;
& :checked {
counter-increment: filters;
}
&::after {
content: counter(filters);
}
}
@media (pointer: coarse) {
//
}
Under-Engineered Select Menus | Adrian Roselli
font,letter-spacing,word-spacing상속appearance화살표 수정- 상태(focus, required, invalid)에 따른 스타일 추가
::-webkit-input-placeholder /* for (Chrome/Safari/Opera) */
:-ms-input-placeholder /* for IE. */
::-ms-input-placeholder /* for Edge (also supports webkit prefix) */
::-ms-clear {}
::-ms-reveal {}
type Props = {
popover: 'auto' | 'manual'
popovertarget: string
popovertargetaction: 'hide' | 'show' | 'toggle'
}
type State = {
hasBackdrop: boolean
isPopoverOpen: boolean
}
type Methods = {
hidePopover: () => void
showPopover: () => void
togglePopover: () => void
}
type Events = {
beforetoggle: () => void
toggle: () => void
}
class ColorManipulator {
private baseColor: string
private targetColor: string
constructor(baseColor: string, targetColor: string) {
this.baseColor = baseColor
this.targetColor = targetColor
}
private hexToRgb(hex: string) {
const bigint = parseInt(hex.slice(1), 16)
return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 255,
b: bigint & 255,
}
}
public calculateOpacity() {
const target = this.hexToRgb(this.targetColor)
const baseRG = this.hexToRgb(this.baseColor)
const opacities = [
(target.r - baseRG.r) / (255 - baseRG.r),
(target.g - baseRG.g) / (255 - baseRG.g),
(target.b - baseRG.b) / (255 - baseRG.b),
]
const averageOpacity =
opacities.reduce((sum, value) => sum + value, 0) / opacities.length
return averageOpacity
}
public getCssRGBA() {
const opacity = this.calculateOpacity()
return `rgba(0, 0, 0, ${opacity.toFixed(2)})`
}
}
const manipulator = new ColorManipulator('#000000', '#D1D7DE')
const opacity = manipulator.calculateOpacity()
const cssRGBA = manipulator.getCssRGBA()
@value b from "./b.module.css";
.root {
color: aquamarine;
}
.root :global(.b) {
text-decoration: line-through;
}
CSS 모듈에서 변수를 값으로 내보내고 사용하는 방법
- PostCSS와
postcss-modules-values플러그인을 사용하여 CSS 모듈 내에서 변수 값 내보내기 지원 - 색상 변수를 정의하는 파일 생성
- 변수 선언:
@value구문 사용
- 변수 선언:
- 다른 CSS 모듈 파일에서 해당 변수를 가져와서 사용
- 변수 가져오기 및 CSS 클래스에 적용
// 기본
@primary: #3498db;
.button { color: @primary; }
// 보간 (선택자, 속성, URL에서)
@component: 'button';
.@{component} { ... } // .button
background-@{property}: blue; // background-color: blue
url('@{path}/icon.svg')
// 변수를 이용한 변수
@theme: 'primary';
color: @@theme; // @primary 값
// 맵 (LESS 3.5+)
@colors: { primary: #3498db; danger: #e74c3c; };
color: @colors[primary];
// CSS 변수 조합
:root { --primary: @primary; }
.el { color: var(~'--@{prefix}-color'); }
~""이스케이프 (문자열 그대로 출력)@{}보간 (변수를 문자열로 치환)@@var변수를 이용한 변수 참조
로딩 스피너의 깜빡임은 양 끝에서 따로 생기는데 CSS는 한쪽만 잡는다.
- 앞쪽(leading) — 응답이 너무 빨라 스피너가 뜨기 전이나 직후 사라진다. 언제 보이기 시작하나의 문제다. → CSS가 잡는다:
animation-delay/transition-delay가 곧 그 지연이고, 응답이 지연보다 빨리 오면 한 프레임도 안 보인다(스피너를 통째로 건너뛴다). 단 억제력은 지연에서만 나온다 —@starting-style단독(지연 0)으론 못 막는다. - 뒤쪽(trailing) — 스피너가 떴는데 너무 짧게 보이고 하드컷된다. 언제 사라지나의 문제다. 이미 떴다는 사실은 못 되돌리니 CSS가 가진 두 값(지연·지속 시간)으로는 못 잡고 사라지는 시각 자체를 미뤄야 한다 — CSS 밖, JS 타이밍이다. →
useMinimumLoading: unmount를max(응답 시각, 최소 노출 시간)으로 밀어, 한 번 뜬 스피너가 최소 노출 시간만큼은 보이게 만든다.
둘이 갈리는 이유는 하나다 — CSS는 시작만 통제한다. 스피너가 보이는 구간은 [뜬 시각, unmount 시각]인데, 지연은 앞쪽을 미루고 지속 시간은 애니메이션 길이일 뿐 요소가 DOM에 남는 시간이 아니다. 뒤쪽 끝은 응답이 언제 오느냐가 정하고 그건 CSS가 모르는 값이다. 그래서 “최소 몇 ms는 보인다”를 CSS만으로는 보장할 수 없다.
곁가지 — @starting-style은 같은 뿌리의 반대편이다. transition은 두 상태 사이를 보간하는데, 진입에는 옮겨갈 이전 상태가 없고1 퇴장에는 요소 자체가 이미 사라진다. @starting-style은 앞쪽의 출발값을 대신 정해주고(진입 fade-in), useMinimumLoading은 뒤쪽의 unmount를 미룬다. 뿌리는 같아도 손대는 지점이 달라 별개 결정이다.
Footnotes
-
CSS transition은 요소의 첫 스타일 적용이나
display: none→ 표시 전환에서는 기본적으로 발동하지 않는다.@starting-style이 “무엇에서 출발할지”를 정의해 그걸 가능하게 한다. (@starting-style — MDN) ↩
- How to Modify Nodes in an Abstract Syntax Tree | CSS-Tricks1
- AST for JavaScript developers. TL;DR This article is my talk for… | by Bohdan Liashenko | ITNEXT2
- GitHub - NV/CSSOM: Unmaintained! ⚠️ CSS Object Model implemented in pure JavaScript. Also, a CSS parser.3
- GitHub - csstree/csstree: A tool set for CSS including fast detailed parser, walker, generator and lexer based on W3C specs and browser implementations
Footnotes
hash 링크로 연결될 경우 스크롤위치가 최상단으로 위치하기 때문에 문제(헤더가 고정일 경우)가 있을수도 있어서 scroll-margin-top으로 제어가 가능한 부분을 설명하고 있다.
- Add scroll margin to all elements which can be targeted - Piccalilli1
- Fixed Headers and Jump Links? The Solution is scroll-margin-top | CSS-Tricks
- Prevent content from being hidden underneath a fixed header by using scroll-margin-top – Bram.us
Footnotes
-
2ex유닛을 사용하여 선택한 글꼴의 x 높이의 상대적인 크기로 설정. ↩