Blazorのb-属性(b-xxxxxxxxxx)はなぜ付く?CSS Isolationの仕組みと消す方法・HTMLバリデーター対策

Blazorでページを表示すると、各HTML要素に b-xxxxxxxxxx のような謎の属性が勝手に付与され、W3CのHTMLバリデーターがエラー扱いして困ることがあります。これは不具合ではなく、BlazorのCSS分離(CSS isolation)の仕様です。本記事では、仕組みを押さえたうえで「消す」「残す」「バリデーションと両立する」現実的な対処法を具体手順つきで整理します。

目次

結論:b-xxxxxxxxxx はBlazorのCSS分離(CSS isolation)が付けるスコープ識別子

BlazorのCSS分離(CSS isolation)は、コンポーネント単位でCSSをスコープ(適用範囲)させるための仕組みです。Example.razor と同名の Example.razor.css を用意すると、そのCSSは基本的に「そのコンポーネントがレンダリングしたHTML」にだけ当たるように処理されます。

そのスコープを成立させるために、Blazorはビルド時に次の2つを行います(重要ポイントです)。

  • コンポーネントが出力するHTML要素へ、スコープ識別子(例:b-3xxtam6d07)を属性として付与する
  • .razor.css 内のCSSセレクタを、上の属性に一致するよう書き換えて、{ASSEMBLY}.styles.css にまとめる

Microsoft Learnの公式ドキュメントでも、b-{STRING} 形式の属性が付与されること、そして {ASSEMBLY}.styles.css 側が h1[b-xxxx] のような形になることが明記されています(CSS isolation bundlingの項)。ASP.NET Core Blazor CSS isolation(Microsoft Learn)

具体例(イメージ)です。

<!-- 生成されるHTML(例) -->
<h1 b-3xxtam6d07>Scoped CSS Example</h1>

<!-- 生成されるCSS(例:{AssemblyName}.styles.css 内) -->
h1[b-3xxtam6d07] {
  color: brown;
}

このように、HTMLとCSSの両方が「同じスコープ識別子」をキーにして結びつくため、属性が消える=スコープが成立しないという関係になります。

なぜW3C HTMLバリデーターがエラーにするのか(ブラウザは基本的に無視する)

W3CのHTMLバリデーターは、仕様に定義されていない属性を「許可されない属性」として指摘することがあります。一方でブラウザは、未知の属性が付いていてもDOMとして扱えますし、レンダリングも通常は壊れません。

つまり、バリデーターの指摘=動作不良ではありません。困るのは次のようなケースです。

  • CIでHTML検証を必須にしていて、エラー扱いでビルドが落ちる
  • 納品物として「バリデーションエラーゼロ」を要求される
  • 自社の品質ゲートで、未知属性を禁止している

このタイプの要件がある場合は、後述する「CSS分離をやめる」か「スコープ属性をdata-*へ変更して両立する」を検討するのが現実的です。

<DisableScopedCssBundling>true</DisableScopedCssBundling> では消えない理由

よくある誤解がここです。<DisableScopedCssBundling>true</DisableScopedCssBundling> は、CSS分離そのもの(= b-属性の付与)を止める設定ではありません。

公式ドキュメントでは、DisableScopedCssBundling は「分離CSSを実行時にどうpublish/loadするか(自動バンドルをどうするか)」をオプトアウトするためのプロパティ、と説明されています。つまり、バンドルや読み込みの責務を“自分側のツール/プロセスに渡す”設定です。Disable automatic bundling(Microsoft Learn)

内部的には概ね次の分離で考えると理解しやすいです。

工程何が起きるか主な成果物/影響
スコープ付与(CSS isolationの核)HTMLにスコープ属性を付け、CSSセレクタもそれに合わせて書き換えるb-xxxxxxxxxx がHTMLに付く/CSSが [b-xxxx] を含む形になる
バンドル・配信(bundling)書き換え後のCSSをまとめ、静的アセットとして参照できるようにする{AssemblyName}.styles.css の生成・参照

DisableScopedCssBundling は後者の「自動バンドル・配信」を止める方向に働くため、前者の「スコープ属性付与」は残り、結果として b- 属性も残ります。

対処方針は3択:消す/受け入れる/data-*化して両立する

まずは、目的(HTMLをきれいにしたいのか、CSSの保守性を重視するのか、CIでエラーゼロが必須なのか)で選びます。

方針HTMLバリデーションCSSの保守性おすすめ度
A:CSS分離をやめる(b-属性を出さない)通しやすい大規模だと衝突しやすい「エラーゼロ必須」なら強い
B:CSS分離は維持(b-属性は仕様として受け入れる)ツール次第で警告/エラーが残る衝突を避けやすく、部品化に強い基本はこれが王道
C:CSS分離は維持しつつスコープ属性をdata-*へ変更通しやすい(data-*は拡張用途として標準的)維持できる(ただし命名・運用ルールが必要)「品質ゲート+保守性」両方欲しい人向け

以降、A→B→Cの順に具体手順を解説します。

方法A:CSS分離をやめて b- 属性を出さない(最も確実)

「HTMLをきれいにする」が最優先なら、これが一番確実です。ポイントは“分離CSSに依存しているスタイルを必ずグローバル側へ移す”ことです。<ScopedCssEnabled>false</ScopedCssEnabled> だけ先に入れると、.razor.css で書いていたスタイルが消えて見た目が崩れます。

公式ドキュメントでも、プロジェクト単位でCSS分離を無効化する方法として <ScopedCssEnabled>false</ScopedCssEnabled> が案内されています。Disable CSS isolation(Microsoft Learn)

手順:グローバルCSSへ移す

  • *.razor.css(例:MainLayout.razor.css)に書いていたCSSを、グローバルCSS(例:wwwroot/css/app.css や wwwroot/app.css)へ移動します。
  • 移動後、見た目が維持されていることを確認します(ここで崩れるなら移し漏れがある可能性が高いです)。
  • プロジェクトファイル(.csproj)に以下を追加します。
<PropertyGroup>
  <ScopedCssEnabled>false</ScopedCssEnabled>
</PropertyGroup>

手順:{AssemblyName}.styles.css の参照を削除する

CSS分離を無効化したら、テンプレートが自動で入れている {AssemblyName}.styles.css の参照(<link ...>)は不要になります。参照を残しても致命的でないことはありますが、構成によっては「存在しないCSSを参照」「空のCSSを参照」などの違和感が出るため、整理しておくのが安全です。

参照場所はアプリの種類(テンプレート)で変わります。Microsoft Learnでも、Blazor Web Apps と Standalone Blazor WebAssembly で参照方法が違う例が示されています。CSS isolation bundling(Microsoft Learn)

アプリ種別{AssemblyName}.styles.css を参照している代表的な場所削除するもの
Blazor Web App(.NET 8以降の新テンプレート系)App.razor の <head> 付近(テンプレートにより @Assets[...] 形式)<link href="..." rel="stylesheet">
Standalone Blazor WebAssemblywwwroot/index.html<link href="{AssemblyName}.styles.css" rel="stylesheet">
Blazor Server(従来テンプレート)Pages/_Host.cshtml<link href="{AssemblyName}.styles.css" rel="stylesheet">

方法Aの落とし穴(移行でハマりやすいポイント)

  • CSSの衝突が増える:分離がなくなるため、同じクラス名・同じ要素セレクタが別画面に影響しやすくなります。
  • セレクタの強さ(specificity)が変わる:分離CSSでは暗黙に属性セレクタが入るため、グローバルに移すと「上書きの順番」が変わり、期待通りに当たらないことがあります。
  • コンポーネント単位の保守性が落ちる:UI部品を増やすほど、グローバルCSSが肥大化しやすいです。

現場でこの落とし穴を踏みにくくするコツは、グローバル化したCSSを「クラス命名ルール(例:BEM風)」「CSSレイヤー」「ページ/機能単位のファイル分割」などで運用することです。

方法B:CSS分離は維持し、b- 属性は仕様として受け入れる

Blazorのコンポーネント設計と相性が良いのはこの方針です。特に、画面や部品が増えていくほど「局所スタイル」が効いてきます。CSS分離のメリットは次の通りです。

  • コンポーネント単位でスタイルを閉じ込められる(他画面に漏れにくい)
  • 同名クラスや要素セレクタでも衝突しにくい
  • 部品の再利用・リファクタがやりやすい

「バリデーターがうるさい」だけが課題なら、運用で割り切るのも合理的です。動作に影響するものではないため、品質ゲートの目的が“表示崩れや致命的な構文ミスの早期検知”であれば、フレームワーク起因の属性は例外扱いにするケースは少なくありません。

CSS分離を維持したまま、スタイルが効かないときのチェック

「属性が付く=CSS分離が動いている」ように見えても、実際にはCSSが読み込まれていないだけでスタイルが効かない、という事故が起きます。次の順で確認すると切り分けが速いです。

症状よくある原因確認・対処
b- 属性は付くのに .razor.css のスタイルが当たらない{AssemblyName}.styles.css が読み込まれていない開発者ツールのNetworkで .styles.css が200で取得できているか確認。<head> 内の <link> が存在するか確認。
親コンポーネントの分離CSSで子コンポーネントの要素を狙っても当たらないCSS分離のルール上、子まで素直に届かない::deep の利用や、ルート要素のラップが必要なケースがある(後述)。
特定ページだけ崩れるグローバルCSSやUIライブラリ側のCSSと競合セレクタの強さ・読み込み順を確認。必要ならクラスを付けてセレクタを明確化。

::deep と「ルート要素がない問題」

「子コンポーネントにスタイルが届かない」系の悩みは、CSS分離の仕様を理解していないとハマりがちです。::deep を使って子要素を対象にする方法や、ルート要素がないとマッチしないケースなど、実例つきで解説されている記事としては次が参考になります。

方法C:CSS分離を維持しつつ、スコープ属性を data-* に変更してバリデーションと両立する

「CSS分離のメリットは捨てたくない。でもW3Cバリデーターを通したい」なら、この方法が最もバランスが良いです。

Microsoft Learnには、スコープ識別子(デフォルトの b-{STRING})をプロジェクトファイルでカスタマイズできることが記載されています。CssScope メタデータを設定すると、生成される属性名が b-xxxxx から任意の文字列へ変更されます。Scope identifiers(Microsoft Learn)

ここでポイントになるのが、HTML側で「拡張用途として正当化しやすい属性名」を選ぶことです。HTMLでは data-* 属性が、拡張データを埋め込むための標準的な仕組みとして定義されています。MDNでも data-* が「非標準属性などのハックを使わずに追加情報を保持できる」と説明されています。data-* global attributes(MDN)

手順:特定コンポーネントのスコープを data-* に変更する

例として、MainLayout.razor.css のスコープ属性名を data-scope-mainlayout に変えてみます。

<!-- *.csproj -->
<ItemGroup>
  <None Update="Components/Layouts/MainLayout.razor.css" CssScope="data-scope-mainlayout" />
</ItemGroup>

これにより、HTMLには次のような属性が付与され、CSSもそれに合わせて書き換えられます(概念的な例)。

<!-- 生成されるHTML(例) -->
<div data-scope-mainlayout>...</div>

<!-- 生成されるCSS(例) -->
div[data-scope-mainlayout] { ... }

運用ルール(ここを決めないと逆に事故る)

この方法は強力ですが、命名が雑だと「別コンポーネントのCSSが当たってしまう」事故につながります。以下のルールを決めて運用するのがおすすめです。

  • 原則としてコンポーネントごとに一意の data-scope-* を割り当てる
  • 英小文字+ハイフン中心で、短く読みやすくする(例:data-scope-user-card)
  • チームで命名規則を固定し、レビューでブレを潰す
  • 変更したら一度クリーンビルドし、生成物(.styles.css)が置き換わったことを確認する

「一意なIDを自動生成してほしい」という要望は自然に出ますが、ここは割り切りポイントです。デフォルトの b-xxxxxxxxxx はフレームワークが衝突しにくい形で生成してくれる一方、data-* へ寄せる場合は人間側で“衝突させない運用”が必要になります。

補足:サードパーティコンポーネントの b- 属性は残る可能性がある

もしUIライブラリなど外部コンポーネント(Razor class libraryなど)を使っている場合、ライブラリ側がCSS分離を使っていれば、そのコンポーネントが出力するHTMLには b- 属性が付与されることがあります。自分のプロジェクト設定だけでは完全に消えないケースがあるため、バリデーション要件が厳しい場合は「どのHTMLを検証対象にするか」も合わせて設計してください。

今どこで発生しているかを素早く特定する方法

「とにかく原因箇所を見つけたい」場合は、次の2方向から探すのが速いです。

.razor.css を起点に探す

  • プロジェクト内で .razor.css を検索し、どのコンポーネントがCSS分離を使っているか棚卸しする
  • まずは崩れている画面(Layoutや該当ページ)に直結する *.razor.css から見る

生成されたCSSを見て「どのスコープか」を逆引きする

{AssemblyName}.styles.css を開き、[b- あるいは [data-scope- を検索すると、どのスコープが存在するかが分かります。Microsoft Learnには、ビルド時にプロジェクトバンドルが obj/{CONFIG}/{TFM}/scopedcss/projectbundle/... に生成されることも記載されています。CSS isolation bundling(Microsoft Learn)

よくある質問

スコープの文字列は毎回変わりますか?

デフォルトの b-{STRING} はフレームワークが生成する識別子で、内部実装の詳細です。値に依存した実装(「このb-値ならこうする」)は避け、あくまで「CSS分離のための属性が付く」という前提で設計するのが安全です。

<ScopedCssEnabled>false</ScopedCssEnabled> にしたのに一部が崩れます

CSS分離を無効化すると、.razor.css のスタイルは基本的に適用されなくなります。崩れた箇所の *.razor.css をグローバルCSSへ移すか、代替のスタイル定義が必要です。Microsoft Q&Aでも、MainLayout.razor.css のスタイルをグローバルへ移し、さらに {AssemblyName}.styles.css の参照を削除して解決した例が報告されています。Blazor adds random attributes(Microsoft Q&A)

バリデーターを通したいだけなら、どれが最短ですか?

プロジェクト規模が小さく、CSS衝突リスクを許容できるなら「方法A(CSS分離をやめる)」が最短です。規模が中〜大で、コンポーネントの再利用性を落としたくない場合は「方法C(data-*化)」が現実的です。

参考リンク

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次