HTML/CSSの2026年時点の基本——セマンティクス・コンテナクエリ・:has()
新しい機能を追いかける前に、いまの制作で「前提にしていい」ものを3つに絞りました。意味のあるHTML、部品単位で効くコンテナクエリ、親や状態を選べる:has()。私が実案件でどう使い分けているかを書きます。
HTML/CSSの「いまの基本」を3つに絞るなら、私は 意味の合ったHTML要素を選ぶこと、レイアウトの分岐を画面幅ではなく部品の幅で書くこと(コンテナクエリ)、状態による見た目の切り替えをCSSの:has()で書くこと を挙げます。どれも主要ブラウザでそろって数年がたち、MDNでは「Baseline: Widely available」と表示されている機能です。新機能を追うより先に、この3つを制作の前提に置き換えるほうが、コードは確実に短くなります。
セマンティクス:divを減らすと、説明も減る
まずHTMLです。ここは2026年だから新しい、という話ではありません。ただ、生成AIやノーコードツールが出力するHTMLを受け取る機会が増えて、div と span だけで組まれたマークアップを見る頻度はむしろ上がったと感じています。
私が最低限チェックしているのは次のあたりです。
| 用途 | 使う要素 |
|---|---|
| ページの骨格 | header / nav / main / footer |
| 押すと何かが起きる | button(div にクリックイベントを付けない) |
| 別ページへ移動する | a(button で遷移しない) |
| 検索・絞り込みのフォーム | search |
| 開閉するQ&A | details / summary |
search 要素は、検索やフィルタリングのための領域を表す要素で、暗黙のARIAロールとして search を持つため、role="search" を手で書く必要がなくなります(MDN: <search>)。検索結果そのものではなく、検索フォームの側を囲むものだという点は間違えやすいところです。
正しい要素を選ぶと、キーボード操作やスクリーンリーダーでの読み上げがブラウザ側で最初から用意されます。div で作ったボタンは、フォーカスやEnterキーへの対応を自分で足していく必要がある。つまり、セマンティクスは「正しさ」の話であると同時に、書かなくていいJavaScriptを増やす話でもあります。
コンテナクエリ:カードは自分の幅で形を変える
レスポンシブの分岐を @media (min-width: …) だけで書いていると、同じカードをメイン列とサイドバーの両方に置いたとき困ります。画面は広いのに、置き場所は狭い。この食い違いを解消するのがコンテナクエリです。
.card-wrap {
container-type: inline-size;
}
@container (width > 480px) {
.card {
display: grid;
grid-template-columns: 160px 1fr;
}
}
親に container-type: inline-size を指定しておくと、子の @container がその親の幅を基準に判定されます。MDNでは @container は2023年2月から主要ブラウザで利用可能になったとされています(MDN: @container)。
私の使い分けは単純で、ページ全体の段組みはメディアクエリ、部品の中の組み替えはコンテナクエリです。こう分けておくと、デザイナーとの会話も「この画面幅のとき」から「このカードが480pxより広いとき」に変わり、カンプの確認箇所が部品単位で閉じるようになりました。
:has():状態をCSSだけで拾う
:has() は、引数のセレクタに一致する要素を含む(あるいは後ろに持つ)要素を選べる疑似クラスです。いわゆる「親セレクタ」として紹介されることが多い機能で、MDNでは2023年12月から主要ブラウザで利用可能とされています(MDN: :has())。
実務でいちばん効くのは、フォームやカードの状態の出し分けです。
/* 入力エラーを含むフィールドの枠を赤くする */
.field:has(input:user-invalid) {
border-color: var(--color-error);
}
/* 画像を持たないカードだけ余白を詰める */
.card:not(:has(img)) {
padding-block: 16px;
}
以前なら、JavaScriptで状態を見て親にクラスを付け外ししていた処理です。CSSだけで完結すると、状態管理のコードが1か所減ります。
注意点もMDNに書かれています。:has() の中に :has() は入れられないこと、疑似要素は引数に使えないこと、そして body や * のような広い要素に付けるとDOMが変わるたびの再計算が重くなりうることです。私は「部品のルート要素に付ける」ことを自分のルールにしています。
3つを同時に使うと、何が減るか
3つは別々の機能ですが、組み合わせると効果がはっきりします。意味の合った要素でマークアップし、部品の組み替えはコンテナクエリ、状態の見た目は:has()。こうすると、部品ごとに必要だったJavaScriptと、画面幅ごとの例外的なCSSがまとめて減ります。一人で設計から実装まで持つときほど、この「書かずに済む量」の差が効いてきます。
もちろん、古い端末を使う利用者が多い案件では、対応範囲を事前に確認するのが先です。Baseline の表示は判断の目安として便利ですが、最終的には案件ごとのアクセス解析で決めています。
明日から試せる一手
手元のプロジェクトで、JavaScriptで親要素にクラスを付け外ししている箇所を1つ探してください。たとえば「入力エラー時に .is-error を付ける」ような処理です。それを :has() に置き換えられるか試してみる。1か所置き換えられれば、同じ形の処理がほかにもいくつか見つかるはずです。