Syntax/Error handling

김동현·2026년 3월 21일

mdn 학습 번역 - CSS

목록 보기
158/190

안녕하세요! 프론트엔드 강사입니다.

오늘은 정말 중요한, 하지만 의외로 많은 분들이 정확히 모르는 주제인 'CSS의 에러 처리(Error Handling) 방식'에 대해 MDN 공식 문서를 통해 알아보겠습니다.

자바스크립트 코드를 짜다가 오타가 하나라도 나면 화면 전체가 하얗게 변하거나 콘솔에 시뻘건 에러 메시지가 뜨며 프로그램이 멈춰버리죠? 하지만 CSS는 이상하게도 오타가 나거나 존재하지 않는 속성을 써도 에러 창 하나 띄우지 않고 화면이 멀쩡하게(?) 렌더링됩니다. 대체 왜 그런 걸까요? 이것이 버그일까요?

아닙니다! 이것은 웹의 하위 호환성을 지키기 위해 아주 치밀하게 설계된 CSS만의 특별한 '생존 전략'입니다. 지금부터 그 비밀을 하나씩 풀어보겠습니다.


CSS 에러 처리 (CSS error handling)

CSS 코드 내에 유효하지 않은 값(invalid value)이나 세미콜론 누락 같은 에러가 존재할 때, 브라우저(또는 다른 사용자 에이전트)는 자바스크립트처럼 에러를 던지며(throw an error) 멈추는 대신, 아주 우아하게 복구(gracefully recover)합니다.

브라우저는 CSS와 관련된 경고창(alerts)을 띄우지도 않고, 스타일에 에러가 발생했다는 어떤 표시도 하지 않습니다. 브라우저는 그저 유효하지 않은 콘텐츠를 버려버리고(discard), 그 뒤에 이어지는 유효한 스타일들을 계속해서 파싱(분석)해 나갑니다. 이것은 버그가 아니라, CSS의 의도된 기능(feature)입니다.

이 가이드에서는 CSS 파서(parsers)가 유효하지 않은 CSS를 어떻게 버리는지에 대해 다룹니다.


이 문서의 목차


CSS 파서 에러 (CSS parser errors)

CSS 에러를 발견했을 때, 브라우저의 파서는 에러가 포함된 줄(line)을 무시합니다. 이때 최소한의 CSS 코드만을 버린 후, 다시 정상적으로 CSS를 파싱하는 작업으로 돌아갑니다. 여기서 말하는 "에러 복구(error recovery)"란 그저 유효하지 않은 콘텐츠를 무시하거나 건너뛰는 것을 의미합니다.

브라우저가 유효하지 않은 코드를 무시한다는 사실 덕분에, 우리는 구형 브라우저에서 화면이 깨질까 봐 걱정할 필요 없이 새로운 CSS 기능들을 마음껏 사용할 수 있습니다. 구형 브라우저는 새로운 기능을 인식하지 못하겠지만, 그래도 괜찮습니다. 에러를 뿜어내는 대신 유효하지 않은 콘텐츠를 그냥 버리기 때문에, 동일한 규칙 세트(ruleset) 안에 구형 문법과 신형 문법을 공존시킬 수 있죠. (단, 구형을 먼저 쓰고 신형을 나중에 써야 한다는 순서는 꼭 지켜야 합니다.)

예를 들어볼까요:

div {
  display: inline-flex; /* 구형 문법 (현재 널리 쓰임) */
  display: inline flex; /* 신형 문법 (Multi-keyword syntax) */
}

display 속성은 기존의 단일 값 문법과 새로운 다중 키워드 문법(multi-keyword syntax)을 모두 허용합니다.

신형 문법을 아직 모르는 구형 브라우저는 첫 번째 줄인 display: inline-flex;를 읽고 적용합니다. 그리고 두 번째 줄을 읽었을 때 "어? inline flex는 내가 모르는 문법인데?" 하고는 에러를 내는 게 아니라 그 줄을 그냥 쿨하게 무시해 버립니다! 결과적으로 구형 브라우저에서도 inline-flex가 안전하게 적용되죠.
반면, 신형 문법을 아는 최신 브라우저는 첫 번째 줄을 읽고 적용한 뒤, 두 번째 줄도 유효하다는 것을 알고 기존 값을 덮어써 버립니다(override).

👨‍🏫 강사님의 꿀팁:
이 원리가 바로 프론트엔드 실무에서 크로스 브라우징(Cross Browsing)을 해결하는 핵심 테크닉, '폴백(Fallback)'의 비밀입니다! CSS는 언제나 위에서 아래로 흐르며(Cascade), 자신이 이해하지 못하는 코드는 뱉어버린다는 점을 이용해, 안전한 옛날 코드를 먼저 써두고 그 아래에 최신 코드를 덧붙이는 것이죠.

에러의 종류에 따라 브라우저가 무시하는 CSS의 종류와 범위가 달라집니다. 흔하게 발생하는 에러 상황들은 다음과 같습니다:

브라우저는 선언, 스타일 규칙, At-규칙 등을 파싱한 후, 해당 구조에 대해 자신이 기대하는 문법(grammar)과 일치하는지 검사합니다. 만약 기대하는 문법과 일치하지 않으면 브라우저는 이를 유효하지 않다고 판단하고 무시해 버립니다.


At-규칙 에러 (At-rule errors)

@ 기호는 CSS 명세서에서 <at-keyword-token>으로 불리며, CSS At-규칙(at-rule)의 시작을 알립니다. 파서의 관점에서 보면 @ 기호로 시작된 후부터는 처음 만나는 세미콜론(;)이나 여는 중괄호({) 전까지의 모든 내용(서문, prelude)은 일단 무효하지 않은 것으로 간주됩니다. 각 At-규칙의 내용은 그 특정 At-규칙의 문법 규칙에 따라 해석됩니다.

@import@namespace 같은 '명령문(Statement) At-규칙'은 오직 서문(prelude)만 가집니다. 세미콜론은 이 규칙을 즉시 끝내버리죠. 만약 이 서문의 내용이 해당 At-규칙의 문법에 어긋난다면, 그 At-규칙 전체는 무시되고 브라우저는 다음 세미콜론 이후부터 다시 파싱을 이어갑니다.

예를 들어, @import 규칙은 반드시 문서의 맨 위에 와야 하는데, 일반적인 CSS 선언문 뒤에 @import가 오면 문법 위반이므로 해당 @import는 통째로 무시됩니다.

@import "assets/fonts.css" layer(fonts);
@namespace svg url("[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)");

만약 파서가 세미콜론보다 여는 중괄호({)를 먼저 만나면, 그것은 '블록(Block) At-규칙'으로 파싱됩니다. @font-face@keyframes 같은 블록 At-규칙들은 중괄호({}) 안에 선언 블록을 담고 있습니다. 브라우저는 짝이 맞는 닫는 중괄호(})를 찾을 때까지 쭉 파싱을 진행하여 블록을 닫습니다.

At-규칙마다 문법이 다르고, 에러가 발생했을 때 규칙 전체가 무효화되는 조건도 다릅니다. 예를 들어:

  • @font-face 규칙은 반드시 font-familysrc 속성을 포함해야 합니다. 둘 중 하나라도 없거나 틀리면 껍데기인 @font-face 블록 전체가 무효화됩니다. 하지만 이름과 소스(src)가 정상적으로 있다면, 그 안에 엉뚱한 속성(예: color: red;)을 넣어도 그 엉뚱한 속성 한 줄만 무시될 뿐 @font-face 규칙 자체는 살아서 정상 작동합니다.
  • @keyframes 규칙 내부에 잘못된 선택자(예: -10% {}100 {}처럼 %를 빼먹은 경우)를 넣으면 해당 선택자 블록만 무시되고 전체 애니메이션은 유지됩니다. 하지만 키프레임 블록들 사이에 엉뚱한 일반 스타일을 넣으면 @keyframes 전체가 망가져 무시됩니다.

선택자 목록에서의 에러 (Errors in selector lists)

선택자를 잘못 작성하는 방법은 수없이 많지만, 오직 '유효하지 않은 선택자(invalid selectors)'만이 선택자 목록 전체를 무효화시킵니다.

만약 여러분이 문서에 존재하지 않는 클래스(.non-exist)나 ID(#no-way)를 CSS에 썼다면, 이건 논리적인 실수일 뿐 '문법 에러(syntax error)'는 아닙니다. 브라우저는 그냥 "음, 매칭되는 애가 없네" 하고 정상적으로 파싱합니다. 하지만, 가상 클래스(pseudo-class)나 가상 요소(pseudo-element)의 이름에 오타를 낸다면 (예: :hovver) 그것은 유효하지 않은 선택자가 되어 파서가 에러로 처리합니다.

쉼표(,)로 구분된 선택자 목록 중 단 하나라도 유효하지 않은 선택자가 섞여 있다면, 그 안에 있는 스타일 블록 전체가 깡그리 무시됩니다!

👨‍🏫 강사님의 꿀팁 & 매우 중요한 주의사항:
이거 진짜 실무에서 엄청 자주 겪는 참사입니다!
h1, h2, h3, :my-custom-pseudo { color: blue; }
이렇게 적으면, 앞의 h1, h2, h3는 멀쩡한데 뒤의 요상한 가상 클래스 하나 때문에 브라우저가 "이 블록 전체가 에러네!" 하고 color: blue;를 통째로 날려버립니다! 그래서 브라우저 전용 벤더 프리픽스(-webkit-, -moz-)가 붙은 선택자들을 쉼표로 한 번에 묶어 쓰면, 다른 브라우저에서는 전체가 깨지는 대참사가 발생할 수 있습니다. 각 브라우저 전용 선택자는 귀찮더라도 블록을 따로 분리해서 써주어야 안전합니다.

단, :is():where() 가상 클래스는 '관대한(forgiving)' 선택자 목록을 허용하기 때문에, 그 안에서 오타를 내면 틀린 것만 쏙 빼고 정상적인 것들은 살려줍니다.

-webkit- 예외 (The -webkit- exception)

과거에 브라우저 전용 접두사(Vendor prefixes)를 너무 남용하던 시절의 유산(legacy) 때문에, 최신 브라우저들은 쉼표로 묶인 선택자 리스트 전체가 날아가는 것을 막기 위해 특별한 예외를 하나 두었습니다.
바로 대소문자 구분 없이 -webkit-으로 시작하고 ()로 끝나지 않는 모든 가상 요소(pseudo-elements)는 일단 '유효한 것'으로 취급해 줍니다.

즉, Firefox 브라우저에서 ::-webkit-works-only-in-samsung 이라는 선택자를 만나도 "내가 모르는 거지만, 벤더 프리픽스니까 일단 눈감아 줄게" 하고 그 블록 전체를 날려버리지 않습니다.


CSS 선언 블록 내부의 에러 (Errors within CSS declaration blocks)

선언 블록({ }) 내부에 있는 '속성(property)'이나 '값(value)' 둘 중 하나라도 유효하지 않다면, 해당 '속성-값' 쌍(pair) 딱 한 줄만 무시되고 버려집니다. 파서는 다음번 세미콜론(;)이나 닫는 중괄호(})를 만날 때까지 전진한 뒤, 그곳에서부터 아무 일 없었다는 듯이 파싱을 다시 시작합니다.

아래 예제 코드에는 아주 흔한 에러가 숨어 있습니다:

p {
  /* 세미콜론 누락으로 인한 문법 에러 (Invalid syntax) */
  border-color: red
  background-color: green;

  /* 문법은 맞지만 논리적으로 말이 안 되는 에러 (Logic error) */
  border-width: 100vh;
}

첫 번째 선언이 유효하지 않은 이유는 red 뒤에 세미콜론(;)이 빠졌기 때문입니다. 브라우저는 세미콜론이나 닫는 괄호를 만나기 전까지를 한 덩어리의 값으로 인식합니다. 즉, 브라우저는 이것을 border-color: red background-color: green; 이라는 거대한 한 줄의 값으로 읽어들입니다. 당연히 이것은 올바른 <color> 값이 아니므로, border-colorbackground-color 두 속성이 한꺼번에 묶여서 버려집니다. (배경색이 초록색으로 안 변하게 되죠!)

반면에 border-width: 100vh; (테두리 두께가 화면 전체 높이)는 여러분의 의도와는 다른 명백한 실수(논리적 에러)일 확률이 높지만, CSS 문법상 길이 단위(vh)를 쓴 완벽히 유효한 코드이므로 파서가 에러로 잡지 않고 그대로 화면에 적용해 버립니다.

벤더 프리픽스 (Vendor prefixes)

브라우저가 이해하지 못하는 벤더 프리픽스 속성 이름이나 값(예: -moz-border-radius)도 에러로 취급되어 그 한 줄만 깔끔하게 무시됩니다. 파서는 다음 세미콜론을 찾고 파싱을 재개합니다.

/* Prefixed properties */
.rounded {
  -webkit-border-radius: 50%; /* 크롬 옛날 버전 적용 */
  -moz-border-radius: 50%;    /* 파이어폭스 옛날 버전 적용 */
  -ms-border-radius: 50%;     /* IE 옛날 버전 적용 */
  -o-border-radius: 50%;      /* 오페라 옛날 버전 적용 */
  border-radius: 50%;         /* 표준 문법: 최신 브라우저는 이걸로 덮어씁니다! */
}

참고 (Note):
요즘은 자동화 도구(Autoprefixer 등)가 알아서 처리해 주지만, 수동으로 적어야 한다면 반드시 표준(프리픽스가 없는) 속성을 가장 마지막 줄에 적어야 합니다! 브라우저는 위에서 아래로 읽으며 캐스케이딩(Cascading) 규칙에 따라 마지막에 읽은 값을 덮어쓰기 때문입니다.


자동 닫힘 처리 에러 (Errors with auto-closed endings)

만약 스타일 시트가 끝이 났는데 특정 규칙, 함수, 문자열, 혹은 주석이 아직 열려있는 상태로 끝나지 않았다면(예: 닫는 괄호를 빼먹음), 파서는 알아서 빠진 닫기 기호들을 채워 넣어서 열려있던 모든 것을 자동으로 닫아버립니다.

만약 마지막 세미콜론과 스타일 시트의 끝 사이에 있는 코드가 불완전하더라도 문법적으로 유효하다면, CSS는 정상적으로 파싱됩니다. 예를 들어 <style> 태그를 닫기 전에 @keyframes의 닫는 중괄호(})를 빼먹었더라도 애니메이션은 정상적으로 작동합니다!

<style>
@keyframes move {
  100% {
    transform: translateX(100vw)
  /* 중괄호 2개와 세미콜론이 빠졌지만, 브라우저가 알아서 닫아줘서 작동함! */
</style>

브라우저가 이렇게 너그럽게 닫아주긴 하지만, 이런 CSS의 관대한 성질(forgiving nature)을 일부러 악용하지는 마세요! 항상 모든 구문과 괄호는 짝을 맞춰서 완벽하게 닫아주는 습관을 들이는 것이 코드를 읽기 쉽고 유지보수하기 좋게 만들어줍니다.

닫히지 않은 주석 (Unclosed comments)

주석을 닫지 않는 것은 구문 에러(syntax error)가 아니라 논리적 에러(logic error)입니다. /*로 주석을 시작했는데 실수로 */를 적지 않으면, 브라우저는 그 뒤에 나오는 모든 CSS 코드를 다 주석(메모)으로 취급해 버립니다! (다음 */를 만나거나 파일이 끝날 때까지 말이죠). 이 때문에 여러분이 열심히 짠 CSS 코드가 몽땅 화면에서 사라지는 마술을 경험하게 됩니다.


문법 검사 (Grammar check)

파싱을 마친 후 브라우저는 각 선언, 스타일 규칙, At-규칙이 정해진 문법을 잘 지키고 있는지 검사합니다. 기대되는 문법과 다르면 가차 없이 유효하지 않은 것으로 간주하고 무시합니다.

각 CSS 속성은 받을 수 있는 고유한 데이터 타입이 정해져 있습니다. 예를 들어 background-color<color> 타입이나 transparent 같은 글로벌 키워드만 받을 수 있죠. 만약 background-color: 45deg; 처럼 각도 값을 넣으면 문법 검사에서 탈락하여 해당 줄은 쿨하게 무시됩니다.

유효하지 않은 커스텀 속성 (Invalid custom properties)

CSS 변수라고 불리는 '커스텀 속성(Custom properties, --var-name)'은 선언될 당시에는 어떤 값이 들어가든 일단 '유효한 것'으로 대우받습니다. 하지만 이 변수를 var() 함수를 통해 꺼내서 다른 속성에 집어넣을 때(accessed), 비로소 문법 검사가 일어납니다.

일반 속성들은 에러가 나면 해당 줄이 무시되고 '그전에 선언되었던 유효한 값(last valid value)'으로 돌아가지만, 커스텀 속성 에러는 작동 방식이 조금 다릅니다.

var()로 꺼내온 값이 문법에 맞지 않을 때, 그 줄이 무시되는 것이 아니라 해당 속성의 초기값(initial value)이나 상속된 값(inherited value)으로 강제로 세팅되어 버립니다.

:root {
  --theme-color: 45deg; /* 여기선 각도를 변수로 저장함 (에러 아님) */
}
body {
  /* 배경색에 각도를 넣었으니 에러 발생! 
     하지만 이 줄이 무시되는게 아니라, 배경색의 초기값인 'transparent(투명)'으로 리셋되어 버림! */
  background-color: var(--theme-color); 
}

이런 예기치 못한 초기화(reset) 현상을 방지하고 싶다면, CSS Houdini의 @property at-규칙을 사용해서 커스텀 속성의 타입(syntax)과 에러 시 돌아갈 안전한 기본값(initial-value)을 명시적으로 지정해 주면 완벽하게 해결할 수 있습니다!

@property --theme-color {
  syntax: "<color>"; /* 색상만 들어올 수 있다고 선언! */
  inherits: false;
  initial-value: rebeccapurple; /* 엉뚱한 값이 들어오면 이 보라색으로 렌더링됨 */
}

같이 보기 (See also)


MDN 개선에 도움을 주세요 (Help improve MDN)

이 페이지가 도움이 되었나요? (Was this page helpful to you?)

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


수고하셨습니다! 왜 CSS 쉼표 리스트 안에서 오타를 내면 전체가 망가지는지, 세미콜론을 빼먹으면 왜 그다음 줄까지 같이 적용이 안 되는지 이제 확실하게 이해하셨죠? 에러가 났을 때 브라우저가 어떻게 무시하고 넘어가는지 원리를 파악해 두면, 디버깅 속도가 훨씬 빨라질 겁니다!

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

0개의 댓글