NDC Oslo 2025 - 아직도 이 CSS를 모르시나요 (모던 CSS 총정리)

NDC Oslo 2025 유튜브 링크

여기 아주 좋은 유튜브 강연이 있는데요, 이 강연의 핵심만 전체적으로 살펴볼까 힙니다.

발표자는 레몬(Lemon)이라는 분인데, CSS에 대한 애정과 유머가 넘치는 아주 유쾌한 개발자더라고요.

이 분의 말을 빌리자면, 우리는 가끔 너무 많은 자바스크립트를 필요로 하지 않을지도 모릅니다.

어쩌면 CSS가 이미 그 해답을 가지고 있을지도 모르죠.

이 강연은 바로 우리가 '어쩌면 모르고 있었을' 강력한 최신 CSS 기능들에 대한 이야기입니다.

CSS 변수, 아직도 낯설다면 주목하세요

발표자는 아주 충격적인 사실과 함께 강연을 시작하는데요.

바로 CSS에 '변수'가 있다는 겁니다.

네, 맞아요.

사실 CSS 변수, 정식 명칭으로는 '사용자 지정 속성(Custom Properties)'은 2018년부터 우리 곁에 있었습니다.

하지만 여전히 많은 분들이 Sass 변수에만 익숙하고, 순수 CSS 변수의 진짜 힘을 잘 활용하지 못하고 있더라고요.

Sass 변수와 CSS 변수의 가장 큰 차이점은 '동작 시점'에 있는데요.

Sass 변수는 컴파일 시점에 일반 값으로 대체되지만, CSS 변수는 브라우저가 렌더링하는 동안에도 살아 숨 쉬는 진짜 변수입니다.

이게 왜 중요하냐면, '문맥에 따라' 변수 값을 바꿀 수 있기 때문이죠.

:root {
  --primary-color: #007bff;
  --text-color: #212529;
}

body.dark-mode {
  --primary-color: #66b0ff;
  --text-color: #f8f9fa;
}

.button {
  background-color: var(--primary-color);
  color: var(--text-color);
}

위 코드를 보세요.

bodydark-mode 클래스가 붙는 순간, --primary-color--text-color 변수의 값이 바뀌는데요.

그러면 .button의 스타일을 전혀 건드리지 않았는데도 버튼의 배경색과 글자색이 알아서 샥 바뀝니다.

이게 바로 CSS 변수의 마법이죠.

발표자는 이걸 활용해서 다크/라이트 모드뿐만 아니라 'warm', 'cool', 'grayscale' 같은 여러 컬러 테마를 아주 적은 양의 코드로 구현하는 멋진 데모를 보여주더라고요.

문법이 --로 시작하고 var() 함수로 호출하는 게 좀 괴상하긴 하지만, 그 강력함은 정말 매력적입니다.

이제 네이티브로 즐기는 CSS 네스팅

Sass를 사용하는 가장 큰 이유 중 하나가 바로 '네스팅(Nesting)'이었는데요.

이제 더 이상 Sass에 의존할 필요가 없어졌습니다.

순수 CSS에서 네스팅을 지원하게 되었거든요.

/* SCSS 방식 */
.card {
  background: white;
  .headline {
    font-size: 2rem;
  }
  p {
    line-height: 1.5;
    a {
      color: blue;
      &:hover {
        color: darkblue;
      }
    }
  }
}

/* 이제 이게 순수 CSS입니다 */
.card {
  background: white;
  & .headline {
    font-size: 2rem;
  }
  & p {
    line-height: 1.5;
    & a {
      color: blue;
      &:hover {
        color: darkblue;
      }
    }
  }
}

문법이 거의 똑같아서 Sass를 쓰던 분들은 바로 적응할 수 있죠.

다만 몇 가지 중요한 차이점이 있는데요.

Sass에서는 & 기호가 선택적인 경우가 많았지만, 순수 CSS 네스팅에서는 대부분 필수입니다.

특히 > (자식 결합자)나 + (인접 형제 결합자) 같은 결합자를 사용할 때는 반드시 &를 앞에 붙여줘야 하죠.

이제 복잡한 전처리기 없이도 CSS 파일의 구조를 훨씬 깔끔하고 직관적으로 관리할 수 있게 된 겁니다.

단 한 줄로 폼을 스마트하게 field-sizing

이건 정말 간단하면서도 아주 유용한 새 속성인데요.

field-sizing: content; 라는 한 줄만 추가하면 폼 요소들이 내용물에 맞게 알아서 크기를 조절합니다.

특히 textareaselect 박스에서 그 진가가 드러나죠.

textarea는 사용자가 글을 길게 쓰면 스크롤바 없이도 알아서 늘어나고, select 박스는 가장 긴 옵션의 길이에 맞춰 너비가 늘어났다가 짧은 옵션을 선택하면 다시 줄어듭니다.

이제 자바스크립트로 이런 기능을 구현하느라 고생할 필요가 없어진 거죠.

미디어 쿼리의 시대를 끝낼 '컨테이너 쿼리'

이건 정말 '혁명'이라고 부를 만한 기능인데요.

우리가 반응형 웹을 만들 때 당연하게 사용했던 미디어 쿼리에는 치명적인 한계가 있었습니다.

미디어 쿼리는 오직 '뷰포트', 즉 브라우저 창 전체의 크기만 신경 쓰거든요.

내 컴포넌트가 사이드바에 있든, 메인 콘텐츠 영역에 있든 상관없이 똑같은 규칙을 적용받아야 했죠.

하지만 이제 '컨테이너 쿼리'가 등장하면서 모든 게 바뀌었습니다.

컴포넌트가 뷰포트가 아닌, 자신을 감싸고 있는 '부모 컨테이너'의 크기에 따라 스스로의 스타일을 결정하게 된 건데요.

.container {
  container-type: inline-size;
  container-name: card-container;
}

.card {
  /* 기본 스타일 */
  display: flex;
  flex-direction: column;
}

/* .card-container의 너비가 400px보다 커지면 .card의 스타일을 바꾼다 */
@container card-container (min-width: 400px) {
  .card {
    flex-direction: row;
  }
}

먼저 감시할 부모 요소에 container-type을 지정해주고, @container 규칙을 사용해서 그 컨테이너의 크기에 따른 스타일 변화를 정의해주면 끝입니다.

이제 똑같은 카드 컴포넌트라도 넓은 공간에 놓이면 가로로 배치되고, 좁은 사이드바에 놓이면 세로로 착 하고 바뀌는 '진짜 모듈형 디자인'이 가능해진 거죠.

더 이상 "사이드바에 들어가는 버전", "메인에 들어가는 버전"을 따로 만들 필요가 없습니다.

:has() 선택자, CSS의 역사를 새로 쓰다

발표자가 'CSS 그리드 이후 최고의 기능'이라고 극찬한 :has() 선택자가 있는데요.

이건 정말 CSS의 오랜 규칙, 즉 '위에서 아래로만 흐른다'는 대원칙을 깨부수는 기능입니다.

A:has(B)는 'B라는 자손을 가진 A를 선택하라'는 뜻인데요.

이걸 '부모 선택자'라고 부르기도 합니다.

이게 얼마나 강력한지 몇 가지 예시를 보시죠.

/* 이미지(img)를 포함하고 있는 카드(.card)는 배경색을 바꾼다 */
.card:has(img) {
  background-color: lightgray;
}

/* 필수 입력 필드([required])를 감싸고 있는 라벨(label) 뒤에 별표를 추가한다 */
label:has(+ [required])::after {
  content: ' *';
  color: red;
}

/* 유효하지 않은(:invalid) 입력 필드를 포함하고 있는 폼 그룹(.form-group)에 빨간 테두리를 친다 */
.form-group:has(:invalid) {
  border: 1px solid red;
}

이제껏 자바스크립트의 도움 없이는 불가능했던 일들이 순수 CSS만으로 가능해졌습니다.

자식의 상태에 따라 부모의 스타일을 바꿀 수 있게 되면서, UI 로직의 상당 부분을 CSS가 처리할 수 있게 된 거죠.

발표자는 이걸 이용해서 그리드에 아이템이 추가될 때 레이아웃이 깨지지 않도록 자동으로 열 개수를 조정하는 환상적인 데모도 보여주더라고요.

애니메이션의 신세계

최신 CSS는 애니메이션 기능도 엄청나게 발전했는데요.

먼저, 개발자들의 오랜 골칫거리였던 height: auto 애니메이션이 드디어 가능해졌습니다.

기존에는 높이가 0에서 auto로 변하는 아코디언 메뉴를 부드럽게 애니메이션 처리할 수 없었는데요.

이제는 calc-size라는 값을 사용하면 브라우저가 알아서 최종 높이를 계산해서 애니메이션을 적용해줍니다.

'스크롤 기반 애니메이션'도 이제 자바스크립트 없이 가능한데요.

animation-timeline: view(); 라는 속성을 사용하면, 특정 요소가 화면에 들어오고 나갈 때를 감지해서 애니메이션을 실행할 수 있습니다.

사용자가 스크롤하는 것에 맞춰서 요소가 스르륵 나타나거나 움직이는 효과를 순수 CSS만으로 구현할 수 있게 된 거죠.

알아두면 피가 되고 살이 되는 CSS 신기능들

발표자는 그 외에도 여러 가지 유용한 기능들을 속사포처럼 쏟아냈는데요.

text-wrap: balance 또는 pretty를 사용하면 문단 끝에 한 단어만 덩그러니 남는 '외톨이 단어' 현상을 막고, 텍스트 줄바꿈을 훨씬 보기 좋게 만들어줍니다.

@layer 규칙은 CSS의 '캐스케이드(Cascade)' 순서를 직접 제어할 수 있게 해주는데요.

특히 외부 라이브러리나 레거시 코드의 스타일을 덮어써야 할 때 !important를 남발하지 않고도 우아하게 문제를 해결할 수 있습니다.

마치 CSS에 타입스크립트를 도입한 것 같은 @property 규칙도 있는데요.

CSS 변수의 타입을 미리 정의해서 예측 가능한 동작을 보장하고, 색상이나 그라디언트처럼 원래는 애니메이션이 불가능했던 속성들도 애니메이션 가능하게 만들어줍니다.

마치며

강연을 듣고 나니 CSS가 지난 몇 년간 얼마나 눈부시게 발전했는지 새삼 깨닫게 되더라고요.

이제 CSS는 단순히 스타일을 꾸미는 언어를 넘어서, 정교한 레이아웃과 인터랙션 로직까지 처리할 수 있는 강력한 프로그래밍 언어로 진화하고 있습니다.

물론 자바스크립트가 필요한 순간은 여전히 많겠죠.

하지만 다음 프로젝트를 시작하기 전에 한 번쯤 고민해보는 건 어떨까요.

"이 기능, 혹시 CSS만으로도 가능하지 않을까?" 하고 말이죠.

어쩌면 우리가 생각하는 것보다 훨씬 많은 것을 해낼 수 있을 겁니다.