How to create a star rating input 星評価入力の作り方
A keyboard-accessible star rating input filled entirely with the CSS general sibling selector, no JavaScript required for the visual state. JavaScriptなしでCSSの一般兄弟セレクタだけで塗りつぶす、キーボード操作可能な星評価入力。
ライブデモ Live Demo
概要・用途・特徴Overview, Usage & Features
何ができるかWhat it does
A star rating input that replaces a native radio group with SVG star icon labels, filled with pure CSS.
ネイティブのラジオグループをSVG星アイコンのラベルに置き換え、CSSだけで塗りつぶす星評価入力。
どこで使うかWhere to use
product review form, post-purchase feedback, checkout review step, survey question
商品レビューフォーム、購入後フィードバック、購入確認ステップ、アンケート項目、ECサイトの商品詳細
特徴Key features
CSS-only fill and hover preview using the general sibling selector. Native radio semantics preserved for keyboard arrow-key navigation and screen readers. A clear button to reset the group. A live text readout of the current rating.
一般兄弟セレクタによるCSSのみの塗りつぶしとホバープレビュー。キーボードの矢印キー操作とスクリーンリーダーのためにネイティブのラジオセマンティクスを保持。グループをリセットするクリアボタン。現在の評価を知らせるライブテキスト表示。
調整可能パラメータ Adjustable Parameters
| Parameter | Default | Description |
|---|---|---|
--star-color | #f5b301 | Filled/hover star color |
--star-size | 32px | Star icon size |
実装コード Implementation Code
<fieldset class="star-rating">
<legend>Rate this product</legend>
<div class="star-rating__stars">
<input type="radio" name="star-rating-demo" id="star-5" value="5" />
<label for="star-5" class="star-rating__star">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 2.5l2.9 6.2 6.8.6-5.1 4.6 1.5 6.7L12 17.3 5.9 20.6l1.5-6.7-5.1-4.6 6.8-.6L12 2.5z" /></svg>
<span class="star-rating__sr">5 stars</span>
</label>
<input type="radio" name="star-rating-demo" id="star-4" value="4" />
<label for="star-4" class="star-rating__star">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 2.5l2.9 6.2 6.8.6-5.1 4.6 1.5 6.7L12 17.3 5.9 20.6l1.5-6.7-5.1-4.6 6.8-.6L12 2.5z" /></svg>
<span class="star-rating__sr">4 stars</span>
</label>
<input type="radio" name="star-rating-demo" id="star-3" value="3" />
<label for="star-3" class="star-rating__star">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 2.5l2.9 6.2 6.8.6-5.1 4.6 1.5 6.7L12 17.3 5.9 20.6l1.5-6.7-5.1-4.6 6.8-.6L12 2.5z" /></svg>
<span class="star-rating__sr">3 stars</span>
</label>
<input type="radio" name="star-rating-demo" id="star-2" value="2" />
<label for="star-2" class="star-rating__star">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 2.5l2.9 6.2 6.8.6-5.1 4.6 1.5 6.7L12 17.3 5.9 20.6l1.5-6.7-5.1-4.6 6.8-.6L12 2.5z" /></svg>
<span class="star-rating__sr">2 stars</span>
</label>
<input type="radio" name="star-rating-demo" id="star-1" value="1" />
<label for="star-1" class="star-rating__star">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 2.5l2.9 6.2 6.8.6-5.1 4.6 1.5 6.7L12 17.3 5.9 20.6l1.5-6.7-5.1-4.6 6.8-.6L12 2.5z" /></svg>
<span class="star-rating__sr">1 star</span>
</label>
</div>
<p class="star-rating__value" role="status" aria-live="polite" id="rating_text"></p>
<button type="button" class="star-rating__clear" id="star_clear_btn">Clear</button>
</fieldset>
<script>
const stars = Array.from(document.querySelectorAll('input[name="star-rating-demo"]'));
const text = document.getElementById('rating_text');
const clearBtn = document.getElementById('star_clear_btn');
const max = stars.length;
function render(value) {
text.textContent = value ? `Rated ${value} out of ${max}` : 'Not rated';
}
stars.forEach((input) => {
input.addEventListener('change', () => render(Number(input.value)));
});
clearBtn.addEventListener('click', () => {
stars.forEach((input) => { input.checked = false; });
render(0);
});
const checked = stars.find((input) => input.checked);
render(checked ? Number(checked.value) : 0);
</script>
:root {
--star-size: 32px;
--star-color: #f5b301;
--star-gap: 4px;
}
.star-rating__stars {
display: inline-flex;
flex-direction: row-reverse;
gap: var(--star-gap);
}
.star-rating__stars input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.star-rating__star { display: inline-flex; cursor: pointer; border-radius: 6px; }
.star-rating__star svg {
width: var(--star-size);
height: var(--star-size);
fill: #d7dde0;
transition: fill .15s ease, transform .15s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.star-rating__stars input:checked ~ .star-rating__star svg,
.star-rating__star:hover svg,
.star-rating__star:hover ~ .star-rating__star svg {
fill: var(--star-color);
}
.star-rating__stars input:checked + .star-rating__star svg { transform: scale(1.15); }
.star-rating__stars input:focus-visible + .star-rating__star {
outline: 3px solid #3b6fe0;
outline-offset: 3px;
}
import { Fragment, useId, useState } from 'react';
import './F-010.css';
const STAR_PATH = 'M12 2.5l2.9 6.2 6.8.6-5.1 4.6 1.5 6.7L12 17.3 5.9 20.6l1.5-6.7-5.1-4.6 6.8-.6L12 2.5z';
export default function StarRatingInput({ label = 'Rate this product', max = 5, defaultValue = 0, onChange }) {
const name = useId();
const [value, setValue] = useState(defaultValue);
const stars = Array.from({ length: max }, (_, i) => max - i);
function select(next) {
setValue(next);
onChange?.(next);
}
return (
<fieldset className="star-rating">
<legend>{label}</legend>
<div className="star-rating__stars">
{stars.map((star) => {
const id = `${name}-${star}`;
return (
<Fragment key={star}>
<input
type="radio"
id={id}
name={name}
value={star}
checked={value === star}
onChange={() => select(star)}
/>
<label htmlFor={id} className="star-rating__star">
<svg aria-hidden="true" viewBox="0 0 24 24"><path d={STAR_PATH} /></svg>
<span className="star-rating__sr">{star} star{star === 1 ? '' : 's'}</span>
</label>
</Fragment>
);
})}
</div>
<p className="star-rating__value" role="status" aria-live="polite">
{value ? `Rated ${value} out of ${max}` : 'Not rated'}
</p>
<button type="button" className="star-rating__clear" onClick={() => select(0)}>
Clear
</button>
</fieldset>
);
}
.star-rating { border: none; margin: 0; padding: 0; }
.star-rating legend { padding: 0; margin: 0 0 16px; font-weight: 600; }
.star-rating__stars {
display: inline-flex;
flex-direction: row-reverse;
gap: 4px;
}
.star-rating__stars input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.star-rating__star { display: inline-flex; cursor: pointer; border-radius: 6px; }
.star-rating__star svg {
width: 32px;
height: 32px;
fill: #d7dde0;
transition: fill .15s ease, transform .15s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.star-rating__stars input:checked ~ .star-rating__star svg,
.star-rating__star:hover svg,
.star-rating__star:hover ~ .star-rating__star svg {
fill: #f5b301;
}
.star-rating__stars input:checked + .star-rating__star svg { transform: scale(1.15); }
.star-rating__stars input:focus-visible + .star-rating__star {
outline: 3px solid #3b6fe0;
outline-offset: 3px;
}
.star-rating__value { margin: 16px 0 0; font-size: 14px; color: #5b6b6f; min-height: 1.4em; }
.star-rating__clear {
margin-top: 12px;
min-height: 40px;
padding: 8px 16px;
border: 1px solid #d7dde0;
border-radius: 8px;
background: transparent;
font: inherit;
font-size: 13px;
cursor: pointer;
}
.star-rating__sr {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
仕組みとカスタマイズHow It Works & Customization
仕組みHow it works
Radio inputs are listed in descending order (5 to 1) in the DOM, then visually reversed with flex-direction: row-reverse so they read 1 to 5 left to right. Because input:checked ~ label matches every label that follows the checked input in DOM order, checking any star automatically highlights itself and every star to its visual left in a single CSS rule. The same technique with :hover previews the selection before it is committed. A small script only updates the bilingual status text and wires the clear button.
ラジオ入力はDOM内で5から1の降順に並べ、flex-direction: row-reverseで見た目だけ1から5の昇順に反転させます。input:checked ~ labelはDOM順でチェックされた入力より後ろにあるすべてのラベルに一致するため、どの星をチェックしても、その星自身と視覚的に左側にあるすべての星が1つのCSSルールでまとめてハイライトされます。:hoverでも同じ仕組みでプレビューを表示します。スクリプトは日英の状態テキストの更新とクリアボタンの配線だけに使われます。
カスタマイズ方法Customization
Change --star-color and --star-size as custom properties. Swap the SVG path for a heart, thumb, or any icon shape. Add a disabled attribute to every input for a read-only display variant, or double the star count with a clip-path overlay for half-star increments.
--star-colorと--star-sizeのカスタムプロパティを変更してください。SVGのpathをハート・サムズアップなど別アイコンに置き換えられます。読み取り専用で表示するにはすべてのinputにdisabled属性を追加し、半星刻みにするには星の数を2倍にしてclip-pathで半分だけ塗りつぶしてください。
注意点Caveats
Keep the hidden inputs with opacity:0 and zero size rather than display:none, otherwise they drop out of the tab order and arrow-key navigation breaks. The visible status text must exist in the markup from page load (not injected later) for the bilingual .lang toggle to keep working after a language switch.
非表示のinputはdisplay:noneではなくopacity:0とサイズ0のままにしてください。display:noneにするとタブ順から外れ、矢印キー操作が機能しなくなります。表示用の状態テキストはページ読み込み時からマークアップに存在させる必要があります(後から注入すると)言語切替後の.langトグルが効かなくなります。
よくある質問 Frequently Asked Questions
How to customize the star rating input? Star Rating Inputをカスタマイズするには?
Modify the CSS custom properties and class styles defined in the code section. Key adjustable values include colors, sizes, durations, and spacing. See the Adjustable Parameters section for specific variables.
コードセクションで定義されているCSSカスタムプロパティとクラススタイルを変更してください。色、サイズ、時間、間隔が主な調整可能値です。具体的な変数は調整可能パラメータセクションを参照してください。
How to use the star rating input in React? ReactでStar Rating Inputを使うには?
Import the provided React component and its CSS file. The component accepts props for customization. Check the React code section for the full implementation and available props.
提供されているReactコンポーネントとCSSファイルをインポートしてください。コンポーネントのpropsでカスタマイズできます。完全な実装と利用可能なpropsはReactコードセクションを参照してください。
Does the star rating input work with the keyboard and screen readers? Star Rating Inputはキーボードやスクリーンリーダーで操作できますか?
Yes. The native radio inputs stay in the DOM and are only visually hidden, so Tab moves focus to the group, the arrow keys change the selected star, and screen readers announce the group label and each star option normally.
はい。ネイティブのラジオ入力はDOMに残り視覚的にのみ非表示にしているため、Tabでグループにフォーカスが移動し、矢印キーで選択する星を変更でき、スクリーンリーダーもグループのラベルと各星の選択肢を通常通りアナウンスします。
AIへの指示テンプレート AI Prompt Template
以下をAIに貼り付けると実装を依頼できます。 Paste the following into your AI assistant to request implementation.