スターレーティング
ホバーでプレビュー、クリックで確定し、矢印キー操作にも対応した5段階の星評価。レビュー投稿やアンケートの入力UIに使えます。
ライブデモ
使用例(お題: カフェ MOON BREW)
この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- MOON BREW: レビュー投稿フォーム。スターレーティングを主役に -->
<div class="mb">
<header class="mb__head">
<span class="mb__logo">☕ MOON BREW</span>
<span class="mb__sub">ご来店ありがとうございました</span>
</header>
<div class="mb__body">
<div class="mb__item">
<span class="mb__thumb" aria-hidden="true"></span>
<div>
<h1 class="mb__name">本日の一杯はいかがでしたか?</h1>
<p class="mb__desc">琥珀カフェラテ ・ 渋谷桜丘店</p>
</div>
</div>
<div class="rate">
<p class="rate__label">評価を選んでください</p>
<div class="rating" role="radiogroup" aria-label="5段階評価">
<button class="star" type="button" role="radio" aria-checked="false" aria-label="1点">★</button>
<button class="star" type="button" role="radio" aria-checked="false" aria-label="2点">★</button>
<button class="star" type="button" role="radio" aria-checked="false" aria-label="3点">★</button>
<button class="star" type="button" role="radio" aria-checked="false" aria-label="4点">★</button>
<button class="star" type="button" role="radio" aria-checked="false" aria-label="5点">★</button>
</div>
<p class="rate__out">評価: <span class="rating__out">—</span></p>
</div>
<textarea class="mb__ta" rows="2" placeholder="ひとことコメント(任意)"></textarea>
<button class="mb__send" type="button">レビューを送信</button>
</div>
</div>
CSS
/* MOON BREW カフェ テーマ: クリーム/濃ブラウン/琥珀 */
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Hiragino Mincho ProN", "Segoe UI", system-ui, serif;
background: #f5ede1;
color: #2b1d12;
}
.mb {
height: 400px;
display: flex;
flex-direction: column;
padding: 0 30px;
}
.mb__head {
display: flex;
align-items: baseline;
gap: 14px;
padding: 18px 0;
border-bottom: 1px solid #e3d6c2;
}
.mb__logo { font-size: 17px; font-weight: 700; }
.mb__sub {
font-size: 12px;
color: #9b876f;
font-family: "Segoe UI", system-ui, sans-serif;
}
.mb__body {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
gap: 14px;
}
.mb__item {
display: flex;
align-items: center;
gap: 14px;
}
.mb__thumb {
width: 50px;
height: 50px;
border-radius: 12px;
flex: none;
background: radial-gradient(circle at 42% 38%, #d9b388, #c98a3b 55%, #7a5224);
box-shadow: 0 6px 14px rgba(122,82,36,.3);
}
.mb__name {
margin: 0;
font-size: 18px;
font-weight: 700;
}
.mb__desc {
margin: 4px 0 0;
font-size: 12px;
color: #9b876f;
font-family: "Segoe UI", system-ui, sans-serif;
}
/* 評価ブロック */
.rate {
background: #fbf6ee;
border: 1px solid #e7d9c4;
border-radius: 14px;
padding: 14px 18px;
}
.rate__label {
margin: 0 0 6px;
font-size: 12px;
color: #6b513a;
font-family: "Segoe UI", system-ui, sans-serif;
}
/* 主役: スターレーティング */
.rating {
display: inline-flex;
gap: 6px;
}
.star {
font-size: 32px;
line-height: 1;
color: #e0cdb1;
background: none;
border: none;
padding: 0 2px;
cursor: pointer;
transition: transform .12s ease, color .15s ease;
}
.star:hover { transform: scale(1.12); }
.star.is-on { color: #c98a3b; text-shadow: 0 2px 8px rgba(201,138,59,.4); }
.star.is-pop { animation: star-pop .32s ease; }
.star:focus-visible { outline: 2px solid #c98a3b; outline-offset: 3px; border-radius: 4px; }
@keyframes star-pop {
0% { transform: scale(1); }
45% { transform: scale(1.4); }
100% { transform: scale(1); }
}
.rate__out {
margin: 8px 0 0;
font-size: 13px;
font-weight: 700;
color: #2b1d12;
font-family: "Segoe UI", system-ui, sans-serif;
}
.rating__out { color: #c98a3b; }
.mb__ta {
font-family: "Segoe UI", system-ui, sans-serif;
font-size: 13px;
color: #2b1d12;
background: #fbf6ee;
border: 1px solid #e7d9c4;
border-radius: 10px;
padding: 10px 12px;
resize: none;
}
.mb__ta::placeholder { color: #b9a78d; }
.mb__ta:focus { outline: none; border-color: #c98a3b; }
.mb__send {
align-self: flex-start;
font-family: "Segoe UI", system-ui, sans-serif;
font-size: 13px;
font-weight: 700;
color: #fff;
background: linear-gradient(135deg, #c98a3b, #a86b28);
border: none;
padding: 11px 26px;
border-radius: 10px;
cursor: pointer;
box-shadow: 0 8px 20px rgba(201,138,59,.35);
}
@media (prefers-reduced-motion: reduce) {
.star, .star.is-pop { transition: none; animation: none; }
}
JavaScript
// MOON BREWレビュー: ホバーでプレビュー、クリックで確定、矢印キー対応
(() => {
const group = document.querySelector('.rating');
const out = document.querySelector('.rating__out');
if (!group) return; // null安全
const stars = Array.from(group.querySelectorAll('.star'));
if (!stars.length) return;
let current = 0; // 確定値
// n個目までを点灯(プレビュー兼確定描画)
const paint = (n) => stars.forEach((s, i) => s.classList.toggle('is-on', i < n));
// 確定値を反映しARIAを同期
const commit = (n) => {
current = n;
paint(n);
stars.forEach((s, i) => s.setAttribute('aria-checked', String(i === n - 1)));
if (out) out.textContent = n ? `${n} / 5` : '—';
};
stars.forEach((star, i) => {
const val = i + 1;
star.addEventListener('pointerenter', () => paint(val)); // ホバープレビュー
star.addEventListener('click', () => {
commit(val);
star.classList.remove('is-pop');
void star.offsetWidth; // リフローでアニメ再生をリセット
star.classList.add('is-pop');
});
// 左右キーで増減(アクセシビリティ)
star.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
e.preventDefault();
const next = Math.min(5, current + 1);
commit(next);
stars[next - 1].focus();
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
e.preventDefault();
const next = Math.max(1, current - 1);
commit(next);
stars[next - 1].focus();
}
});
});
// グループから離れたら確定値表示へ戻す
group.addEventListener('pointerleave', () => paint(current));
})();
コード
HTML
<!-- スターレーティング: ホバープレビュー+クリック確定の星評価 -->
<div class="stage">
<div class="rating" role="radiogroup" aria-label="5段階評価">
<button class="star" type="button" role="radio" aria-checked="false" data-value="1" aria-label="星1">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.7 1.4 6.8L12 17.8 6 21.2l1.4-6.8L2.3 9.7l6.9-.7z"/></svg>
</button>
<button class="star" type="button" role="radio" aria-checked="false" data-value="2" aria-label="星2">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.7 1.4 6.8L12 17.8 6 21.2l1.4-6.8L2.3 9.7l6.9-.7z"/></svg>
</button>
<button class="star" type="button" role="radio" aria-checked="false" data-value="3" aria-label="星3">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.7 1.4 6.8L12 17.8 6 21.2l1.4-6.8L2.3 9.7l6.9-.7z"/></svg>
</button>
<button class="star" type="button" role="radio" aria-checked="false" data-value="4" aria-label="星4">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.7 1.4 6.8L12 17.8 6 21.2l1.4-6.8L2.3 9.7l6.9-.7z"/></svg>
</button>
<button class="star" type="button" role="radio" aria-checked="false" data-value="5" aria-label="星5">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.7 1.4 6.8L12 17.8 6 21.2l1.4-6.8L2.3 9.7l6.9-.7z"/></svg>
</button>
</div>
<p class="rating__label">タップして評価 <span class="rating__out">—</span></p>
</div>
CSS
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background: radial-gradient(circle at 50% 30%, #2a2440 0%, #15101f 75%);
color: #f3eefc;
}
.stage {
display: grid;
place-items: center;
gap: 18px;
}
.rating {
display: flex;
gap: 8px;
}
/* 星ボタン: 既定は枠だけのアウトライン表示 */
.star {
padding: 4px;
border: none;
background: none;
cursor: pointer;
line-height: 0;
-webkit-tap-highlight-color: transparent;
}
.star svg {
width: 42px;
height: 42px;
fill: #3b3354;
stroke: #5b507e;
stroke-width: 1;
transition: fill .15s ease, transform .15s cubic-bezier(.34,1.56,.64,1), filter .15s ease;
}
/* 点灯状態(JSが is-on を付与): 累積で左から点く */
.star.is-on svg {
fill: #ffc83d;
stroke: #ffb300;
filter: drop-shadow(0 0 6px rgba(255, 200, 61, .6));
}
/* ホバー/フォーカス中の星を少し持ち上げる */
.star:hover svg,
.star:focus-visible svg {
transform: scale(1.18) rotate(-4deg);
}
.star:focus-visible { outline: none; }
/* 確定アニメ(クリック時のひと押し) */
.star.is-pop svg { animation: star-pop .4s ease; }
@keyframes star-pop {
0% { transform: scale(.6); }
60% { transform: scale(1.3); }
100% { transform: scale(1); }
}
.rating__label {
margin: 0;
font-size: 14px;
letter-spacing: .03em;
color: rgba(243, 238, 252, .7);
}
.rating__out {
font-weight: 700;
color: #ffc83d;
margin-left: 4px;
}
@media (prefers-reduced-motion: reduce) {
.star svg { transition: fill .15s ease; }
.star:hover svg, .star:focus-visible svg { transform: none; }
.star.is-pop svg { animation: none; }
}
JavaScript
// スターレーティング: ホバーでプレビュー、クリックで確定。キーボード操作対応
(() => {
const group = document.querySelector('.rating');
const out = document.querySelector('.rating__out');
if (!group) return; // null安全
const stars = Array.from(group.querySelectorAll('.star'));
if (!stars.length) return;
let current = 0; // 確定値
// n個目までを点灯表示する(プレビュー兼確定描画)
const paint = (n) => {
stars.forEach((s, i) => s.classList.toggle('is-on', i < n));
};
// 確定値を反映し、ARIAを同期
const commit = (n) => {
current = n;
paint(n);
stars.forEach((s, i) => s.setAttribute('aria-checked', String(i === n - 1)));
if (out) out.textContent = n ? `${n} / 5` : '—';
};
stars.forEach((star, i) => {
const val = i + 1;
// ホバーで一時プレビュー
star.addEventListener('pointerenter', () => paint(val));
// クリックで確定+ポップ
star.addEventListener('click', () => {
commit(val);
star.classList.remove('is-pop');
void star.offsetWidth; // リフローでアニメ再生をリセット
star.classList.add('is-pop');
});
// 左右キーで値を増減(アクセシビリティ)
star.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
e.preventDefault();
const next = Math.min(5, current + 1);
commit(next);
stars[next - 1].focus();
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
e.preventDefault();
const next = Math.max(1, current - 1);
commit(next);
stars[next - 1].focus();
}
});
});
// グループから離れたら確定値の表示へ戻す
group.addEventListener('pointerleave', () => paint(current));
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「スターレーティング」の効果を追加してください。
# 追加してほしい効果
スターレーティング(マイクロインタラクション)
ホバーでプレビュー、クリックで確定し、矢印キー操作にも対応した5段階の星評価。レビュー投稿やアンケートの入力UIに使えます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- スターレーティング: ホバープレビュー+クリック確定の星評価 -->
<div class="stage">
<div class="rating" role="radiogroup" aria-label="5段階評価">
<button class="star" type="button" role="radio" aria-checked="false" data-value="1" aria-label="星1">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.7 1.4 6.8L12 17.8 6 21.2l1.4-6.8L2.3 9.7l6.9-.7z"/></svg>
</button>
<button class="star" type="button" role="radio" aria-checked="false" data-value="2" aria-label="星2">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.7 1.4 6.8L12 17.8 6 21.2l1.4-6.8L2.3 9.7l6.9-.7z"/></svg>
</button>
<button class="star" type="button" role="radio" aria-checked="false" data-value="3" aria-label="星3">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.7 1.4 6.8L12 17.8 6 21.2l1.4-6.8L2.3 9.7l6.9-.7z"/></svg>
</button>
<button class="star" type="button" role="radio" aria-checked="false" data-value="4" aria-label="星4">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.7 1.4 6.8L12 17.8 6 21.2l1.4-6.8L2.3 9.7l6.9-.7z"/></svg>
</button>
<button class="star" type="button" role="radio" aria-checked="false" data-value="5" aria-label="星5">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l2.9 6.3 6.9.7-5.1 4.7 1.4 6.8L12 17.8 6 21.2l1.4-6.8L2.3 9.7l6.9-.7z"/></svg>
</button>
</div>
<p class="rating__label">タップして評価 <span class="rating__out">—</span></p>
</div>
【CSS】
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background: radial-gradient(circle at 50% 30%, #2a2440 0%, #15101f 75%);
color: #f3eefc;
}
.stage {
display: grid;
place-items: center;
gap: 18px;
}
.rating {
display: flex;
gap: 8px;
}
/* 星ボタン: 既定は枠だけのアウトライン表示 */
.star {
padding: 4px;
border: none;
background: none;
cursor: pointer;
line-height: 0;
-webkit-tap-highlight-color: transparent;
}
.star svg {
width: 42px;
height: 42px;
fill: #3b3354;
stroke: #5b507e;
stroke-width: 1;
transition: fill .15s ease, transform .15s cubic-bezier(.34,1.56,.64,1), filter .15s ease;
}
/* 点灯状態(JSが is-on を付与): 累積で左から点く */
.star.is-on svg {
fill: #ffc83d;
stroke: #ffb300;
filter: drop-shadow(0 0 6px rgba(255, 200, 61, .6));
}
/* ホバー/フォーカス中の星を少し持ち上げる */
.star:hover svg,
.star:focus-visible svg {
transform: scale(1.18) rotate(-4deg);
}
.star:focus-visible { outline: none; }
/* 確定アニメ(クリック時のひと押し) */
.star.is-pop svg { animation: star-pop .4s ease; }
@keyframes star-pop {
0% { transform: scale(.6); }
60% { transform: scale(1.3); }
100% { transform: scale(1); }
}
.rating__label {
margin: 0;
font-size: 14px;
letter-spacing: .03em;
color: rgba(243, 238, 252, .7);
}
.rating__out {
font-weight: 700;
color: #ffc83d;
margin-left: 4px;
}
@media (prefers-reduced-motion: reduce) {
.star svg { transition: fill .15s ease; }
.star:hover svg, .star:focus-visible svg { transform: none; }
.star.is-pop svg { animation: none; }
}
【JavaScript】
// スターレーティング: ホバーでプレビュー、クリックで確定。キーボード操作対応
(() => {
const group = document.querySelector('.rating');
const out = document.querySelector('.rating__out');
if (!group) return; // null安全
const stars = Array.from(group.querySelectorAll('.star'));
if (!stars.length) return;
let current = 0; // 確定値
// n個目までを点灯表示する(プレビュー兼確定描画)
const paint = (n) => {
stars.forEach((s, i) => s.classList.toggle('is-on', i < n));
};
// 確定値を反映し、ARIAを同期
const commit = (n) => {
current = n;
paint(n);
stars.forEach((s, i) => s.setAttribute('aria-checked', String(i === n - 1)));
if (out) out.textContent = n ? `${n} / 5` : '—';
};
stars.forEach((star, i) => {
const val = i + 1;
// ホバーで一時プレビュー
star.addEventListener('pointerenter', () => paint(val));
// クリックで確定+ポップ
star.addEventListener('click', () => {
commit(val);
star.classList.remove('is-pop');
void star.offsetWidth; // リフローでアニメ再生をリセット
star.classList.add('is-pop');
});
// 左右キーで値を増減(アクセシビリティ)
star.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
e.preventDefault();
const next = Math.min(5, current + 1);
commit(next);
stars[next - 1].focus();
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
e.preventDefault();
const next = Math.max(1, current - 1);
commit(next);
stars[next - 1].focus();
}
});
});
// グループから離れたら確定値の表示へ戻す
group.addEventListener('pointerleave', () => paint(current));
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。