検索結果なし
虫眼鏡のイラストと「0件」バッジで検索結果が無いことを優しく伝える空状態。条件をクリアすると結果ありの表示に切り替わる小さな体験も内蔵しています。
ライブデモ
コード
HTML
<!-- 空状態: 検索結果なし。虫眼鏡+「0件」バッジ、条件クリアで結果ありの表示へ切り替え可能 -->
<main class="es-card" data-state="empty">
<svg class="es-illus" viewBox="0 0 200 170" width="168" height="143" role="img" aria-label="検索結果が見つからないことを示す虫眼鏡のイラスト">
<g class="es-dust" aria-hidden="true">
<circle class="es-speck es-speck--1" cx="26" cy="44" r="3"></circle>
<circle class="es-speck es-speck--2" cx="172" cy="112" r="2.6"></circle>
<circle class="es-speck es-speck--3" cx="34" cy="126" r="2.2"></circle>
</g>
<g class="es-float">
<circle class="es-lens" cx="78" cy="74" r="34"></circle>
<circle class="es-lens-inner" cx="78" cy="74" r="17"></circle>
<path class="es-shine" d="M60 60 A24 24 0 0 1 90 52"></path>
<line class="es-handle" x1="103" y1="99" x2="136" y2="132"></line>
<g class="es-badge">
<circle cx="150" cy="36" r="18"></circle>
<text class="es-badge-num" x="150" y="41" text-anchor="middle" data-num-empty="0" data-num-found="12">0</text>
</g>
</g>
</svg>
<h2 class="es-title" data-empty="見つかりませんでした" data-found="12件見つかりました">見つかりませんでした</h2>
<p class="es-desc"
data-empty="条件に一致する結果がありません。キーワードや絞り込み条件を変えて、もう一度お試しください。"
data-found="フィルターをクリアしたので、より多くの候補を表示しています。">条件に一致する結果がありません。キーワードや絞り込み条件を変えて、もう一度お試しください。</p>
<div class="es-chips" data-chips>
<span class="es-chip">価格: ¥1,000〜¥3,000</span>
<span class="es-chip">カラー: ブルー</span>
</div>
<button class="es-cta" type="button" data-cta data-label-empty="条件をクリア" data-label-found="元に戻す">条件をクリア</button>
</main>
CSS
/* 空状態: 検索結果なし */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 32px 20px;
font-family: "Hiragino Sans", "Yu Gothic UI", "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 18% 14%, rgba(129, 140, 248, .18) 0%, transparent 46%),
radial-gradient(circle at 84% 86%, rgba(196, 181, 253, .22) 0%, transparent 50%),
#eef1fb;
color: #312e81;
}
.es-card {
--badge-fill: #fb7185;
--ink: #312e81;
width: 100%;
max-width: 380px;
background: #ffffff;
border-radius: 24px;
padding: 40px 32px 34px;
text-align: center;
box-shadow: 0 24px 60px -24px rgba(79, 70, 229, .28), 0 2px 8px rgba(49, 46, 129, .06);
border: 1px solid rgba(129, 140, 248, .12);
}
.es-illus { display: block; margin: 0 auto 8px; overflow: visible; }
/* 浮遊するアイドルアニメーション */
.es-float {
transform-origin: 78px 74px;
animation: es-bob 3.4s ease-in-out infinite;
}
@keyframes es-bob {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-5px) rotate(-2deg); }
}
.es-lens {
fill: rgba(129, 140, 248, .08);
stroke: #6366f1;
stroke-width: 6;
}
.es-lens-inner {
fill: none;
stroke: #a5b4fc;
stroke-width: 2.5;
stroke-dasharray: 4 5;
}
.es-handle {
stroke: #6366f1;
stroke-width: 8;
stroke-linecap: round;
}
.es-shine {
fill: none;
stroke: #ffffff;
stroke-width: 4;
stroke-linecap: round;
opacity: .9;
animation: es-shine 3.4s ease-in-out infinite;
}
@keyframes es-shine {
0%, 100% { opacity: .35; }
50% { opacity: .95; }
}
.es-badge circle {
fill: var(--badge-fill);
stroke: #ffffff;
stroke-width: 4;
transition: fill .4s ease;
}
.es-badge-num {
fill: #ffffff;
font-size: 15px;
font-weight: 700;
font-family: inherit;
transition: opacity .2s ease;
}
/* ふわふわ舞う粒子 */
.es-speck {
fill: #a5b4fc;
animation: es-drift 4.5s ease-in-out infinite;
}
.es-speck--2 { animation-delay: 1.1s; }
.es-speck--3 { animation-delay: 2.2s; }
@keyframes es-drift {
0%, 100% { transform: translateY(0); opacity: .35; }
50% { transform: translateY(-8px); opacity: .9; }
}
.es-title {
margin: 6px 0 10px;
font-size: 21px;
font-weight: 700;
color: var(--ink);
transition: opacity .22s ease;
}
.es-desc {
margin: 0 0 18px;
font-size: 14.5px;
line-height: 1.7;
color: #6b7280;
transition: opacity .22s ease;
}
.es-chips {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: center;
max-height: 60px;
opacity: 1;
overflow: hidden;
margin: 0 0 22px;
transition: max-height .35s ease, opacity .3s ease, margin .35s ease;
}
.es-chip {
font-size: 12.5px;
color: #4338ca;
background: #eef2ff;
border: 1px solid #c7d2fe;
border-radius: 999px;
padding: 6px 12px;
}
.es-cta {
appearance: none;
border: none;
cursor: pointer;
padding: 13px 30px;
font-size: 15px;
font-weight: 600;
color: #fff;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
border-radius: 999px;
box-shadow: 0 12px 26px -10px rgba(99, 102, 241, .55);
transition: transform .25s cubic-bezier(.22,1,.36,1), box-shadow .25s ease;
}
.es-cta:hover { transform: translateY(-2px); box-shadow: 0 16px 32px -10px rgba(99, 102, 241, .65); }
.es-cta:active { transform: translateY(0); }
/* クリア済み(結果ありを模擬する)状態 */
.es-card[data-state="found"] { --badge-fill: #10b981; }
.es-card[data-state="found"] .es-chips {
max-height: 0;
opacity: 0;
margin: 0 0 22px;
pointer-events: none;
}
.es-card.is-swapping .es-title,
.es-card.is-swapping .es-desc,
.es-card.is-swapping .es-badge-num {
opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
.es-float, .es-shine, .es-speck { animation: none !important; }
}
JavaScript
// 空状態: 検索結果なし — 「条件をクリア」で結果ありの表示へトグルする簡易デモ
(() => {
const card = document.querySelector('.es-card');
if (!card) return; // null安全
const title = card.querySelector('.es-title');
const desc = card.querySelector('.es-desc');
const badgeNum = card.querySelector('.es-badge-num');
const cta = card.querySelector('[data-cta]');
if (!title || !desc || !badgeNum || !cta) return;
let isFound = false;
let swapTimer = null;
const applyTexts = () => {
title.textContent = isFound ? (title.dataset.found || '') : (title.dataset.empty || '');
desc.textContent = isFound ? (desc.dataset.found || '') : (desc.dataset.empty || '');
badgeNum.textContent = isFound ? (badgeNum.dataset.numFound || '0') : (badgeNum.dataset.numEmpty || '0');
cta.textContent = isFound ? (cta.dataset.labelFound || '') : (cta.dataset.labelEmpty || '');
};
cta.addEventListener('click', () => {
isFound = !isFound;
card.dataset.state = isFound ? 'found' : 'empty';
// 一瞬フェードしてからテキストを差し替える(唐突な切り替えを避ける)
card.classList.add('is-swapping');
if (swapTimer) clearTimeout(swapTimer);
swapTimer = setTimeout(() => {
applyTexts();
card.classList.remove('is-swapping');
}, 160);
});
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「検索結果なし」の効果を追加してください。
# 追加してほしい効果
検索結果なし(アラート & 空状態)
虫眼鏡のイラストと「0件」バッジで検索結果が無いことを優しく伝える空状態。条件をクリアすると結果ありの表示に切り替わる小さな体験も内蔵しています。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 空状態: 検索結果なし。虫眼鏡+「0件」バッジ、条件クリアで結果ありの表示へ切り替え可能 -->
<main class="es-card" data-state="empty">
<svg class="es-illus" viewBox="0 0 200 170" width="168" height="143" role="img" aria-label="検索結果が見つからないことを示す虫眼鏡のイラスト">
<g class="es-dust" aria-hidden="true">
<circle class="es-speck es-speck--1" cx="26" cy="44" r="3"></circle>
<circle class="es-speck es-speck--2" cx="172" cy="112" r="2.6"></circle>
<circle class="es-speck es-speck--3" cx="34" cy="126" r="2.2"></circle>
</g>
<g class="es-float">
<circle class="es-lens" cx="78" cy="74" r="34"></circle>
<circle class="es-lens-inner" cx="78" cy="74" r="17"></circle>
<path class="es-shine" d="M60 60 A24 24 0 0 1 90 52"></path>
<line class="es-handle" x1="103" y1="99" x2="136" y2="132"></line>
<g class="es-badge">
<circle cx="150" cy="36" r="18"></circle>
<text class="es-badge-num" x="150" y="41" text-anchor="middle" data-num-empty="0" data-num-found="12">0</text>
</g>
</g>
</svg>
<h2 class="es-title" data-empty="見つかりませんでした" data-found="12件見つかりました">見つかりませんでした</h2>
<p class="es-desc"
data-empty="条件に一致する結果がありません。キーワードや絞り込み条件を変えて、もう一度お試しください。"
data-found="フィルターをクリアしたので、より多くの候補を表示しています。">条件に一致する結果がありません。キーワードや絞り込み条件を変えて、もう一度お試しください。</p>
<div class="es-chips" data-chips>
<span class="es-chip">価格: ¥1,000〜¥3,000</span>
<span class="es-chip">カラー: ブルー</span>
</div>
<button class="es-cta" type="button" data-cta data-label-empty="条件をクリア" data-label-found="元に戻す">条件をクリア</button>
</main>
【CSS】
/* 空状態: 検索結果なし */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 32px 20px;
font-family: "Hiragino Sans", "Yu Gothic UI", "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 18% 14%, rgba(129, 140, 248, .18) 0%, transparent 46%),
radial-gradient(circle at 84% 86%, rgba(196, 181, 253, .22) 0%, transparent 50%),
#eef1fb;
color: #312e81;
}
.es-card {
--badge-fill: #fb7185;
--ink: #312e81;
width: 100%;
max-width: 380px;
background: #ffffff;
border-radius: 24px;
padding: 40px 32px 34px;
text-align: center;
box-shadow: 0 24px 60px -24px rgba(79, 70, 229, .28), 0 2px 8px rgba(49, 46, 129, .06);
border: 1px solid rgba(129, 140, 248, .12);
}
.es-illus { display: block; margin: 0 auto 8px; overflow: visible; }
/* 浮遊するアイドルアニメーション */
.es-float {
transform-origin: 78px 74px;
animation: es-bob 3.4s ease-in-out infinite;
}
@keyframes es-bob {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-5px) rotate(-2deg); }
}
.es-lens {
fill: rgba(129, 140, 248, .08);
stroke: #6366f1;
stroke-width: 6;
}
.es-lens-inner {
fill: none;
stroke: #a5b4fc;
stroke-width: 2.5;
stroke-dasharray: 4 5;
}
.es-handle {
stroke: #6366f1;
stroke-width: 8;
stroke-linecap: round;
}
.es-shine {
fill: none;
stroke: #ffffff;
stroke-width: 4;
stroke-linecap: round;
opacity: .9;
animation: es-shine 3.4s ease-in-out infinite;
}
@keyframes es-shine {
0%, 100% { opacity: .35; }
50% { opacity: .95; }
}
.es-badge circle {
fill: var(--badge-fill);
stroke: #ffffff;
stroke-width: 4;
transition: fill .4s ease;
}
.es-badge-num {
fill: #ffffff;
font-size: 15px;
font-weight: 700;
font-family: inherit;
transition: opacity .2s ease;
}
/* ふわふわ舞う粒子 */
.es-speck {
fill: #a5b4fc;
animation: es-drift 4.5s ease-in-out infinite;
}
.es-speck--2 { animation-delay: 1.1s; }
.es-speck--3 { animation-delay: 2.2s; }
@keyframes es-drift {
0%, 100% { transform: translateY(0); opacity: .35; }
50% { transform: translateY(-8px); opacity: .9; }
}
.es-title {
margin: 6px 0 10px;
font-size: 21px;
font-weight: 700;
color: var(--ink);
transition: opacity .22s ease;
}
.es-desc {
margin: 0 0 18px;
font-size: 14.5px;
line-height: 1.7;
color: #6b7280;
transition: opacity .22s ease;
}
.es-chips {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: center;
max-height: 60px;
opacity: 1;
overflow: hidden;
margin: 0 0 22px;
transition: max-height .35s ease, opacity .3s ease, margin .35s ease;
}
.es-chip {
font-size: 12.5px;
color: #4338ca;
background: #eef2ff;
border: 1px solid #c7d2fe;
border-radius: 999px;
padding: 6px 12px;
}
.es-cta {
appearance: none;
border: none;
cursor: pointer;
padding: 13px 30px;
font-size: 15px;
font-weight: 600;
color: #fff;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
border-radius: 999px;
box-shadow: 0 12px 26px -10px rgba(99, 102, 241, .55);
transition: transform .25s cubic-bezier(.22,1,.36,1), box-shadow .25s ease;
}
.es-cta:hover { transform: translateY(-2px); box-shadow: 0 16px 32px -10px rgba(99, 102, 241, .65); }
.es-cta:active { transform: translateY(0); }
/* クリア済み(結果ありを模擬する)状態 */
.es-card[data-state="found"] { --badge-fill: #10b981; }
.es-card[data-state="found"] .es-chips {
max-height: 0;
opacity: 0;
margin: 0 0 22px;
pointer-events: none;
}
.es-card.is-swapping .es-title,
.es-card.is-swapping .es-desc,
.es-card.is-swapping .es-badge-num {
opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
.es-float, .es-shine, .es-speck { animation: none !important; }
}
【JavaScript】
// 空状態: 検索結果なし — 「条件をクリア」で結果ありの表示へトグルする簡易デモ
(() => {
const card = document.querySelector('.es-card');
if (!card) return; // null安全
const title = card.querySelector('.es-title');
const desc = card.querySelector('.es-desc');
const badgeNum = card.querySelector('.es-badge-num');
const cta = card.querySelector('[data-cta]');
if (!title || !desc || !badgeNum || !cta) return;
let isFound = false;
let swapTimer = null;
const applyTexts = () => {
title.textContent = isFound ? (title.dataset.found || '') : (title.dataset.empty || '');
desc.textContent = isFound ? (desc.dataset.found || '') : (desc.dataset.empty || '');
badgeNum.textContent = isFound ? (badgeNum.dataset.numFound || '0') : (badgeNum.dataset.numEmpty || '0');
cta.textContent = isFound ? (cta.dataset.labelFound || '') : (cta.dataset.labelEmpty || '');
};
cta.addEventListener('click', () => {
isFound = !isFound;
card.dataset.state = isFound ? 'found' : 'empty';
// 一瞬フェードしてからテキストを差し替える(唐突な切り替えを避ける)
card.classList.add('is-swapping');
if (swapTimer) clearTimeout(swapTimer);
swapTimer = setTimeout(() => {
applyTexts();
card.classList.remove('is-swapping');
}, 160);
});
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。