写真の上に白い文字を置いたら、空の明るいところで文字が消えてしまった。メインビジュアルやカードを作るとよく起きるこの問題は、画像と文字のあいだに「暗い層」を1枚はさむと解決します。CSSだけでできます。
写真の上に置く文字が、見出しと短い一文までなら使う。説明の文章が長くなるなら、文字は画像の外に出したほうが読みやすい。
使う部品は、画像の上に文字を重ねて置く position: absolute(ポジション・アブソリュート)と、あいだに暗さをはさむ 半透明の色や linear-gradient(リニア・グラデーション=だんだん変わる色)、画像そのものを暗くする filter(フィルター)です。
完成見本:画像の上の文字を読みやすくする5種類
同じような写真に、5つの方法で文字を重ねました。どれも白い文字です。





※ 見本の画像は、写真のかわりに用意したイラストです。
暗い層をはさむ前と後を並べると、違いがはっきりします。左の「重ねる前」は、文字も位置も同じで、暗くする指定だけを外したものです。










コピペ用のコード
見本ごとに、HTMLとCSSを並べました。HTMLは画像を置きたい場所に、CSSはサイトのCSSファイルや、WordPressならテーマの「追加CSS」などに入れます。src の画像のアドレスと文字は、実際のものに書きかえてください。
CSSの前半にある .kd-ov から .kd-ov-s までは、5種類に共通する土台です。同じページで何種類か使うときは、1回だけ入れれば足ります。クラス名の頭の kd-ov- は、サイトのほかのデザインと名前がぶつからないようにするための目印です。文字の部分は、見出しにするなら <span> を <h2> などに変えてください。
1. 全体を暗く
画像の上に、紺色を60%の濃さで1枚重ねます。いちばん基本の形で、写真のどこに文字が来ても読めます。

<div class="kd-ov kd-ov-dark"><img src="https://www.kakeru-d.jp/wp-content/uploads/2026/10/css-image-hover-zoom-mountain.jpg" alt="" width="1200" height="800"><div class="kd-ov-text"><span class="kd-ov-h">季節のおすすめプラン</span><span class="kd-ov-s">山あいの宿で過ごす2泊3日</span></div></div>/* 5種類に共通:画像と文字を重ねる土台 */
.kd-ov {
position: relative;
overflow: hidden;
border-radius: 12px;
color: #FFFFFF;
}
.kd-ov img {
display: block;
width: 100%;
height: auto;
aspect-ratio: 3 / 2;
object-fit: cover;
}
/* 画像の上に重ねる文字の箱(画像と同じ大きさにぴったり重ねる) */
.kd-ov-text {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
padding: 14px 16px;
text-align: center;
/* 重なる順番:画像 → 暗い層(1) → 文字(2) */
z-index: 2;
}
.kd-ov-h {
display: block;
font-size: 20px;
font-weight: 700;
line-height: 1.4;
}
.kd-ov-s {
display: block;
font-size: 14px;
line-height: 1.6;
}
.kd-ov-dark::before {
content: "";
position: absolute;
inset: 0;
z-index: 1;
background: rgba(15, 23, 42, .6);
}2. 下だけ暗く
下から上へ、だんだん透明になる暗さを重ねます。写真の上半分はそのまま見えるので、記事の一覧やカードに向いています。文字は暗い下側に寄せます。

<div class="kd-ov kd-ov-grad"><img src="https://www.kakeru-d.jp/wp-content/uploads/2026/10/css-image-hover-zoom-sea.jpg" alt="" width="1200" height="800"><div class="kd-ov-text"><span class="kd-ov-h">夕方の海辺を歩く</span><span class="kd-ov-s">ブログ|2026.10.13</span></div></div>/* 5種類に共通:画像と文字を重ねる土台 */
.kd-ov {
position: relative;
overflow: hidden;
border-radius: 12px;
color: #FFFFFF;
}
.kd-ov img {
display: block;
width: 100%;
height: auto;
aspect-ratio: 3 / 2;
object-fit: cover;
}
/* 画像の上に重ねる文字の箱(画像と同じ大きさにぴったり重ねる) */
.kd-ov-text {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
padding: 14px 16px;
text-align: center;
/* 重なる順番:画像 → 暗い層(1) → 文字(2) */
z-index: 2;
}
.kd-ov-h {
display: block;
font-size: 20px;
font-weight: 700;
line-height: 1.4;
}
.kd-ov-s {
display: block;
font-size: 14px;
line-height: 1.6;
}
.kd-ov-grad::before {
content: "";
position: absolute;
inset: 0;
z-index: 1;
background: linear-gradient(to top, rgba(15, 23, 42, .8) 0%, rgba(15, 23, 42, .6) 50%, rgba(15, 23, 42, 0) 85%);
}
.kd-ov-grad .kd-ov-text {
justify-content: flex-end;
align-items: flex-start;
text-align: left;
}3. 色を重ねる
黒ではなく、サイトの色に近い濃い青を重ねます。写真の色味がそろうので、何枚か並べたときにまとまって見えます。

<div class="kd-ov kd-ov-color"><img src="https://www.kakeru-d.jp/wp-content/uploads/2026/10/css-image-hover-zoom-sea.jpg" alt="" width="1200" height="800"><div class="kd-ov-text"><span class="kd-ov-h">採用情報</span><span class="kd-ov-s">一緒に働く仲間を募集しています</span></div></div>/* 5種類に共通:画像と文字を重ねる土台 */
.kd-ov {
position: relative;
overflow: hidden;
border-radius: 12px;
color: #FFFFFF;
}
.kd-ov img {
display: block;
width: 100%;
height: auto;
aspect-ratio: 3 / 2;
object-fit: cover;
}
/* 画像の上に重ねる文字の箱(画像と同じ大きさにぴったり重ねる) */
.kd-ov-text {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
padding: 14px 16px;
text-align: center;
/* 重なる順番:画像 → 暗い層(1) → 文字(2) */
z-index: 2;
}
.kd-ov-h {
display: block;
font-size: 20px;
font-weight: 700;
line-height: 1.4;
}
.kd-ov-s {
display: block;
font-size: 14px;
line-height: 1.6;
}
.kd-ov-color::before {
content: "";
position: absolute;
inset: 0;
z-index: 1;
background: rgba(12, 74, 110, .8);
}4. 文字の後ろに帯
画像全体は暗くせず、文字の後ろにだけ暗い帯を敷きます。写真をできるだけそのまま見せたいときの形です。

<div class="kd-ov kd-ov-band"><img src="https://www.kakeru-d.jp/wp-content/uploads/2026/10/css-image-hover-zoom-mountain.jpg" alt="" width="1200" height="800"><div class="kd-ov-text"><span class="kd-ov-h">季節のおすすめプラン</span><span class="kd-ov-s">山あいの宿で過ごす2泊3日</span></div></div>/* 5種類に共通:画像と文字を重ねる土台 */
.kd-ov {
position: relative;
overflow: hidden;
border-radius: 12px;
color: #FFFFFF;
}
.kd-ov img {
display: block;
width: 100%;
height: auto;
aspect-ratio: 3 / 2;
object-fit: cover;
}
/* 画像の上に重ねる文字の箱(画像と同じ大きさにぴったり重ねる) */
.kd-ov-text {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
padding: 14px 16px;
text-align: center;
/* 重なる順番:画像 → 暗い層(1) → 文字(2) */
z-index: 2;
}
.kd-ov-h {
display: block;
font-size: 20px;
font-weight: 700;
line-height: 1.4;
}
.kd-ov-s {
display: block;
font-size: 14px;
line-height: 1.6;
}
.kd-ov-band .kd-ov-text {
inset: auto auto 12px 12px;
max-width: calc(100% - 24px);
align-items: flex-start;
padding: 10px 14px;
text-align: left;
background: rgba(15, 23, 42, .8);
border-radius: 8px;
}5. ぼかして暗く
画像そのものを、ぼかしてから暗くします。写真は雰囲気だけになり、文字がいちばん目立ちます。ページの区切りや、お問い合わせの入口に使う形です。

<div class="kd-ov kd-ov-blur"><img src="https://www.kakeru-d.jp/wp-content/uploads/2026/10/css-image-hover-zoom-plant.jpg" alt="" width="800" height="800"><div class="kd-ov-text"><span class="kd-ov-h">ご予約・お問い合わせ</span><span class="kd-ov-s">お気軽にご相談ください</span></div></div>/* 5種類に共通:画像と文字を重ねる土台 */
.kd-ov {
position: relative;
overflow: hidden;
border-radius: 12px;
color: #FFFFFF;
}
.kd-ov img {
display: block;
width: 100%;
height: auto;
aspect-ratio: 3 / 2;
object-fit: cover;
}
/* 画像の上に重ねる文字の箱(画像と同じ大きさにぴったり重ねる) */
.kd-ov-text {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
padding: 14px 16px;
text-align: center;
/* 重なる順番:画像 → 暗い層(1) → 文字(2) */
z-index: 2;
}
.kd-ov-h {
display: block;
font-size: 20px;
font-weight: 700;
line-height: 1.4;
}
.kd-ov-s {
display: block;
font-size: 14px;
line-height: 1.6;
}
.kd-ov-blur img {
filter: blur(3px) brightness(.45);
/* ぼかすと端が透けるので、少し大きくして隠す */
transform: scale(1.06);
}※ WordPressのテーマによっては、本文の画像に余白や角の丸みの指定が付いていて、見本どおりに見えないことがあります。そのときは、セレクタの頭に本文の枠のクラス(たとえば .entry-content)を足して、テーマ側の指定より強くしてください。
仕組み:画像・暗い層・文字の3枚を重ねる
5種類とも、やっていることは同じです。いちばん下に画像、その上に暗い層、いちばん上に文字。この3枚を、同じ場所にぴったり重ねています。

position で、同じ場所に重ねる
外側の箱 .kd-ov に position: relative を付けると、その箱が「重ねるときの基準」になります。中の文字の箱には position: absolute と inset: 0 を付けます。inset: 0 は「上下左右の端を、基準の箱の端にそろえる」という意味で、文字の箱が画像と同じ大きさになります。あとは中の文字を、上下左右の真ん中や、下の端に寄せるだけです。
::before で、暗い層を足す
1〜3番目では、暗い層をHTMLに書かず、CSSの ::before(ビフォア)で足しています。::before は、要素の最初に飾りの部分を1つ足す書き方です。中身は空(content: "")で、inset: 0 で画像と同じ大きさに広げ、半透明の色を塗ります。HTMLの中は画像と文字だけなので、あとから層の色を変えるときもCSSだけで済みます。
重なる順番は z-index(ゼット・インデックス=重なりの順番を決める数字)で決めています。数字が大きいほど上に来るので、暗い層を1、文字の箱を2にしています。何も書かなくても書いた順番で重なりますが、テーマによっては画像にも重なりの指定が付いていて、暗い層が画像の下に隠れることがあります。実際に、このブログのテーマでも z-index なしでは層が見えなくなったので、数字をはっきり書いています。
| 書いたもの | 付ける場所 | 意味 |
|---|---|---|
| position: relative | 外側の箱(.kd-ov) | 重ねるときの基準にする |
| position: absolute; inset: 0 | 文字の箱・::before | 基準の箱と同じ大きさで、上に重ねる |
| z-index: 1 / 2 | ::before / 文字の箱 | 重なる順番。数字が大きいほど上 |
| rgba(15, 23, 42, .6) | ::before | 紺色を60%の濃さで塗る(40%は下の画像が透ける) |
| linear-gradient(to top, …) | ::before | 下から上へ、だんだん透明になる色を塗る(2番目) |
| filter: blur(3px) brightness(.45) | 画像(img) | 画像をぼかして、明るさを45%に落とす(5番目) |
4番目と5番目は、層を使わない
4番目の帯は、文字の箱そのものに暗い背景を塗っています。暗くなるのは文字の後ろだけです。5番目は、画像に filter を付けて、画像そのものをぼかして暗くしています。ぼかすと画像の端が透けて白くにじむので、transform: scale(1.06) で少しだけ大きくして、にじんだ端を枠の外へ出しています(はみ出した分は、外側の箱の overflow: hidden で隠れます)。
画像は aspect-ratio: 3 / 2(縦横の比率)と object-fit: cover(枠いっぱいに収める)で、どんな比率の写真でも同じ形にそろえています。
ここで使った position と ::before と linear-gradient は2015年7月から、filter は2016年9月から、inset は2021年4月から、主要なブラウザで使える機能とMDNで示されています。object-fit は2020年1月から、aspect-ratio は2021年9月からです。
数値を変えるとどうなるか
見本の数値を1つずつ変えて並べました。
暗さ:重ねる色の濃さ



下の数字は、写真の文字の下が真っ白だったとしても、白い文字とのあいだに残るコントラスト比(文字と背景の明るさの差を表す数字)です。重ねる色と濃さから計算できます。W3Cの基準では、ふつうの文字は4.5:1以上、大きな文字は3:1以上が目安です。
30%では、明るい写真だと基準に届きません。85%まで濃くすると、写真がほとんど見えなくなります。紺色なら60%で、どんな写真でも4.5:1を超えます。だから見本は60%にしました。
範囲:グラデーションが透明になる位置


(見本)

「45%で透明」は、写真がよく見える反面、見出しの上のほうが明るいまま残り、文字の一部が読みにくくなります。グラデーションを使うときは、文字が乗っている範囲が全部、十分に暗いかを確かめます。見本は、下から50%までを60%以上の濃さにしています。右の「全体を一色」は1番目と同じ考え方で、確実に読める代わりに、写真の上半分も暗くなります。
ぼかし:blur の量と、拡大の有無

+拡大(見本)

拡大なし

真ん中は、拡大をやめたものです。ぼかした画像の端が透けて、枠のまわりが白っぽくにじみます。右の12pxは、写真が何だったか分からなくなります。写真の雰囲気を残すなら、2〜4pxあたりが目安です。なお、5番目は明るさを45%に落としているので、写真の白い部分は灰色(#737373)になり、白い文字とのコントラスト比は約4.7:1です。
スマホと、見る人への配慮
いちばん明るい写真で確かめる
画像の上の文字が読めるかどうかは、写真しだいで変わります。W3Cも、背景の画像と文字のコントラストが足りないことを、基準を満たさない例として挙げています。サイトを作るときは用意した写真で確かめても、あとで担当の方が明るい写真に差し替えると、読めなくなります。だから写真に合わせて調整するのではなく、真っ白な写真でも読める濃さにしておくのが安全です。
文字に影(text-shadow)を付ける方法もありますが、影だけで足りるかは写真によって変わります。影は、暗い層に足す「おまけ」と考えておきます。
スマホでは、文字があふれやすい
画面の幅が狭くなると画像は小さくなりますが、文字の大きさはあまり変わりません。見出しが2行、3行と増えて、画像の高さを超えると、外側の箱の overflow: hidden で文字が切れてしまいます。画像の上の文字は、スマホの幅で2行に収まる長さにします。後半の「応用」では、スマホのときだけ画像を縦長にして、文字の場所を確保しています。
文字は画像に焼き込まない
文字を画像の中に描き込んでしまう方法もありますが、HTMLの文字で重ねる理由ははっきりしています。画面の読み上げで読める、スマホで文字が小さくなりすぎない、あとで文字だけ直せる。どれも、文字が画像になっていると難しくなります。
写真の alt は、写真の役割で決める
画面の読み上げを使う人には、画像は alt(オルト=代わりの文字)で伝わります。見本の写真は雰囲気を出すための飾りで、伝えたいことは上の文字に書いてあるので、alt=""(空)にしています。写真そのものに意味があるとき(商品や、実際の店内など)は、何が写っているかを alt に書きます。CSSの背景画像(background-image)で写真を置くと alt を付けられないので、意味のある写真は <img> で置きます。

画像の上の文字が読めないサイトは、意外と多いです。
作ったときはきれいでも、写真を替えた日に読めなくなる。だから「真っ白でも読める濃さ」を先に決めておきます。
応用:パソコンとスマホで文字の場所を変える
ページのいちばん上に置く大きな画像(メインビジュアル)の例です。パソコンでは横長の画像の左側に文字を置き、左から右へ暗さを薄くしています。画面の幅が600px以下になると、画像を縦長にして、文字を下に移し、暗さも下からに切り替えます。スマホとパソコンで、見え方を比べてみてください。

<div class="kd-ov kd-ov-hero"><img src="https://www.kakeru-d.jp/wp-content/uploads/2026/10/css-image-hover-zoom-mountain.jpg" alt="" width="1200" height="800"><div class="kd-ov-text"><span class="kd-ov-h">山あいの宿で、静かな秋を</span><span class="kd-ov-s">紅葉の季節だけの宿泊プランをご用意しました</span><a class="kd-ov-btn" href="#">プランを見る</a></div></div>/* 応用:メインビジュアル(1〜5の共通部分と組み合わせて使う) */
.kd-ov-hero img {
aspect-ratio: 16 / 7;
}
.kd-ov-hero::before {
content: "";
position: absolute;
inset: 0;
z-index: 1;
background: linear-gradient(to right, rgba(15, 23, 42, .8) 0%, rgba(15, 23, 42, .6) 50%, rgba(15, 23, 42, 0) 85%);
}
.kd-ov-hero .kd-ov-text {
inset: 0 auto 0 0;
width: 50%;
align-items: flex-start;
padding: 20px 28px;
text-align: left;
}
.kd-ov-btn {
display: inline-block;
margin-top: 10px;
padding: 10px 20px;
border-radius: 999px;
background: #FFFFFF;
color: #0F172A;
font-weight: 700;
text-decoration: none;
}
.kd-ov-btn:focus-visible {
outline: 3px solid #FFFFFF;
outline-offset: 3px;
}
/* 画面が狭いときは、縦長にして文字を下へ。暗さも下からに変える */
@media (max-width: 600px) {
.kd-ov-hero img {
aspect-ratio: 4 / 5;
}
.kd-ov-hero::before {
background: linear-gradient(to top, rgba(15, 23, 42, .8) 0%, rgba(15, 23, 42, .6) 50%, rgba(15, 23, 42, 0) 85%);
}
.kd-ov-hero .kd-ov-text {
inset: auto 0 0 0;
width: auto;
}
}ボタンは、キーボードのTabキーで選んだときに白い枠が出るよう、:focus-visible(フォーカス・ビジブル=キーボードなどで選んだ状態)も書いています。
AIに頼むなら
同じものは、Claudeなどの生成AIに頼んでもすぐ作れます。そのときは、見た目だけでなく、写真が替わることや、スマホのことまで伝えると、直す回数が減ります。
トップページのメインビジュアルで、写真の上に白い見出しと短い一文を重ねたい。HTMLとCSSだけで。
写真はあとで差し替えるので、どんな明るい写真でも白い文字とのコントラスト比が4.5:1以上になる暗さを、写真と文字のあいだに重ねて。
スマホの幅では文字が画像からはみ出さないようにして、写真は飾りなので alt は空に。AIが出したコードは、そのまま使う前に、次の3つを確かめます。
- いちばん明るい写真に差し替えても読めるか。白い画像に替えて表示してみるのが早い確かめ方です。暗さが30〜40%程度だと、明るい写真で読めなくなります
- スマホの幅で、文字が切れていないか。画像の高さより文字が長いと、はみ出した部分が見えなくなります。見出しの長さを変えて試します
- 文字が画像に焼き込まれていないか、alt がどうなっているか。文字がHTMLで書かれているか、写真の役割に合った alt になっているかを見ます
KAKERUならこう使う/使わない
最後に、作り手としての判断です。
使う場面
- メインビジュアルには「全体を暗く」か、応用の形。写真は差し替わる前提で、真っ白でも読める濃さにします
- ブログの記事一覧・お知らせのカードには「下だけ暗く」。写真の上半分が見えるので、一覧が暗くなりすぎません
- 写真をなるべく見せたい実績紹介や商品には「文字の後ろに帯」。暗くなるのは文字の後ろだけです
- ページの区切りやお問い合わせの入口には「ぼかして暗く」。写真より文字を読ませたい場所に使います
使わない場面
- 説明の文章が3行を超えるとき。画像の上は文章を読む場所ではありません。文章は画像の外に出します
- 商品の色や形を見せたい写真。暗くすると、本当の色が伝わりません
- 1ページの中で、5種類を全部使うこと。カードはカード、メインビジュアルはメインビジュアルで、方法をそろえます

画像の上の文字は「写真の雰囲気と言葉を一緒に届ける」ための形です。
写真を見せたいのか、言葉を読ませたいのか。先に決めると、5種類のどれを使うかも決まります。
まとめ
- 画像の上の文字は、画像と文字のあいだに暗い層をはさむと読みやすくなる。CSSだけでできる
- 外側の箱に position: relative、文字の箱と ::before に position: absolute と inset: 0
- 紺色なら60%の濃さで、写真が真っ白でも白い文字とのコントラスト比が4.5:1を超える
- グラデーションは、文字が乗る範囲が全部、十分に暗いかを確かめる
- スマホでは文字があふれやすいので、2行に収まる長さに。写真の alt は役割で決める
まずは「全体を暗く」のコードをコピーして、.6 の数字を変えてみてください。そのあと写真を明るいものに替えると、なぜ60%にしたのかが分かります。
参考にした資料
いずれも2026年10月13日に確認しました。ブラウザの対応状況は変わることがあります。最新の情報は各ページでご確認ください。
- MDN「position」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/position
※absolute で、親の要素を基準に重ねて置くこと、対応状況 - MDN「inset」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/inset
※top・right・bottom・left をまとめて書く指定、対応状況 - MDN「::before」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Selectors/::before
※要素の最初に、飾りの部分を足す書き方、対応状況 - MDN「linear-gradient()」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Values/gradient/linear-gradient
※色がだんだん変わる背景の書き方、対応状況 - MDN「filter」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/filter
※blur() と brightness() で画像をぼかす・暗くする指定、対応状況 - MDN「object-fit」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/object-fit
※画像を枠いっぱいに収める指定、対応状況 - MDN「aspect-ratio」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/aspect-ratio
※縦横の比率を決める指定、対応状況 - MDN「Color contrast」(英語)
https://developer.mozilla.org/en-US/docs/Web/Accessibility/Guides/Understanding_WCAG/Perceivable/Color_contrast
※文字と背景のコントラスト比の考え方 - W3C「Understanding SC 1.4.3: Contrast (Minimum)」(英語)
https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html
※文字は4.5:1以上、大きな文字は3:1以上という基準 - W3C「F83」(英語)
https://www.w3.org/WAI/WCAG22/Techniques/failures/F83
※背景の画像と文字のコントラストが足りないことを、基準を満たさない例として挙げている - MDN「
: 画像埋め込み要素」
https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/img
※alt属性の書き方。飾りの画像は alt=”” にすること - MDN「prefers-reduced-motion」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion
※動きを減らす設定の人にだけ別の見え方を出す書き方
ふだんの制作の話や、作ったものの裏側は Instagram と X でも出しています。






















連載「WEBの技術」の8回目です。
前々回の「画像のズーム」で、画像の上に文字を重ねる話はあらためて、と書きました。今回がその回です。