@supports - 안전하게 최신 CSS를 사용하는 법
최신 CSS 기능을 사용하고 싶은데 “구형 브라우저에서는 어떻게 하지?”라는 고민을 해보신 적 있나요?
.container {
display: grid; /* IE에서는 안 되는데... */
gap: 1rem; /* 이것도 안 되는 브라우저가 있을 텐데... */
}
새로운 기능을 쓰면 레이아웃이 깨질까 봐 걱정되고, 그렇다고 구형 문법만 쓰자니 답답하죠. “모든 브라우저에서 작동하는지 어떻게 알지?”, “폴백은 어떻게 제공하지?”
@supports는 바로 이 문제를 해결합니다. 브라우저가 특정 CSS 기능을 지원하는지 확인하고, 지원할 때만 해당 스타일을 적용하는 조건문입니다.
왜 @supports가 필요한가?
웹 개발에서 브라우저 호환성은 영원한 숙제입니다. 하지만 “모든 브라우저에 맞추느라 최신 기능을 못 쓰는 것”과 “최신 기능 쓰다가 구형 브라우저 사용자를 포기하는 것” 사이에는 중간 지대가 있습니다.
실제 문제 상황
문제 1: 기능 지원 불확실성
/* 이 코드는 언제 깨질까요? */
.fancy-text {
text-stroke: 2px blue; /* 일부 브라우저만 지원 */
}
결과:
- ✅ 지원 브라우저: 외곽선
- ❌ 미지원 브라우저: 아무 효과 없음 (다행히 레이아웃은 안 깨짐)
문제 2: 깨진 레이아웃
/* 이건 더 심각합니다 */
.layout {
display: grid; /* IE11 미지원 */
grid-template-columns: repeat(3, 1fr);
}
결과:
- ✅ 최신 브라우저: 완벽한 그리드 레이아웃
- ❌ IE11: 레이아웃이 완전히 깨짐 😱
문제 3: 접두사 지옥
/* 어떤 접두사가 필요한지 어떻게 알죠? */
.transform {
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
고민:
- “이 접두사 중에 어떤 게 실제로 필요하지?”
- “표준 속성만 지원하는 브라우저는 어떻게 하지?”
생각해보면, 우리는 기능 지원 여부를 확인하는 방법이 필요합니다. JavaScript의 if문처럼 CSS에도 조건문이 있다면? 그게 바로 @supports입니다.
먼저, 기초부터 이해하기
@supports가 어떻게 작동하는지 이해하려면, 브라우저가 CSS를 어떻게 처리하는지 알아야 합니다.
브라우저의 기본 동작
CSS에서 브라우저가 모르는 속성을 만나면:
.element {
display: flex; /* ✅ 지원: 적용됨 */
display: unknown-value; /* ❌ 미지원: 무시됨 */
color: red; /* ✅ 계속 파싱 */
}
특징:
- 모르는 속성은 조용히 무시
- 에러 없이 다음 속성으로 진행
- 이를 “graceful degradation(우아한 성능 저하)”라고 부름
하지만 이 방식의 문제는 컨트롤할 수 없다는 점입니다. 지원 여부에 따라 다른 스타일을 적용하고 싶다면?
@supports의 등장
MDN 문서에 따르면, @supports는 “브라우저의 CSS 기능 지원 여부에 따른 CSS 선언을 지정”할 수 있게 합니다.
/* 기본 폴백 */
.container {
display: block;
}
/* Grid 지원 시 업그레이드 */
@supports (display: grid) {
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
}
동작 방식:
- 브라우저가
display: grid지원 여부 체크 - ✅ 지원하면:
@supports블록 내부 실행 - ❌ 미지원하면: 블록 전체 무시
기본 문법
@supports는 세 가지 핵심 패턴이 있습니다.
1. 단일 조건 (기본)
@supports (property: value) {
/* 조건이 참일 때만 적용 */
}
예제:
/* ❌ 이렇게 쓰지 마세요 */
@supports display: grid {
/* 괄호 없음 - 에러! */
}
/* ✅ 올바른 방법 */
@supports (display: grid) {
.container {
display: grid;
}
}
주의: 괄호 ( )는 필수입니다!
2. NOT 연산자 (부정)
@supports not (property: value) {
/* 지원하지 않을 때만 적용 */
}
예제:
/* Grid를 지원하지 않는 브라우저용 폴백 */
@supports not (display: grid) {
.container {
display: flex; /* Grid 대신 Flexbox 사용 */
}
}
3. AND 연산자 (그리고)
@supports (property1: value1) and (property2: value2) {
/* 둘 다 지원할 때만 적용 */
}
예제:
/* Flexbox와 gap을 둘 다 지원할 때만 */
@supports (display: flex) and (gap: 1rem) {
.flex-container {
display: flex;
gap: 1rem; /* 최신 gap 속성 */
}
}
왜 필요할까?
gap은 Grid에서 먼저 나왔지만, Flexbox에서는 나중에 추가되었습니다. display: flex만 지원하고 gap은 미지원인 브라우저가 있을 수 있죠.
4. OR 연산자 (또는)
@supports (property: value1) or (property: value2) {
/* 둘 중 하나라도 지원하면 적용 */
}
예제:
/* 표준 또는 webkit 접두사 중 하나라도 지원 */
@supports (backdrop-filter: blur(10px)) or
(-webkit-backdrop-filter: blur(10px)) {
.modal-backdrop {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px); /* Safari */
}
}
복잡한 조건 조합
여러 연산자를 조합할 때는 괄호로 우선순위를 명확히 해야 합니다.
예제 1: (A and B) or C
@supports ((display: flex) and (gap: 1rem)) or (display: grid) {
.layout {
/* Flexbox+gap 또는 Grid 지원 시 */
}
}
의미:
- “Flexbox와 gap을 모두 지원” 또는
- “Grid를 지원”
예제 2: not (A or B)
@supports not ((transform: rotate(45deg)) or
(-webkit-transform: rotate(45deg))) {
/* transform을 전혀 지원하지 않을 때 */
.fallback {
/* 대체 스타일 */
}
}
❌ 잘못된 예: 괄호 없음
/* 이건 에러! */
@supports (display: flex) and (gap: 1rem) or (display: grid) {
/* 우선순위가 불명확 */
}
MDN 문서 명시: “복잡한 조건 조합 시 괄호 필수”
실전 활용 예제
실무에서 자주 사용되는 패턴들을 살펴봅시다.
1. Grid 레이아웃 폴백
Before (위험한 코드)
/* Grid 미지원 브라우저에서 깨짐 */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
After (@supports 사용)
/* 기본: 모든 브라우저 */
.gallery {
display: flex;
flex-wrap: wrap;
}
.gallery > * {
flex: 1 1 200px;
margin: 0.5rem; /* gap 대신 margin */
}
/* Grid 지원: 업그레이드 */
@supports (display: grid) {
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.gallery > * {
margin: 0; /* Grid gap 사용하므로 margin 제거 */
}
}
효과:
- ✅ IE11: Flexbox 레이아웃
- ✅ 최신 브라우저: Grid 레이아웃
- ✅ 모든 브라우저에서 깨지지 않음!
2. 스티키 포지션 폴백
/* 기본: fixed 사용 (약간 아쉬움) */
.header {
position: fixed;
top: 0;
width: 100%;
}
/* sticky 지원: 더 나은 UX */
@supports (position: sticky) {
.header {
position: sticky;
/* fixed와 달리 문서 흐름 유지 */
}
}
차이점:
fixed: 항상 화면에 고정 (문서 흐름에서 벗어남)sticky: 스크롤 시에만 고정 (더 자연스러움)
3. CSS 변수 폴백
/* 기본: 하드코딩 색상 */
.button {
background: #3498db;
color: white;
}
/* CSS 변수 지원: 테마 시스템 */
@supports (--custom: property) {
:root {
--primary-color: #3498db;
--text-color: white;
}
.button {
background: var(--primary-color);
color: var(--text-color);
}
}
Note: CSS 변수는 이제 대부분 지원되지만, 예시로서 패턴을 보여드립니다.
4. aspect-ratio 폴백
/* 기본: padding 해킹 */
.video-container {
position: relative;
padding-bottom: 56.25%; /* 16:9 비율 */
}
.video-container > iframe {
position: absolute;
width: 100%;
height: 100%;
}
/* aspect-ratio 지원: 깔끔한 방법 */
@supports (aspect-ratio: 16 / 9) {
.video-container {
aspect-ratio: 16 / 9;
padding-bottom: 0; /* 해킹 제거 */
}
.video-container > iframe {
position: static; /* 더 간단 */
}
}
5. :has() 선택자 폴백
/* 기본: JavaScript로 클래스 추가 필요 */
.card.has-image {
display: grid;
}
/* :has() 지원: CSS만으로 해결 */
@supports selector(:has(img)) {
.card:has(img) {
display: grid;
}
}
selector() 함수: 선택자 지원 여부를 체크합니다 (비교적 최신 기능).
6. 접두사 자동 처리
/* 표준 또는 접두사 중 하나라도 지원 */
@supports (text-stroke: 2px) or (-webkit-text-stroke: 2px) {
.outlined-text {
-webkit-text-stroke: 2px black;
text-stroke: 2px black; /* 미래를 위한 표준 */
}
}
JavaScript와의 연동
CSS만으로 부족할 때는 JavaScript를 사용할 수 있습니다.
CSS.supports() 메서드
MDN 문서에서 설명하는 두 가지 문법:
방법 1: 속성-값 분리
// 지원 여부 확인
if (CSS.supports('display', 'grid')) {
console.log('Grid 지원!');
// Grid 관련 JavaScript 코드
}
방법 2: 전체 표현식
// CSS @supports 문법과 동일
if (CSS.supports('(display: grid) and (gap: 1rem)')) {
console.log('Grid + gap 둘 다 지원!');
}
실전 예제: 동적 클래스 추가
// 기능 지원 여부에 따라 클래스 추가
const supportsGrid = CSS.supports('display', 'grid');
const supportsGap = CSS.supports('gap', '1rem');
document.body.classList.toggle('supports-grid', supportsGrid);
document.body.classList.toggle('supports-gap', supportsGap);
CSS:
/* 기본 스타일 */
.layout {
display: flex;
flex-wrap: wrap;
}
/* Grid 지원 시 */
.supports-grid .layout {
display: grid;
}
/* Grid + gap 둘 다 지원 시 */
.supports-grid.supports-gap .layout {
gap: 1rem;
}
브라우저 호환성 체크
const features = [
{ name: 'Grid', check: '(display: grid)' },
{ name: 'Flexbox gap', check: '(gap: 1rem)' },
{ name: 'aspect-ratio', check: '(aspect-ratio: 16/9)' },
{ name: ':has()', check: 'selector(:has(a))' }
];
features.forEach(({ name, check }) => {
const supported = CSS.supports(check);
console.log(`${name}: ${supported ? '✅' : '❌'}`);
});
// 출력 예시 (최신 Chrome):
// Grid: ✅
// Flexbox gap: ✅
// aspect-ratio: ✅
// :has(): ✅
추가: 폰트 체크
MDN 문서에 따르면, @supports는 폰트 기술과 형식도 체크할 수 있습니다.
font-format() 함수
/* WOFF2 지원 시에만 로드 */
@supports font-format(woff2) {
@font-face {
font-family: "CustomFont";
src: url("font.woff2") format("woff2");
}
}
/* WOFF2 미지원 시 WOFF 폴백 */
@supports not font-format(woff2) {
@font-face {
font-family: "CustomFont";
src: url("font.woff") format("woff");
}
}
지원 형식: woff, woff2, truetype, opentype, embedded-opentype, svg
font-tech() 함수
/* 컬러 폰트 지원 체크 */
@supports font-tech(color-COLRv1) {
.fancy-text {
font-family: "Bungee Spice", fantasy;
}
}
지원 기술:
color-COLRv1: 컬러 폰트 v1color-SVG: SVG 기반 컬러 폰트variations: 가변 폰트features-opentype: OpenType 기능
함정과 주의사항
실제로 사용하면서 주의해야 할 점들입니다.
함정 1: 괄호 필수!
/* ❌ 에러 - 괄호 없음 */
@supports display: grid {
/* 작동 안 함 */
}
/* ✅ 올바름 */
@supports (display: grid) {
/* 작동함 */
}
함정 2: 값까지 정확히 체크
/* ❌ 이건 체크 안 됨 */
@supports (display) {
/* 속성만 체크는 불가능 */
}
/* ✅ 속성과 값 모두 필요 */
@supports (display: grid) {
/* 특정 값 지원 체크 */
}
함정 3: 접두사는 각각 체크
/* ❌ 이렇게 하면 표준만 체크 */
@supports (transform: rotate(45deg)) {
.element {
-webkit-transform: rotate(45deg); /* 체크 안 됨 */
transform: rotate(45deg);
}
}
/* ✅ OR로 접두사도 함께 체크 */
@supports (transform: rotate(45deg)) or
(-webkit-transform: rotate(45deg)) {
.element {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
}
함정 4: 부분 지원 주의
어떤 속성은 기본은 지원하지만 특정 값은 미지원일 수 있습니다.
/* display는 지원하지만 display: grid는 미지원일 수 있음 */
@supports (display: grid) {
/* grid 값을 정확히 체크 */
}
함정 5: 성능 고려
/* ❌ 모든 요소에 중복 체크 */
.element1 {
@supports (display: grid) { /* 비효율적 */ }
}
.element2 {
@supports (display: grid) { /* 중복 */ }
}
/* ✅ 한 번만 체크 */
@supports (display: grid) {
.element1 { /* ... */ }
.element2 { /* ... */ }
}
함정 6: 코드 위치 제한
MDN 명시: “코드 최상단 또는 다른 조건부 at-rule 내부에만 사용 가능”
/* ✅ 최상단 - OK */
@supports (display: grid) {
.container { display: grid; }
}
/* ✅ @media 내부 - OK */
@media (min-width: 768px) {
@supports (display: grid) {
.container { display: grid; }
}
}
/* ❌ 선택자 내부 - 불가능 */
.container {
@supports (display: grid) {
/* 에러! */
}
}
실전 패턴: 점진적 향상
MDN 문서가 강조하는 “progressive enhancement(점진적 향상)” 전략입니다.
패턴 1: 모바일 우선 + 기능 향상
/* Level 1: 모든 브라우저 (모바일 포함) */
.layout {
display: block;
}
.layout > * {
margin-bottom: 1rem;
}
/* Level 2: Flexbox 지원 */
@supports (display: flex) {
.layout {
display: flex;
flex-direction: column;
}
}
/* Level 3: Grid 지원 */
@supports (display: grid) {
@media (min-width: 768px) {
.layout {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.layout > * {
margin-bottom: 0; /* Grid gap으로 대체 */
}
}
}
단계별 경험:
- 구형 모바일: 세로 블록 레이아웃
- Flexbox 브라우저: 유연한 레이아웃
- Grid 브라우저 + 큰 화면: 최고의 그리드 경험
패턴 2: 기능 조합 체크
/* 여러 기능이 필요한 경우 */
@supports (display: grid) and
(gap: 1rem) and
(grid-template-areas: "a b") {
.complex-layout {
display: grid;
gap: 1rem;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
}
패턴 3: 폴백 체인
/* 3단계 폴백 */
/* Level 1: 기본 (모든 브라우저) */
.card {
width: 100%;
padding-bottom: 75%; /* 4:3 비율 해킹 */
}
/* Level 2: object-fit 지원 */
@supports (object-fit: cover) {
.card img {
object-fit: cover;
width: 100%;
height: 100%;
}
}
/* Level 3: aspect-ratio 지원 (최선) */
@supports (aspect-ratio: 4 / 3) {
.card {
aspect-ratio: 4 / 3;
padding-bottom: 0; /* 해킹 제거 */
}
}
브라우저 호환성
MDN 문서에 따르면, @supports는 “Baseline Widely available” 등급입니다.
지원 현황
- ✅ Chrome: 28+ (2013년 7월)
- ✅ Firefox: 22+ (2013년 6월)
- ✅ Safari: 9+ (2015년 9월)
- ✅ Edge: 12+ (2015년 7월)
기준: 2015년 9월부터 광범위하게 사용 가능
미지원 브라우저 대응
/* @supports 자체를 지원하지 않는 브라우저는? */
/* 기본 스타일이 폴백 역할 */
.element {
display: block; /* @supports 미지원 브라우저용 */
}
/* @supports 지원 브라우저만 실행 */
@supports (display: grid) {
.element {
display: grid; /* 업그레이드 */
}
}
핵심: @supports 자체를 모르는 브라우저는 블록 전체를 무시하므로, 기본 스타일이 자동으로 폴백이 됩니다!
언제 사용할까?
✅ 사용하기 좋은 경우
- 최신 CSS 기능 도입
- Grid, aspect-ratio, gap 등
- 폴백이 필요한 경우
- 접두사 처리
- backdrop-filter, text-stroke 등
- 표준과 접두사 버전 모두 체크
- 레이아웃 전환
- Flexbox → Grid 업그레이드
- 구형 브라우저 지원 필수
- 실험적 기능 테스트
- 새로운 선택자 (:has, :is 등)
- 안전하게 점진적 도입
❌ 사용하지 않아도 되는 경우
- 광범위하게 지원되는 기능
- Flexbox (IE11 제외하면 안전)
- CSS 변수 (IE 버릴 수 있다면)
- 중요하지 않은 장식
- 그림자, border-radius 등
- 없어도 기능에 문제없음
- JavaScript로 해결 가능
- 복잡한 조건 체크
- 동적 기능 추가
완전한 실전 예제
모든 개념을 종합한 반응형 갤러리:
<!DOCTYPE html>
<html lang="ko">
<head>
<style>
/* ========================================
Level 1: 기본 (모든 브라우저)
======================================== */
.gallery {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
.gallery-item {
margin-bottom: 1rem;
background: #f0f0f0;
padding: 1rem;
}
/* ========================================
Level 2: Flexbox 지원
======================================== */
@supports (display: flex) {
.gallery {
display: flex;
flex-wrap: wrap;
gap: 1rem; /* Flexbox gap (비교적 최신) */
}
.gallery-item {
flex: 1 1 calc(33.333% - 1rem);
margin-bottom: 0;
}
}
/* ========================================
Level 2.5: Flexbox는 있는데 gap 없는 경우
======================================== */
@supports (display: flex) and (not (gap: 1rem)) {
.gallery {
margin: -0.5rem; /* 음수 마진 해킹 */
}
.gallery-item {
margin: 0.5rem;
}
}
/* ========================================
Level 3: Grid 지원 (최선)
======================================== */
@supports (display: grid) {
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
margin: 0; /* 음수 마진 해제 */
}
.gallery-item {
margin: 0;
flex: none; /* Flexbox 속성 리셋 */
}
}
/* ========================================
이미지 비율 처리
======================================== */
.gallery-item img {
width: 100%;
height: 200px;
object-fit: cover;
}
/* aspect-ratio 지원 시 더 나은 방법 */
@supports (aspect-ratio: 16 / 9) {
.gallery-item img {
aspect-ratio: 16 / 9;
height: auto; /* aspect-ratio가 높이 결정 */
}
}
/* ========================================
고급: :has() 선택자로 빈 갤러리 체크
======================================== */
@supports selector(:has(img)) {
.gallery:not(:has(.gallery-item)) {
display: none;
}
.gallery:not(:has(.gallery-item))::before {
content: "갤러리가 비어있습니다.";
display: block;
padding: 2rem;
text-align: center;
color: #999;
}
}
</style>
<script>
// JavaScript로 기능 감지 및 로깅
document.addEventListener('DOMContentLoaded', () => {
const features = {
'Flexbox': '(display: flex)',
'Flexbox gap': '(gap: 1rem)',
'Grid': '(display: grid)',
'aspect-ratio': '(aspect-ratio: 16/9)',
':has()': 'selector(:has(a))'
};
console.log('=== 브라우저 기능 지원 ===');
Object.entries(features).forEach(([name, query]) => {
const supported = CSS.supports(query);
console.log(`${name}: ${supported ? '✅' : '❌'}`);
// body에 클래스 추가
if (supported) {
document.body.classList.add(`supports-${name.toLowerCase().replace(/[():]/g, '')}`);
}
});
});
</script>
</head>
<body>
<div class="gallery">
<div class="gallery-item">
<img src="https://via.placeholder.com/300x200" alt="Image 1">
<h3>항목 1</h3>
</div>
<div class="gallery-item">
<img src="https://via.placeholder.com/300x200" alt="Image 2">
<h3>항목 2</h3>
</div>
<div class="gallery-item">
<img src="https://via.placeholder.com/300x200" alt="Image 3">
<h3>항목 3</h3>
</div>
<div class="gallery-item">
<img src="https://via.placeholder.com/300x200" alt="Image 4">
<h3>항목 4</h3>
</div>
<div class="gallery-item">
<img src="https://via.placeholder.com/300x200" alt="Image 5">
<h3>항목 5</h3>
</div>
<div class="gallery-item">
<img src="https://via.placeholder.com/300x200" alt="Image 6">
<h3>항목 6</h3>
</div>
</div>
</body>
</html>
정리하며
@supports는 CSS의 조건문입니다. 최신 기능을 안전하게 사용하면서 구형 브라우저 호환성을 유지할 수 있게 해줍니다.
핵심 요약
- 목적: 브라우저의 CSS 기능 지원 여부 확인
- 기본 문법:
@supports (property: value) { } - 연산자:
and,or,not - JavaScript:
CSS.supports()메서드 - 전략: 점진적 향상 (기본 → 향상)
실무 체크리스트
- 기본 폴백 먼저 작성
@supports로 최신 기능 추가- 괄호
( )필수 확인 - 복잡한 조건은 괄호로 우선순위 명시
- 접두사도
or로 함께 체크 - JavaScript
CSS.supports()활용 - 실제 브라우저에서 테스트
마지막 조언
좋은 웹사이트는 모든 브라우저에서 동일하게 보일 필요는 없지만, 모든 브라우저에서 작동해야 합니다.
@supports는 점진적 향상의 핵심 도구입니다. 최신 브라우저 사용자에게는 최고의 경험을, 구형 브라우저 사용자에게는 기본적인 기능을 제공하세요.
“이 기능 써도 될까?”라는 고민이 들 때, 이제는 @supports로 안전하게 도입할 수 있습니다!
댓글