2026년 CSS는 사상 최대 규모의 업데이트를 맞이했다. Sass 없이도 사용할 수 있는 네이티브 @mixin, 접근성을 자동으로 보장하는 contrast-color(), 드디어 커스터마이징이 가능해진 base-select, 그리고 DOM 구조를 CSS에서 직접 활용하는 sibling-index()/sibling-count()까지. JavaScript 의존도를 대폭 줄이는 CSS 신기능들을 실전 코드와 함께 살펴보자.

네이티브 CSS @mixin과 @apply

Sass의 킬러 기능이었던 mixin이 CSS에 네이티브로 도입되었다. 재사용 가능한 스타일 블록을 정의하고 어디서든 적용할 수 있다.

기본 사용법

/* mixin 정의 */
@mixin --reset-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

@mixin --flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

@mixin --visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* mixin 적용 */
.nav-list {
  @apply --reset-list;
  gap: 1rem;
}

.hero-section {
  @apply --flex-center;
  min-height: 100vh;
}

.sr-only {
  @apply --visually-hidden;
}

매개변수가 있는 mixin

/* 매개변수를 받는 mixin */
@mixin --button(--bg, --color, --radius: 8px) {
  background-color: var(--bg);
  color: var(--color);
  border-radius: var(--radius);
  padding: 0.75rem 1.5rem;
  border: none;
  cursor: pointer;
  font-weight: 600;
  transition: opacity 0.2s ease;
}

@mixin --button:hover {
  opacity: 0.85;
}

/* 매개변수 전달하여 적용 */
.btn-primary {
  @apply --button(
    --bg: #2563eb,
    --color: #ffffff
  );
}

.btn-danger {
  @apply --button(
    --bg: #dc2626,
    --color: #ffffff,
    --radius: 24px
  );
}

.btn-outline {
  @apply --button(
    --bg: transparent,
    --color: #1f2937
  );
  border: 2px solid currentColor;
}

contrast-color() - 자동 접근성 보장

contrast-color() 함수는 배경색의 밝기를 분석하여 WCAG AA 기준을 충족하는 텍스트 색상을 자동으로 선택한다.

/* 배경색에 따라 검정 또는 흰색 텍스트 자동 선택 */
.badge {
  background-color: var(--badge-color);
  color: contrast-color(var(--badge-color));
  padding: 0.25rem 0.75rem;
  border-radius: 9999px;
  font-size: 0.875rem;
}

/* 다양한 배경색에 적용 */
.badge-success { --badge-color: #22c55e; } /* -> 검정 텍스트 */
.badge-error   { --badge-color: #dc2626; } /* -> 흰색 텍스트 */
.badge-warning { --badge-color: #facc15; } /* -> 검정 텍스트 */
.badge-info    { --badge-color: #0ea5e9; } /* -> 흰색 텍스트 */

/* 테마 색상에 반응하는 버튼 */
:root {
  --primary: #6366f1;
  --secondary: #f59e0b;
}

.btn-themed {
  background: var(--primary);
  color: contrast-color(var(--primary));
  /* 테마 색상이 바뀌어도 항상 가독성 보장 */
}

base-select - 커스터마이징 가능한 select

수년간 개발자들의 숙원이었던 select 요소의 완전한 스타일링이 가능해졌다.

/* opt-in으로 새 select 활성화 */
select {
  appearance: base-select;
}

/* 전체 select 스타일링 */
select {
  appearance: base-select;
  font-size: 1rem;
  padding: 0.75rem 1rem;
  border: 2px solid #e5e7eb;
  border-radius: 12px;
  background: white;
  color: #1f2937;
  min-width: 200px;
  transition: border-color 0.2s;
}

select:focus {
  border-color: #6366f1;
  outline: 3px solid rgba(99, 102, 241, 0.2);
}

/* 드롭다운 피커 스타일링 */
select::picker(select) {
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
  padding: 0.5rem;
  max-height: 300px;
  overflow-y: auto;
}

/* 화살표 아이콘 커스터마이징 */
select::picker-icon {
  content: url('data:image/svg+xml,...');
  transition: rotate 0.2s;
}

select:open::picker-icon {
  rotate: 180deg;
}

/* 개별 옵션 스타일링 */
select option {
  padding: 0.75rem 1rem;
  border-radius: 8px;
  margin: 2px 0;
  transition: background 0.15s;
}

select option:hover {
  background: #f0f0ff;
}

select option:checked {
  background: #6366f1;
  color: white;
  font-weight: 600;
}

sibling-index()와 sibling-count()

DOM 구조 정보를 CSS에서 직접 활용할 수 있다. nth-child보다 훨씬 유연하다.

/* 자식 요소의 인덱스를 활용한 순차 애니메이션 */
.card-grid > .card {
  animation: fadeInUp 0.5s ease both;
  /* 각 카드마다 0.1초씩 지연 */
  animation-delay: calc(sibling-index() * 0.1s);
}

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 전체 형제 수에 따른 반응형 레이아웃 */
.tab-list > .tab {
  /* 탭 수에 따라 자동으로 너비 조절 */
  flex-basis: calc(100% / sibling-count());
}

/* 인덱스 기반 그라데이션 */
.color-strip > span {
  --progress: calc(sibling-index() / sibling-count());
  background: color-mix(in oklch, #6366f1, #06b6d4 calc(var(--progress) * 100%));
  width: 100%;
  height: 4px;
}

/* 마지막 요소 감지를 sibling-count로 */
.list-item {
  --is-last: calc(sibling-index() - sibling-count() + 1);
  /* is-last가 0이면 마지막 요소 */
  border-bottom: max(var(--is-last), 0) * 1px solid #e5e7eb;
}

corner-shape - 다양한 모서리 형태

/* 기존: 원형 모서리만 가능 */
.old-card {
  border-radius: 16px; /* 항상 원형 */
}

/* CSS 2026: 다양한 모서리 형태 */
.squircle-card {
  corner-shape: squircle;
  border-radius: 24px;
  /* iOS 스타일의 부드러운 사각형 */
}

.notch-badge {
  corner-shape: notch;
  border-radius: 8px;
  /* 잘려나간 모서리 */
}

.scoop-card {
  corner-shape: scoop;
  border-radius: 20px;
  /* 안쪽으로 파인 모서리 */
}

.bevel-button {
  corner-shape: bevel;
  border-radius: 12px;
  /* 직선으로 깎인 모서리 */
}

MPA View Transitions

/* 페이지 간 전환 애니메이션 (Multi-Page Application) */
@view-transition {
  navigation: auto;
}

/* 기본 페이지 전환 */
::view-transition-old(root) {
  animation: fade-out 0.3s ease;
}

::view-transition-new(root) {
  animation: fade-in 0.3s ease;
}

@keyframes fade-out {
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
}

/* 특정 요소에 view-transition 이름 부여 */
.product-image {
  view-transition-name: product-hero;
}

/* 상품 이미지 공유 요소 전환 */
::view-transition-old(product-hero) {
  animation: scale-down 0.4s ease;
}

::view-transition-new(product-hero) {
  animation: scale-up 0.4s ease;
}

브라우저 지원 현황 (2026년 4월 기준)

  • @mixin/@apply - Chrome 128+, Firefox 131+, Safari 19.1+
  • contrast-color() - Chrome 127+, Firefox 130+, Safari 19+
  • base-select - Chrome 129+, Firefox 132+ (Safari 예정)
  • sibling-index/count - Chrome 128+, Firefox 131+, Safari 19+
  • corner-shape - Chrome 130+, Firefox 133+ (Safari 예정)
  • MPA View Transitions - Chrome 126+, Firefox 130+ (Safari 예정)