Conditional rules/Using container scroll-state queries

김동현·2026년 3월 21일

mdn 학습 번역 - CSS

목록 보기
69/190

안녕하세요! 프론트엔드 개발 강사입니다. 이번엔 비교적 최신 CSS 스펙인 컨테이너 쿼리(Container Queries) 중에서도 아주 흥미로운 스크롤 상태 쿼리(Scroll-state queries)를 가져오셨네요!

과거에는 스크롤 위치에 따라 요소의 스타일을 바꾸려면 반드시 JavaScript로 scroll 이벤트를 리스닝해야만 했지만, 이제는 CSS만으로도 이런 인터랙티브한 UI를 만들 수 있게 되었습니다. 문서 내용이 꽤 길지만, 실무에서 바로 써먹을 수 있는 꿀팁들과 함께 알기 쉽게 풀어서 전부 번역해 드릴게요! 😉


컨테이너 스크롤 상태 쿼리 사용하기 (Using container scroll-state queries)

👨‍🏫 강사의 부연 설명: "컨테이너 쿼리가 뭔가요?"
미디어 쿼리(@media)가 '브라우저 화면(Viewport) 크기'에 따라 스타일을 바꾼다면, 컨테이너 쿼리(@container)는 '부모 요소(Container)의 상태'에 따라 자식 요소의 스타일을 바꾸는 기술이에요. 컴포넌트 기반 개발(React, Vue 등)에서 재사용성을 극대화할 수 있는 모던 CSS의 핵심이죠!

컨테이너 스크롤 상태 쿼리(Container scroll-state queries)컨테이너 쿼리(container query)의 한 종류입니다. 부모 컨테이너의 '크기'를 기준으로 자식 요소에 스타일을 적용하는 대신, 스크롤 상태 쿼리는 부모 컨테이너의 '스크롤 상태(scroll state)'를 기준으로 자식 요소에 스타일을 선택적으로 적용할 수 있게 해줍니다.

여기서 말하는 '스크롤 상태'란 컨테이너가 일부분 스크롤되었는지, 조상인 스크롤 스냅 컨테이너(scroll snap container)에 딱 맞게 스냅(snap) 되었는지, 또는 position: sticky로 설정되어 조상 스크롤 컨테이너(scroll container)의 경계에 착 달라붙어(stuck) 있는지 등을 모두 포함합니다.

이 문서는 각 유형별 예제를 통해 컨테이너 스크롤 상태 쿼리를 사용하는 방법을 설명합니다. 이 글은 여러분이 컨테이너 쿼리의 기본을 알고 있다고 가정합니다. 만약 컨테이너 쿼리가 처음이시라면, 계속 읽기 전에 CSS 컨테이너 쿼리(CSS container queries) 문서를 먼저 읽어보시길 권장합니다.


이 문서의 내용


컨테이너 스크롤 상태 쿼리의 종류 (Types of container scroll-state query)

scroll-state() 쿼리 안에서 사용할 수 있는 @container 서술자(descriptor)에는 세 가지가 있습니다:

  • scrollable: 사용자가 (스크롤바를 드래그하거나 트랙패드 제스처를 사용하는 등) 컨테이너를 지정된 방향으로 스크롤할 수 있는 상태인지 확인합니다. 즉, 주어진 방향으로 스크롤해서 볼 수 있는 넘치는(overflowing) 콘텐츠가 있는지 묻는 것이죠. 이것은 스크롤 컨테이너의 스크롤 위치와 관련된 스타일을 적용할 때 유용합니다. 예를 들어, 스크롤바가 맨 위에 있을 때는 사용자에게 스크롤을 내려서 더 많은 내용을 보라고 유도하는 힌트를 보여주고, 사용자가 실제로 스크롤을 시작하면 그 힌트를 숨길 수 있습니다.
  • scrolled: 컨테이너가 가장 최근에 어느 방향으로 스크롤되었는지를 확인합니다. 이를 통해 사용자의 스크롤 방향에 따라 선택적으로 스타일을 적용할 수 있습니다. 예를 들어, 사용자가 위로 스크롤할 때만 나타나는 상단 메뉴 바(top menu bar)를 만들 수 있습니다.
  • snapped: 지정된 축을 따라 컨테이너가 조상인 스크롤 스냅(scroll snap) 컨테이너에 스냅(달라붙음)될 예정인지를 확인합니다. 요소가 스크롤 스냅 컨테이너에 딱 맞게 정렬되었을 때 스타일을 적용하는 데 유용합니다. 예를 들어, 스냅된 요소를 어떻게든 강조(highlight)하거나, 이전에 숨겨져 있던 해당 요소의 콘텐츠를 보여주게 할 수 있습니다.
  • stuck: position 값이 sticky인 컨테이너가 조상 스크롤 컨테이너의 가장자리에 달라붙었는지(stuck) 확인합니다. 이것은 position: sticky 요소가 달라붙었을 때 다르게 스타일링하는 데 유용합니다. 예를 들어, 달라붙은 상태일 때 다른 색상 테마나 레이아웃을 제공할 수 있죠.

💡 강사의 실무 팁!
예전에는 이 4가지 기능을 구현하려면 JavaScript로 IntersectionObserver를 쓰거나, scroll 이벤트 리스너를 달아서 방향을 계산하거나(scrolled), 요소가 화면 상단에 닿았는지 계산(stuck)해야 했어요. 이 CSS 쿼리들은 프론트엔드 개발자들의 JavaScript 코드를 엄청나게 줄여줄 혁명적인 기능입니다!


문법 개요 (Syntax overview)

어떤 요소를 스크롤 상태 쿼리의 기준이 되는 컨테이너로 설정하려면, 해당 요소에 container-type 속성을 주고 값을 scroll-state로 설정하면 됩니다. 선택적으로 container-name을 부여해서 특정 컨테이너 쿼리로 해당 컨테이너만 콕 집어 타겟팅할 수도 있습니다.

.container {
  container-type: scroll-state;
  container-name: my-container;
}

그런 다음, 쿼리 조건과 테스트를 통과했을 때 컨테이너의 자식들에게 적용될 CSS 규칙들을 지정하는 @container 블록을 작성할 수 있습니다. 이때 원한다면 쿼리할 컨테이너의 container-name을 지정할 수 있어요. 만약 container-name을 지정하지 않으면, 해당 컨테이너 쿼리는 페이지 내의 모든 스크롤 상태 쿼리 컨테이너에 적용됩니다.

여기서는 이름이 my-container인 컨테이너만 타겟팅하여, 해당 컨테이너가 위쪽 가장자리(top edge)를 향해 스크롤될 수 있는지 여부를 쿼리합니다.

@container my-container scroll-state(scrollable: top) {
  /* CSS 규칙들이 여기에 들어갑니다 */
}

📝 참고:
스크롤 상태 쿼리를 다른 일반적인 컨테이너 쿼리와 구분하기 위해, 스크롤 상태 서술자와 그 값은 괄호 안에 넣고 그 앞에 scroll-state를 붙여서 작성합니다 (scroll-state( ... )). 마치 함수처럼 생겼지만, 함수는 아닙니다.


scrollable 쿼리 사용하기 (Using `scrollable` queries)

스크롤 상태 scrollable 쿼리는 scroll-state(scrollable: <keyword>) 형태로 작성하며, 컨테이너의 스크롤 가능한 조상 요소가 사용자에 의해 지정된 방향으로 스크롤될 수 있는지 여부를 테스트합니다. 만약 스크롤할 수 없다면 쿼리는 false를 반환합니다.

키워드 값은 스크롤 가능 여부를 테스트할 방향을 나타냅니다. 예를 들면 다음과 같습니다:

  • top: 컨테이너가 위쪽 가장자리를 향해 스크롤될 수 있는지 테스트합니다. (즉, 현재 맨 위가 아니라서 위로 올릴 여지가 있는지 묻는 것입니다.)
  • inline-end: 컨테이너가 인라인 끝(inline-end) 가장자리를 향해 스크롤될 수 있는지 테스트합니다.
  • y: 컨테이너가 y축을 따라 어느 한 방향 또는 양방향 모두로 스크롤될 수 있는지 테스트합니다.

만약 테스트를 통과하면, @container 블록 안의 규칙들이 일치하는 스크롤 컨테이너의 자손 요소들에게 적용됩니다.

콘텐츠가 가득 찬 스크롤 컨테이너와 원할 때 맨 위로 돌아갈 수 있는 유용한 "위로 가기(back-to-top)" 링크가 있는 예제를 살펴봅시다. 우리는 scrollable 쿼리를 사용해서 사용자가 콘텐츠를 아래로 스크롤하기 시작했을 때만 이 링크를 보여줄 것입니다.

HTML

HTML에는 문서가 스크롤될 만큼 충분한 콘텐츠를 담은 <article> 요소가 있고, 그 앞에 위로 가기 링크(back-to-top link)가 있습니다:

(MDN Playground에서 실행해보기)

<a class="back-to-top" href="#" aria-label="Top of page"></a>
<article>
  <h1>Reader with container query-controlled "back-to-top" link</h1>
  <section>
    <header>
      <h2>This first section is interesting</h2>

      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    </header>

    ...
  </section>

  ...
</article>

(간결함을 위해 대부분의 HTML 내용은 숨겼습니다.)

CSS

.back-to-top 링크에는 position 값을 fixed로 주고 뷰포트의 우측 하단 모서리에 배치한 뒤, translate 값을 80px 0으로 주어 뷰포트 바깥으로 밀어내어 숨겨둡니다. transition 값은 translate이나 background-color 값이 변경될 때 애니메이션 효과를 줄 것입니다.

.back-to-top {
  width: 64px;
  height: 64px;
  color: white;
  text-align: center;
  position: fixed;
  bottom: 10px;
  right: 10px;
  translate: 80px 0;
  transition:
    0.4s translate,
    0.2s background-color;
}

이 예제에서 스크롤 컨테이너(scroll container)<html> 요소 자체입니다. 우리는 이 요소에 container-type 값을 scroll-state로 설정하여 스크롤 상태 쿼리 컨테이너로 지정합니다. container-name은 꼭 필요한 건 아니지만, 여러 개의 스크롤 상태 쿼리 컨테이너가 있고 각기 다른 쿼리로 타겟팅해야 하는 큰 코드베이스에 코드를 추가할 때 아주 유용합니다.

html {
  container-type: scroll-state;
  container-name: scroller;
}

다음으로, 이 쿼리가 타겟팅할 컨테이너 이름과 쿼리 조건인 scrollable: top을 설정하는 @container 블록을 정의합니다. 이 쿼리는 <html> 요소가 상단 가장자리를 향해 스크롤될 수 있을 때만(다시 말해, 컨테이너가 이전에 아래쪽으로 조금이라도 스크롤 되었을 때만) 블록 안의 규칙을 적용합니다. 이 조건이 맞으면 .back-to-top 링크에 translate: 0 0이 적용되어 링크가 다시 화면 안으로 부드럽게 나타나게 됩니다.

@container scroller scroll-state(scrollable: top) {
  .back-to-top {
    translate: 0 0;
  }
}

결과 (Result)

문서를 아래로 스크롤해 보면, 그 결과로 "back-to-top" 링크가 나타나며 transition 속성 덕분에 뷰포트 오른쪽에서 부드럽게 애니메이션되며 들어오는 것을 확인할 수 있습니다. 링크를 클릭하거나 직접 스크롤하여 다시 맨 위로 올라가면, "back-to-top" 링크는 다시 화면 밖으로 이동하며 사라집니다.

(MDN Playground에서 실행해보기)


scrolled 쿼리 사용하기 (Using `scrolled` queries)

스크롤 상태 scrolled 쿼리는 scroll-state(scrolled: <keyword>) 형태로 작성하며, 컨테이너의 스크롤 조상이 가장 최근에 지정된 방향으로 스크롤되었는지 여부를 테스트합니다. 그렇지 않다면 쿼리는 false를 반환합니다.

키워드 값은 테스트하려는 방향을 나타냅니다. 예를 들면:

  • block-start: 컨테이너가 가장 최근에 블록 시작(block-start, 일반적으로 위쪽) 가장자리 방향으로 스크롤되었는지 테스트합니다.
  • right: 컨테이너가 가장 최근에 오른쪽 가장자리 방향으로 스크롤되었는지 테스트합니다.
  • y: 컨테이너가 가장 최근에 y축을 따라 위나 아래로 스크롤되었는지 테스트합니다.
  • none: 컨테이너가 스크롤 컨테이너가 아니거나 렌더링된 이후로 어떤 방향으로도 스크롤되지 않았는지 테스트합니다.

만약 테스트 결과가 true라면, @container 블록 내부의 규칙들이 일치하는 스크롤 컨테이너의 자손들에게 적용됩니다.

사용자가 위로 스크롤할 때는 상단 바(top bar)를 보여주고, 아래로 스크롤할 때는 하단 바(bottom bar)를 보여주는 scrolled 쿼리 예제를 살펴보겠습니다.

HTML

HTML에는 문서가 스크롤될 수 있도록 충분한 콘텐츠를 담은 <article> 요소가 있고, 그 앞에 우리의 상/하단 "바(bars)"를 나타내는 두 개의 <div> 요소가 있습니다:

(MDN Playground에서 실행해보기)

<div class="bar" id="top-bar">You're currently scrolling towards the top.</div>
<div class="bar" id="bottom-bar">
  You're currently scrolling towards the bottom.
</div>
<article>
  <h1>Document with scrolled container query</h1>
  <section>
    <header>
      <h2>This first section is interesting</h2>

      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    </header>

    ...
  </section>

  ...
</article>

CSS

"바(bars)" 요소들에는 기본적인 스타일이 적용되어 있습니다. 가장 중요한 점은, position 값을 fixed로 설정하고 leftright 값을 이용해 양옆에서 떨어지도록 배치했다는 것입니다.

.bar {
  border-radius: 10px;
  border: 1px solid #000;
  background-color: #0009;
  padding: 10px;
  color: white;
  text-shadow: 1px 1px 1px black;
  display: flex;
  justify-content: center;
  align-items: center;

  position: fixed;
  left: 5px;
  right: 5px;
}

다음으로, 상단 바와 하단 바에 각각 음수(-)의 topbottom 길이 값을 설정하여 기본적으로 뷰포트 위아래 바깥에 숨겨지도록 만듭니다. 그리고 translate 값이 변할 때 화면 안으로 부드럽게 나타나도록 transition을 추가합니다.

#top-bar {
  top: -50px;
  transition: 0.6s translate;
}

#bottom-bar {
  bottom: -50px;
  transition: 0.6s translate;
}

이 예제에서도 스크롤 컨테이너<html> 요소 자체입니다. 우리는 이것을 scroll-state 타입의 쿼리 컨테이너로 선언합니다.

html {
  container-type: scroll-state;
  container-name: scroller;
}

그다음, scroller라는 컨테이너 이름을 타겟팅하는 두 개의 @container 블록을 정의합니다. 첫 번째 블록은 scrolled: block-end 쿼리를 정의하고, 두 번째 블록은 scrolled: block-start 쿼리를 정의합니다.
각각의 쿼리는 <html> 요소가 가장 최근에 블록 끝(아래쪽) 방향으로 스크롤되었는지, 아니면 블록 시작(위쪽) 방향으로 스크롤되었는지에 따라 해당 블록 안의 규칙을 적용합니다. 즉, 컨테이너를 아래로 스크롤할 때와 위로 스크롤할 때를 구분하는 것이죠.

어느 한쪽의 조건이 참(true)이 되면, 해당 블록 안에 참조된 바(bar) 요소에 translate 값이 설정되어 화면 안으로 나타나게 됩니다. 반대로 조건이 더 이상 참이 아니게 된 쿼리 블록의 바(bar) 요소는 다시 화면 밖으로 사라집니다.

@container scroller scroll-state(scrolled: block-start) {
  #top-bar {
    translate: 0 55px; /* 위로 스크롤하면 상단 바가 나타남 */
  }
}

@container scroller scroll-state(scrolled: block-end) {
  #bottom-bar {
    translate: 0 -55px; /* 아래로 스크롤하면 하단 바가 나타남 */
  }
}

결과 (Result)

문서를 위아래로 스크롤해 보면서, 스크롤 방향에 따라 각기 다른 바(bar) 요소가 부드럽게 화면에 나타났다 사라지는 결과를 확인해 보세요.

(MDN Playground에서 실행해보기)


snapped 쿼리 사용하기 (Using `snapped` queries)

스크롤 스냅(scroll snapping) 기능이 구현되어 있을 때만 유효한 스크롤 상태 snapped 쿼리는 scroll-state(snapped: <keyword>) 형태로 작성합니다. 이 쿼리는 컨테이너가 지정된 축을 따라 스크롤 스냅 컨테이너 조상에게 스냅될 예정인지(going to be snapped)를 테스트합니다. 그렇지 않다면 false를 반환합니다.

이 경우 키워드 값은 요소가 스냅될 수 있는지 테스트할 방향을 나타냅니다. 예를 들면:

  • x: 컨테이너가 가로(수평) 방향으로 스크롤 스냅 컨테이너 조상에 스냅되는지 테스트합니다.
  • inline: 컨테이너가 인라인(inline) 방향으로 스크롤 스냅 컨테이너 조상에 스냅되는지 테스트합니다.
  • y: 컨테이너가 양방향(수직 방향)으로 스크롤 스냅 컨테이너 조상에 스냅되는지 테스트합니다.

none이 아닌 snapped 스크롤 상태 쿼리로 컨테이너를 평가하려면, 해당 요소는 반드시 스크롤 스냅 컨테이너 조상을 가져야 합니다. 즉, 조상 요소가 none이 아닌 scroll-snap-type 값을 가지고 있어야 합니다. 반대로 scroll-state(snapped: none) 쿼리는 스크롤 컨테이너 조상이 없는 스크롤 상태 컨테이너와 일치합니다.

이 평가는 스크롤 스냅 컨테이너에서 scrollsnapchanging 이벤트가 발생할 때 일어납니다.
테스트를 통과하면, @container 블록 내부의 규칙들이 일치하는 스크롤 스냅 타겟 컨테이너의 자손들에게 적용됩니다.

이 예제에서는 세로 방향으로 스냅되는 자식 요소들을 가진 스크롤 스냅 컨테이너를 만들고, snapped 쿼리를 사용해서 자식 요소들이 스냅되었거나 스냅되기 직전일 때만 스타일을 적용해 보겠습니다.

💡 강사의 실무 팁! "스크롤 스냅(Scroll Snap)이 뭔가요?"
틱톡(TikTok)이나 유튜브 쇼츠(Shorts)처럼 스크롤을 살짝만 넘겨도 다음 영상이 화면에 '착' 하고 달라붙어 정렬되는 기능 보셨죠? 그게 바로 CSS 스크롤 스냅입니다. snapped 쿼리를 쓰면 현재 화면 한가운데에 '착' 달라붙어 있는 바로 그 요소(활성화된 요소)만 찾아내서 글자색을 바꾸거나 테두리를 칠할 수 있습니다!

HTML

HTML은 스크롤 스냅 컨테이너 역할을 할 <main> 요소로 구성됩니다. 그 내부에는 스냅 타겟(snap targets)이 될 여러 개의 <section> 요소들이 있습니다. 각 <section>은 래퍼(wrapper) <div><h2> 제목을 포함합니다. 래퍼(div.wrapper)를 넣은 이유는, 컨테이너 쿼리가 컨테이너 자기 자신이 아니라 컨테이너의 자손들을 스타일링할 수 있게 해주기 때문입니다. (즉, <section>을 기준으로 그 안의 div.wrapper를 디자인하기 위함입니다.)

(MDN Playground에서 실행해보기)

<main>
  <section>
    <div class="wrapper">
      <h2>Section 1</h2>
    </div>
  </section>

  ...
</main>

CSS

<main> 요소에 overflow 값으로 scroll을, 고정된 height를 설정하여 세로 스크롤 컨테이너로 만듭니다. 또한 scroll-snap-type 값을 y mandatory로 설정하여 <main>을 y축을 따라 스냅 타겟들이 달라붙는 스크롤 스냅 컨테이너로 만듭니다. 여기서 mandatory는 스크롤을 멈췄을 때 타겟 요소가 무조건(항상) 스냅되어야 함을 의미합니다.

main {
  overflow: scroll;
  scroll-snap-type: y mandatory;
  height: 450px;
  width: 250px;
  border: 3px solid black;
}

<section> 요소들은 none이 아닌 scroll-snap-align 값을 설정하여 스냅 타겟으로 지정됩니다. center 값을 주면 이 요소들이 스크롤 컨테이너의 중앙 지점에 맞춰서 스냅(정렬)되게 됩니다.

section {
  font-family: "Helvetica", "Arial", sans-serif;
  width: 150px;
  height: 150px;
  margin: 50px auto;

  scroll-snap-align: center;
}

우리는 <section> 요소들이 컨테이너 쿼리의 대상이 되게 하려 합니다. 구체적으로는, <section> 요소들이 부모 컨테이너에 스냅되는 중인지(가운데 정렬되었는지)를 테스트하고 싶으므로, 이 요소들에 container-type 값을 scroll-state로 설정하여 스크롤 상태 쿼리 컨테이너로 선언합니다.

section {
  container-type: scroll-state;
  container-name: snap-container;
}

다음으로, 타겟으로 할 컨테이너 이름과 쿼리 조건인 snapped: y를 설정하는 @container 블록을 정의합니다. 이 쿼리는 <section> 요소가 부모 컨테이너에 세로로 스냅되고 있을 때만 블록 내부의 규칙을 적용합니다. 조건이 맞으면, <section> 요소의 자식인 .wrapper <div>에 새로운 backgroundcolor를 적용하여 시각적으로 강조(highlight)합니다.

@container snap-container scroll-state(snapped: y) {
  .wrapper {
    background: purple; /* 스냅된 요소만 보라색으로! */
    color: white;
  }
}

결과 (Result)

결과물은 아래와 같습니다. 컨테이너를 위아래로 스크롤해 보면서, 특정 <section>이 컨테이너의 정중앙에 스냅되었을 때 스타일이 어떻게 변하는지 확인해 보세요.

(MDN Playground에서 실행해보기)


stuck 쿼리 사용하기 (Using `stuck` queries)

스크롤 상태 stuck 쿼리는 scroll-state(stuck: <keyword>) 형태로 작성하며, position 값이 sticky인 컨테이너가 스크롤 조상 컨테이너의 가장자리에 달라붙었는지(stuck) 여부를 테스트합니다. 그렇지 않다면 false를 반환합니다.

여기서 키워드 값은 테스트하고자 하는 스크롤 컨테이너의 가장자리를 나타냅니다. 예를 들면:

  • top: 컨테이너가 스크롤 조상의 상단(top) 가장자리에 달라붙었는지 테스트합니다.
  • block-end: 컨테이너가 스크롤 조상의 블록 끝(보통 하단) 가장자리에 달라붙었는지 테스트합니다.
  • none: 컨테이너가 스크롤 조상의 어떤 가장자리에도 달라붙지 않았는지 테스트합니다. (참고로 none 쿼리는 해당 컨테이너에 position: sticky가 설정되어 있지 않아도 일치하는 것으로 간주됩니다.)

쿼리가 true를 반환하면, @container 블록 안의 규칙들이 조건을 만족하는 position: sticky 컨테이너의 자손들에게 적용됩니다.

넘치는 콘텐츠가 있는 스크롤 컨테이너 예제를 살펴봅시다. 여기서 각 제목(heading) 요소들은 position: sticky로 설정되어 있어서, 스크롤을 내리다 보면 컨테이너의 상단 모서리에 찰칵 하고 달라붙게 됩니다. 우리는 stuck 스크롤 상태 쿼리를 사용하여 제목들이 상단 가장자리에 달라붙었을 때(stuck) 디자인이 다르게 보이도록 스타일을 변경해 볼 것입니다.

💡 강사의 실무 팁!
네이버나 다음 카페의 글 목록을 볼 때, 화면을 아래로 내리면 '게시판 카테고리 탭'이 화면 맨 위에 찰싹 달라붙어서 계속 따라오는 UI를 많이 보셨을 거예요. 이게 바로 position: sticky입니다. 그런데 이 탭이 화면 위에 달라붙을 때 그림자(Box-shadow)가 생기거나 배경색이 변하면 더 예쁘겠죠? 그럴 때 자바스크립트 없이 바로 이 stuck 쿼리를 쓰는 겁니다!

HTML

HTML에는 스크롤될 수 있을 만큼의 내용이 담긴 <article> 요소가 있습니다. 이 요소는 여러 개의 <section> 요소로 구성되어 있으며, 각 섹션에는 콘텐츠가 중첩된 <header>가 포함되어 있습니다.

(MDN Playground에서 실행해보기)

<article>
  <h1>Sticky reader with scroll-state container query</h1>
  <section>
    <header>
      <h2>This first section is interesting</h2>

      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    </header>

    ...
  </section>

  <section>
    <header>
      <h2>This one, not so much</h2>

      <p>Confecta res esset.</p>
    </header>

    ...
  </section>

  ...
</article>

CSS

<header>에는 position 값을 sticky로 주고 top 값을 0으로 주어서, 스크롤을 내릴 때 스크롤 컨테이너의 상단 모서리에 닿으면 달라붙게 만들었습니다.
<header> 요소들이 컨테이너의 상단에 달라붙었는지(stuck) 테스트하기 위해, <header> 요소들에 container-type 값을 scroll-state로 주어 스크롤 상태 쿼리 컨테이너로 지정합니다.

header {
  background: white;
  position: sticky;
  top: 0;
  container-type: scroll-state;
  container-name: sticky-heading;
}

또한, <header> 내부의 <h2><p> 요소들에 기본적인 스타일을 주고, 배경색이 변경될 때 부드럽게 애니메이션되도록 transition 값을 추가합니다.

h2,
header p {
  margin: 0;
  transition: 0.4s background;
}

h2 {
  padding: 20px 5px;
  margin-bottom: 10px;
}

header p {
  font-style: italic;
  padding: 10px 5px;
}

마지막으로, 우리가 쿼리할 컨테이너 이름과 stuck: top 이라는 쿼리 조건을 설정하는 @container 블록을 정의합니다. 이 쿼리는 <header> 요소가 자신의 스크롤 컨테이너 맨 위에 달라붙었을 때만 블록 안의 규칙을 적용합니다. 이 경우, <header> 내부의 <h2><p>에 색다른 배경색(background)과 그림자(box-shadow)가 적용됩니다.

@container sticky-heading scroll-state(stuck: top) {
  h2,
  p {
    background: #cccccc; /* 달라붙으면 배경색이 회색으로 변합니다! */
    box-shadow: 0 5px 2px #00000077; /* 그림자도 생기죠 */
  }
}

결과 (Result)

문서를 위아래로 스크롤해 보세요. <h2><p> 요소가 상단에 달라붙는(stuck) 순간, 새로운 색상과 그림자 테마로 부드럽게 전환되는 것을 확인할 수 있습니다!

(MDN Playground에서 실행해보기)


같이 보기 (See also)


MDN 개선에 참여하기 (Help improve MDN)

이 페이지가 도움이 되셨나요? [네 (Yes)] / [아니요 (No)]

기여하는 방법 알아보기 (Learn how to contribute)

이 페이지는 2026년 3월 10일에 MDN 기여자들 (MDN contributors)에 의해 마지막으로 수정되었습니다.

profile
프론트에_가까운_풀스택_개발자

0개의 댓글