JavaScript 없이 가능해진 CSS의 혁신

2025~2026년 CSS는 과거 JavaScript에 의존하던 많은 UI 패턴을 순수 CSS만으로 구현할 수 있게 되는 전환점을 맞이했습니다. View Transitions, Anchor Positioning, Scroll-Driven Animations, Popover API 등이 주요 브라우저에서 안정적으로 지원되기 시작했으며, 이를 활용하면 성능과 접근성을 동시에 개선할 수 있습니다.

1. View Transitions — 페이지 전환 애니메이션

View Transitions API는 SPA와 MPA(Multi-Page App) 모두에서 매끄러운 페이지 전환 효과를 구현합니다. Safari가 지원을 추가하면서 이제 주요 브라우저 전체에서 사용 가능합니다.

/* CSS: View Transition 스타일 정의 */
::view-transition-old(root) {
  animation: slide-out 300ms ease-in;
}

::view-transition-new(root) {
  animation: slide-in 300ms ease-out;
}

@keyframes slide-out {
  from { transform: translateX(0); opacity: 1; }
  to   { transform: translateX(-100%); opacity: 0; }
}

@keyframes slide-in {
  from { transform: translateX(100%); opacity: 0; }
  to   { transform: translateX(0); opacity: 1; }
}

/* 특정 요소에 이름 지정 (공유 요소 전환) */
.product-card {
  view-transition-name: product-card;
}

.product-detail-image {
  view-transition-name: product-card; /* 동일 이름으로 모핑 효과 */
}
// JavaScript: View Transition 트리거
async function navigateTo(url) {
  if (!document.startViewTransition) {
    // 미지원 브라우저 폴백
    window.location.href = url;
    return;
  }

  const transition = document.startViewTransition(async () => {
    const response = await fetch(url);
    const html = await response.text();
    document.getElementById('main-content').innerHTML =
      new DOMParser().parseFromString(html, 'text/html')
        .getElementById('main-content').innerHTML;
  });

  await transition.finished;
}

// MPA: next.config.js (Next.js 15+)
export default {
  experimental: {
    viewTransition: true, // 자동 처리
  },
};

2. CSS Anchor Positioning — JavaScript 없는 툴팁/드롭다운

기존에는 툴팁, 드롭다운, 팝오버의 위치 조정을 위해 JavaScript로 getBoundingClientRect()를 계산해야 했습니다. CSS Anchor Positioning은 이를 순수 CSS로 해결합니다.

/* 앵커 요소 정의 */
.trigger-button {
  anchor-name: --my-trigger; /* 앵커 이름 등록 */
}

/* 앵커에 연결된 팝오버 위치 설정 */
.tooltip {
  position: absolute;
  position-anchor: --my-trigger; /* 앵커 참조 */

  /* 앵커 기준 위치 설정 */
  top: anchor(bottom);    /* 앵커의 bottom에 맞춤 */
  left: anchor(center);   /* 앵커의 center에 맞춤 */
  transform: translateX(-50%);

  /* 자동 위치 조정: 뷰포트 밖으로 나가면 반대로 */
  position-try-fallbacks: flip-block, flip-inline;
}

/* 실용적인 드롭다운 메뉴 */
.nav-item {
  anchor-name: --nav-dropdown;
}

.dropdown-menu {
  position: fixed;
  position-anchor: --nav-dropdown;
  top: anchor(bottom);
  left: anchor(left);
  margin-top: 4px;

  /* 화면 밖으로 나가면 위로 표시 */
  position-try-fallbacks: flip-block;
}

3. Scroll-Driven Animations — 스크롤 기반 애니메이션

스크롤 위치에 애니메이션을 연결하는 기능이 CSS만으로 가능해졌습니다. JavaScript의 IntersectionObserver나 scroll 이벤트 리스너 없이 성능 친화적 애니메이션을 구현합니다.

/* 스크롤 진행도를 나타내는 프로그레스 바 */
@keyframes progress-grow {
  from { width: 0%; }
  to   { width: 100%; }
}

.scroll-progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 4px;
  background: linear-gradient(90deg, #667eea, #764ba2);

  animation: progress-grow linear;
  animation-timeline: scroll(root block); /* 페이지 스크롤에 연결 */
}

/* 요소가 뷰포트에 진입하면서 페이드인 */
@keyframes fade-slide-up {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.animate-on-scroll {
  animation: fade-slide-up ease-out both;
  animation-timeline: view(); /* 요소의 뷰포트 진입에 연결 */
  animation-range: entry 0% entry 60%; /* 진입 구간 설정 */
}

/* 수평 스크롤 갤러리의 진행도 */
.gallery-container {
  overflow-x: scroll;
  scroll-snap-type: x mandatory;
  timeline-scope: --gallery-scroll;
}

.gallery-progress {
  animation-timeline: --gallery-scroll;
}

4. CSS Nesting과 :has() 선택자

/* CSS Nesting (브라우저 네이티브 지원) */
.card {
  padding: 1rem;
  border-radius: 8px;

  /* 중첩 선택자 */
  .title {
    font-size: 1.25rem;
    font-weight: bold;
  }

  .description {
    color: #666;
    margin-top: 0.5rem;
  }

  /* 가상 클래스 중첩 */
  &:hover {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
    transition: all 0.2s ease;
  }

  /* 미디어 쿼리 중첩 */
  @media (max-width: 768px) {
    padding: 0.75rem;
  }
}

/* :has() 선택자 — 부모 선택 가능 */
/* 이미지를 포함한 카드에만 스타일 적용 */
.card:has(img) {
  padding-top: 0;
}

/* 체크박스가 체크된 경우 형제 레이블 스타일 변경 */
.form-item:has(input:checked) label {
  color: #2196F3;
  font-weight: bold;
}

/* 에러가 있는 폼 필드 감지 */
form:has(.field:invalid) .submit-button {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

5. Popover API — 접근성을 갖춘 네이티브 팝오버

<!-- HTML: Popover API -->
<button popovertarget="my-tooltip">
  도움말 보기
</button>

<div id="my-tooltip" popover="hint">
  <p>여기에 도움말 내용이 들어갑니다.</p>
</div>

<!-- 확인 대화상자 -->
<button popovertarget="confirm-dialog">삭제하기</button>

<div id="confirm-dialog" popover>
  <h2>정말 삭제하시겠습니까?</h2>
  <button popovertargetaction="hide" popovertarget="confirm-dialog">취소</button>
  <button onclick="handleDelete()">삭제 확인</button>
</div>
/* CSS: Popover 스타일 */
[popover] {
  /* 기본 popover 스타일 초기화 */
  border: none;
  border-radius: 8px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
  padding: 1rem;

  /* 진입/퇴장 애니메이션 */
  transition: opacity 0.2s, transform 0.2s,
              overlay 0.2s allow-discrete,
              display 0.2s allow-discrete;
  opacity: 1;
  transform: scale(1);
}

[popover]:not(:popover-open) {
  opacity: 0;
  transform: scale(0.95);
}

@starting-style {
  [popover]:popover-open {
    opacity: 0;
    transform: scale(0.95);
  }
}

6. 접근성 향상: WCAG 2.2와 forced-colors

/* 고대비 모드 지원 */
@media (forced-colors: active) {
  .custom-button {
    border: 2px solid ButtonText;
    background: ButtonFace;
    color: ButtonText;
  }

  .custom-button:hover {
    background: Highlight;
    color: HighlightText;
  }
}

/* 포커스 가시성 강화 (WCAG 2.2 2.4.11) */
:focus-visible {
  outline: 3px solid #0066cc;
  outline-offset: 2px;
  border-radius: 2px;
}

/* 동작 감소 선호 사용자를 위한 배려 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }

  .scroll-progress-bar {
    display: none; /* 스크롤 애니메이션 숨김 */
  }
}

/* 색 대비 충족 체크 (미래: CSS Color Contrast) */
:root {
  --primary: #0066cc;
  --on-primary: white; /* WCAG AA 대비 비율 4.5:1 이상 유지 */
}

2025~2026년 CSS는 단순한 스타일링 도구를 넘어, 인터랙션과 접근성을 함께 담당하는 핵심 기술로 진화했습니다. 특히 View Transitions와 Scroll-Driven Animations는 JavaScript 의존도를 낮추면서도 사용자 경험을 크게 향상시킬 수 있는 강력한 도구입니다. 브라우저 지원이 안정화된 지금이 실무 적용의 최적 시점입니다.