CSS 構文エラー

CSS において、値が不正であったりセミコロンが欠落したりといったエラーが存在した場合、ブラウザー(または他のユーザーエージェント)は JavaScript のようにエラーを投げるのではなく、適切に修復します。ブラウザーは CSS 関連のアラートを表示したり、スタイル設定でエラーが発生したことを示したりはしません。ただ、不正な部分を破棄し、その後の有効なスタイルを構文解析します。これは CSS の機能であり、バグではありません。

このガイドでは、 CSS パーサーが不正な CSS を破棄する方法について説明します。

CSS パーサーのエラー

CSS にエラーを見つけると、ブラウザーのパーサーはエラーを含む行を無視し、最小限の CSS コードを破棄してから通常通りの CSS の構文解析処理に戻ります。「エラーの回復」とは、不正な部分を無視したりスキップしたりするだけのことです。

ブラウザーが不正なコードを無視するため、新しい CSS 機能を使用するときに、古いブラウザーにおいて何らかのコードが壊れることを心配することはありません。ブラウザーは新しい機能を認識しないかもしれませんが、それは問題ありません。エラーを発生させずに不正な部分を破棄することで、古い構文と新しい構文を同じルールセットで共存させることができます。例えば、以下の例を見てください。

css
div {
  display: inline-flex;
  display: inline flex;
}

display プロパティは、古い 1 つの値と複数キーワードの構文の両方を受け入れます。ブラウザーは新しい構文が有効と認識するまで古い構文でレンダリングし、この時点で新しい構文が古い構文を上書きします。ユーザーが古いブラウザーを使っている場合、有効な代替構文は新しい CSS を無効なものとみなすため、上書きされません。

エラーによってブラウザーが無視する CSS の種類や量は、エラーの種類によって異なります。一般的なエラーの状況を紹介します。

宣言、スタイル設定、アットルールなどを構文解析した後、ブラウザーは構文解析された部分を、その構文に対して期待される文法に照らして検査します。部分がその構文に対して期待される文法と一致しない場合、ブラウザーはそれを不正なものとみなし、無視します。

アットルールのエラー

@ 記号は、 CSS 仕様書では <at-keyword-token> として知られていますが、 CSS の at-rule の始まりを示します。アットルールが @ 記号で始まったら、パーサーから見て不正なものは何もないとみなされます。最初のセミコロン (;) または中括弧 ({) まではすべてアットルールの前置きの一部です。各アットルールの内容は、具体的なアットルールの文法規則に従って解釈されます。

文アットルール、例えば @import@namespace 宣言では、前置き部分だけからなります。通常アットルールの場合、セミコロンでアットルールがすぐに終わります。前置き部分がそのアットルールの文法と照らして不正であった場合、そのアットルールは無視され、ブラウザーは次のセミコロンが出現した後から CSS の構文解析を続けます。例えば、 @import 文以外の CSS 宣言( @charset@layer など)の後に @import アットルールがあった場合、その @import 宣言は無視されます。

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

パーサーがセミコロンの前に中括弧 ({) を見つけた場合、そのアットルールはブロックアットルールとして構文解析されます。ブロックアットルールは @font-face@keyframes のように入れ子アットルールとも呼ばれ、中括弧 ({}) で囲まれた宣言のブロックを持ちます。開始中括弧により、アットルールの前置きがどこで終わり、アットルールの本体がどこで始まるかをブラウザーが判断します。パーサーは対応するブロック((){}[] で囲まれたコンテンツ)を探しながら、他に対応する中括弧がない閉じ中括弧 (}) を探していきます。

アットルールが異なれば、それぞれ文法規則も異なり、記述子も異なり(ない場合もある)、アットルール全体が不正になる条件も異なります。各アットルールに期待される文法と、エラーがどのように処理されるかは、それぞれのアットルールのページで文書化されています。内容が不正だった場合の扱いは、エラーによって異なります。

例えば @font-face ルールは、 font-familysrc の両方の記述子が必要です。これらのどちらかが省略されたり無効であったりすると、 @font-face ルール全体が不正なものとなります。関係のない記述子がある場合、他に不正な値を持った有効なフォント記述子がある場合、 @font-face 入れ子ブロック内にプロパティスタイル宣言があった場合であっても、フォント宣言が無効になることはありません。フォント名とフォントのソースが含まれていて有効である限り、アットルール内の不正な CSS は無視されますが、 @font-face ブロックは構文解析されます。

@keyframe アットルールの文法は、 @font-face ルールの文法とは極めて異なり、エラーの種類が無視される対象に影響します。キーフレームルールでは、重要宣言(important フラグで示されるもの)とアニメーションできないプロパティは無視されますが、同じキーフレームセレクターブロックで宣言された他のスタイルには影響しません。不正なキーフレームセレクター(パーセント値が 0% 未満または 100% を超えるものや、 <number>% を省略したものなど)を記述している場合は、キーフレームセレクターリストが無効となるため、そのスタイルブロックは無視されます。キーフレームセレクターが不正な場合、不正なセレクターのスタイルブロックだけが無効になります。また、 2 種類のキーフレームセレクターブロックの間にスタイルを記述すると、 @keyframe のアットルール全体が無効になります。

アットルールによっては、常に有効なものもあります。 @layer アットルールには通常形と入れ子形があります。 @layer 文は前置き部分だけで、セミコロンで終わります。一方、入れ子構文では、レイヤースタイルは中括弧の間で入れ子になっており、中括弧は前置きの後に来ます。閉じ中括弧を省略すると論理エラーになることはありますが、構文エラーにはなりません。 @layer の閉じ中括弧がない場合、閉じ中括弧があるべき場所にスタイルが来ると、アットルールの前置きで定義されたカスケードレイヤーにあるものとして解釈されます。構文エラーがないため、この CSS は有効です。構文エラーが発生すると、名前付きまたは無名のレイヤーが空になる可能性がありますが、レイヤーは作成されます。

セレクターリストのエラー

セレクターの書き間違いの形はたくさんありますが、セレクターリストが不正になるのは不正なセレクターだけです(無効なセレクターリストを参照してください)。

クラスid要素型セレクターで、存在しないクラス、id、要素(またはカスタム要素)を記述した場合、それは論理エラーかもしれませんが、構文エラーではありません。しかし、擬似クラスや擬似要素にスペルミスがあった場合は、不正なセレクターが作成された可能性があり、パーサーが対処すべきエラーとなります。

セレクターリストに不正なセレクターが含まれていた場合、そのスタイルブロック全体が無視されます。例外もあります。不正なセレクターが :is または :where 擬似クラス(寛容なセレクターリストを受け入れる)の中にあった場合、または未知のセレクターが -webkit- 接頭辞付き擬似要素であった場合、未知のセレクターだけがどのセレクターにも一致しないものとして無視されます。セレクターリストは不正なものにはなりません。

これらの例外以外では、セレクターリストで単一の無効な、あるいは対応していないセレクターがあると、ルール全体が不正なものとなり、セレクターブロック全体が無視されます。ブラウザーは閉じ中括弧を探し、その点から構文解析を続けます。

-webkit- の例外

セレクターやプロパティ名(および値)にブラウザー固有の接頭辞を多用することによる互換性問題のため、ブラウザーは、擬似要素のうち大文字と小文字を区別しない -webkit- 接頭辞で始まり () で終わらないものをすべて有効なものとして扱うことで、セレクターリストが不正と扱われすぎることを避けています。

これは、 ::-webkit-works-only-in-samsung のような擬似要素が、コードがどのブラウザーで実行されているかにかかわらず、セレクターリストを不正なものにしないという意味です。このような場合、擬似要素はブラウザーに認識されなかったり対応していなかったりしても、セレクターリスト全体とそれに関連するスタイルブロックが無視されることにはなりません。一方、 ::-webkit-imaginary-function() という関数記法を伴う未知の接頭辞つきセレクターがあると、セレクターリスト全体が不正なものとなり、ブラウザーはそのセレクターブロック全体を無視します。

CSS 宣言ブロック内のエラー

宣言ブロック内の CSS プロパティと値に関しては、プロパティか値のどちらかが不正な場合、そのプロパティと値のペアは無視され、破棄されます。ユーザーエージェントが宣言のリストを構文解析するとき、未知の構文がどこかにあると、ブラウザーは現在の宣言だけを破棄します。そして、次のセミコロンか閉じ中括弧のどちらかが現れた後、 CSS の構文解析を続けます。

この例にはエラーが含まれています。パーサーはエラー(とコメント)を無視し、セミコロンに出会うまで進み、それから構文解析を再開します。

css
p {
/* セミコロンがないため不正な構文 */
  border-color: red
  background-color: green;

/* 有効な構文だが、おそらく論理エラー */
  border-width: 100vh;
}

このセレクターブロックの最初の宣言が不正なのは、セミコロンが欠落しており、かつその宣言がセレクターブロックの最後の宣言ではないからです。セミコロンが抜けているプロパティは無視され、それに続くプロパティと値のペアも同様に無視されます。なぜなら、ブラウザーはセミコロンまたは閉じ括弧に遭遇した後からのみ構文解析を続けるからです。具体的には、 border-color 値は red background-color: green; として解釈されますが、これは有効な <color> 値ではありません。

border-width100vhという値はおそらく間違いでしょうが、エラーではありません。構文的には有効なので、構文解析され、セレクターに一致する要素に適用されます。

ベンダー接頭辞

ベンダー接頭辞のついたプロパティ名やプロパティ値は、ブラウザーが理解できない場合、不正なものとして扱われ、無視されます。不正なプロパティや値を持つ個々のルールだけが無視されます。パーサーは、次のセミコロンまたは閉じ中括弧を探して、そこから構文解析を続けます。

以下のような古い CSS を見かけることがあるかもしれません。

css
/* 接頭辞付きの値 */
.wrapper {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  display: block flex;
}
/* 接頭辞付きのプロパティ */
.rounded {
  -webkit-border-radius: 50%;
  -moz-border-radius: 50%;
  -ms-border-radius: 50%;
  -o-border-radius: 50%;
  border-radius: 50%;
}

この例では、各ブロックの最後の宣言 — display: flex;border-radius: 50%; — は、すべてのブラウザーで有効です。カスケードには出現順ルールがあるため、ブラウザーは理解できる接頭辞の宣言を適用し、それらの値を標準の接頭辞なしバージョンで上書きします。

メモ: 接頭辞の付いたプロパティやプロパティ値を含めることは、可能な限り避けてください。どうしても使用する必要がある場合は、接頭辞付きバージョンを接頭辞なしバージョンの前に宣言してください。

末尾の自動終了におけるエラー

ルール、宣言、関数、文字列、コメントが終了せずにスタイルシートが終了すると、パーサーは閉じていないものを自動的にすべて閉じます。

メモ: これは、外部スタイルシート、 HTML の <style> 要素内のセレクターブロック、style 属性内のインラインルールに当てはまります。

最後のセミコロンからスタイルシートの終わりまでの内容が有効であれば、たとえ不完全であっても、 CSS は正常に構文解析されます。例えば、 <style> を閉じる前に @keyframe 宣言を閉じなかった場合でも、アニメーションは有効になります。

html
<style>
@keyframes move {
  100% {
    transform: translatex(100vw)
</style>

ここでは move アニメーションは有効になります。 CSS の記述が適切に閉じられなくても、その記述が不正なものになるとは限りません。だからと言って、 CSS の寛容な性質を利用してはいけません。文とスタイルブロックは常にすべて閉じましょう。そうすることで、 CSS が読みやすくなり、維持しやすくなります。また、ブラウザーが意図通りに CSS を構文解析できるようになります。

閉じていないコメント

閉じていないコメントは論理エラーであり、構文エラーではありません。コメントが /* で始まっているが閉じていない場合、それに続くコメントの区切り文字 (*/)、またはスタイルシートの終わりのどちらかが先に来るまで、すべての CSS コードがコメントの一部になります。コメントが閉じられていなくても CSS が不正なものになるわけではありませんが、区切り文字 (/*) に続く CSS は無視されます。

html
<style>
  /* このコメントは閉じていない
  @keyframes move {
    0% {transform: translatex(0);}
    100% {transform: translatex(100vw);}
  }
</style>
<p style="/* これも閉じていないコメント">HTML として構文解析される</p>

この例では、 2 つの CSS コメントは閉じていませんが、 </style> 閉じタグが 1 つ目のコメントを閉じ、 style 属性の閉じ引用符が 2 つ目のコメントを閉じています。

文法チェック

それぞれの宣言、スタイルルール、アットルールなどを構文解析した後、ユーザーエージェントは文法がその宣言のルールに従っているかどうかを調べます。例えば、プロパティ値が間違ったデータ型であったり、記述子が記述されているアットルールに対して有効でない場合など、期待される文法に一致しないコンテンツは不正なものとみなされ、無視されます。

それぞれの CSS プロパティは特定のデータ型を受け入れます。例えば、 background-color プロパティは、有効な <color> か CSS グローバルキーワードのどちらかを受け入れます。プロパティに代入された値が background-color: 45deg のように不正な型である場合、その宣言は不正であるため無視されます。

不正なカスタムプロパティ

カスタムプロパティは一般的に、宣言されたときは有効ですが、アクセスしたときに不正な CSS になる可能性があります。つまり、その値の型を受け入れないプロパティの値として(var() 関数を介して)使用される可能性があります。ブラウザーは、プロパティが消費される場所に関係なく、遭遇したときに各カスタムプロパティを構文解析します。

通常、プロパティ値が不正であった場合、その宣言は無視され、プロパティは最後に有効であった値に戻ります。しかし、計算されたカスタムプロパティの値が不正な場合は、少し異なる形で動作します。

var() による代入が無効な場合、宣言は無視されず、プロパティの初期値または継承値が代わりに使用されます。プロパティには新しい値が設定されますが、期待した値ではない可能性があります。

この動作を示す例を見ていきましょう。

css
:root {
  --theme-color: 45deg;
}
body {
  background-color: var(--theme-color);
}

上記のコードでは、カスタムプロパティの宣言は有効です。また、 background-color の宣言も計算時点では有効です。しかし、ブラウザーが var(--theme-color) のカスタムプロパティの値である 45degbackground-color プロパティの値として代入すると、文法は不正なものになります。 <angle> は有効な background-color 値ではありません。この場合、宣言は不正なものとして無視されるわけではありません。カスタムプロパティの型が誤っている場合、プロパティが継承可能であれば、値はその親から継承されます。プロパティが継承可能でない場合、初期値が使用されます。 background-color の場合、プロパティの値は継承される値ではないので、初期値の transparent が使用されます。

カスタムプロパティが代替する方法をうまく制御するには、 @property アットルールを使用してプロパティの初期値を定義してください。

css
@property --theme-color {
  syntax: "<color>";
  inherits: false;
  initial-value: rebeccapurple;
}

関連情報