SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/star-ratings

8 CSS Star Ratings

7 Pure CSS1 with JSPublished 2026-10-04Free · no sign-up

Eight rating designs for collecting and showing reviews: an accessible star input, a fractional star display, a breakdown chart, heart and emoji scales, a review card, a compact product-card rating and stars that fill with a sweep on hover.

01. Star Rating Input

FormRadio

A five-star rating people can set, built from radio buttons so it is keyboard accessible with the arrow keys. The inputs are in reverse order with flex-direction: row-reverse, which lets the sibling selector light up every star up to the hovered one.

View code
<fieldset class="rate">
  <legend>Rate your order</legend>
  <div class="stars">
    <input type="radio" name="r" id="r5" value="5"><label for="r5" title="5 stars">&#9733;<span class="sr">5 stars</span></label>
    <input type="radio" name="r" id="r4" value="4" checked><label for="r4" title="4 stars">&#9733;<span class="sr">4 stars</span></label>
    <input type="radio" name="r" id="r3" value="3"><label for="r3" title="3 stars">&#9733;<span class="sr">3 stars</span></label>
    <input type="radio" name="r" id="r2" value="2"><label for="r2" title="2 stars">&#9733;<span class="sr">2 stars</span></label>
    <input type="radio" name="r" id="r1" value="1"><label for="r1" title="1 star">&#9733;<span class="sr">1 star</span></label>
  </div>
</fieldset>
.rate { margin: 0; padding: 0; border: 0; font: 600 15px Inter, system-ui, sans-serif; color: #101828; }
.rate legend { margin-bottom: 8px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.stars { display: inline-flex; flex-direction: row-reverse; gap: 4px; }
.stars input { position: absolute; opacity: 0; }
.stars label { color: #d0d5dd; font-size: 34px; line-height: 1; cursor: pointer; transition: color .1s, transform .1s; }
.stars input:checked ~ label, .stars label:hover, .stars label:hover ~ label { color: #f59e0b; }
.stars:hover input:checked ~ label:not(:hover ~ label):not(:hover) { color: #d0d5dd; }
.stars input:focus-visible + label { outline: 2px solid #2563eb; outline-offset: 2px; border-radius: 4px; }
.stars label:active { transform: scale(.9); }

02. Partial Star Rating Display

ReviewsRead-only

A read-only rating that shows fractions such as 4.3 stars. Two layers of the same five stars are stacked, and the coloured top layer is clipped to the exact percentage width, while the text 4.3 out of 5 is what screen readers hear.

View code
<div class="rd">
  <span class="stars" style="--v:4.3" role="img" aria-label="Rated 4.3 out of 5"></span>
  <span class="txt">4.3 <small>(1,248 reviews)</small></span>
</div>
.rd { display: flex; align-items: center; gap: 12px; font: 600 15px Inter, system-ui, sans-serif; color: #fff; }
.rd .stars { position: relative; font-size: 26px; line-height: 1; letter-spacing: 3px; }
.rd .stars::before { content: "\2605\2605\2605\2605\2605"; color: #343c51; }
.rd .stars::after { content: "\2605\2605\2605\2605\2605"; position: absolute; left: 0; top: 0; width: calc(var(--v) / 5 * 100%); overflow: hidden; white-space: nowrap; color: #fbbf24; }
.rd small { color: #9aa4b8; font-weight: 500; }

03. Rating Breakdown Bars

ReviewsChart

The review summary found on product pages: the average score, a star row and a bar for each star level showing how many reviews gave it. Every row is a link that could filter the reviews, and the counts are written as text beside each bar.

View code
<section class="bd">
  <div class="avg"><b>4.6</b><span aria-hidden="true">&#9733;&#9733;&#9733;&#9733;&#9733;</span><small>312 reviews</small></div>
  <ul>
    <li><a href="#">5 stars</a><span class="bar"><i style="width:72%"></i></span><span>226</span></li>
    <li><a href="#">4 stars</a><span class="bar"><i style="width:18%"></i></span><span>55</span></li>
    <li><a href="#">3 stars</a><span class="bar"><i style="width:6%"></i></span><span>19</span></li>
    <li><a href="#">2 stars</a><span class="bar"><i style="width:2%"></i></span><span>7</span></li>
    <li><a href="#">1 star</a><span class="bar"><i style="width:2%"></i></span><span>5</span></li>
  </ul>
</section>
.bd { box-sizing: border-box; max-width: 100%; display: grid; grid-template-columns: auto 1fr; gap: 24px; align-items: center; width: min(460px, 94vw); padding: 20px; border-radius: 16px; background: #fff; font: 14px Inter, system-ui, sans-serif; color: #344054; }
.avg { display: grid; justify-items: center; gap: 2px; }
.avg b { color: #101828; font-size: 40px; }
.avg span { color: #f59e0b; letter-spacing: 2px; }
.avg small { color: #5d6679; }
.bd ul { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }
.bd li { display: grid; grid-template-columns: 52px 1fr 32px; align-items: center; gap: 10px; }
.bd a { color: #1d4ed8; font-size: 13px; text-decoration: none; }
.bd a:hover { text-decoration: underline; }
.bar { height: 8px; border-radius: 4px; background: #eef0f4; overflow: hidden; }
.bar i { display: block; height: 100%; background: #f59e0b; }
.bd li span:last-child { text-align: right; font-variant-numeric: tabular-nums; }
@media (max-width: 420px) { .bd { grid-template-columns: 1fr; } }

04. Heart Rating

IconRadio

A rating with hearts instead of stars for wish lists or recipe sites. It uses the same radio-group technique as the star input, with a little scale pop on the chosen heart; the animation is skipped for people who prefer reduced motion.

View code
<fieldset class="hearts">
  <legend>How much did you love it?</legend>
  <div class="hr">
    <input type="radio" name="h" id="h3"><label for="h3"><span class="sr">3 hearts</span>&#10084;</label>
    <input type="radio" name="h" id="h2" checked><label for="h2"><span class="sr">2 hearts</span>&#10084;</label>
    <input type="radio" name="h" id="h1"><label for="h1"><span class="sr">1 heart</span>&#10084;</label>
  </div>
</fieldset>
.hearts { margin: 0; padding: 0; border: 0; color: #e7ebf5; font: 600 15px Inter, system-ui, sans-serif; }
.hearts legend { margin-bottom: 10px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.hr { display: inline-flex; flex-direction: row-reverse; gap: 8px; }
.hr input { position: absolute; opacity: 0; }
.hr label { color: #3a4256; font-size: 32px; line-height: 1; cursor: pointer; }
.hr input:checked ~ label, .hr label:hover, .hr label:hover ~ label { color: #f43f5e; }
.hr input:checked + label { animation: pop .3s; }
.hr input:focus-visible + label { outline: 2px solid #fda4af; outline-offset: 3px; border-radius: 6px; }
@keyframes pop { 50% { transform: scale(1.3); } }
@media (prefers-reduced-motion: reduce) { .hr input:checked + label { animation: none; } }

05. Emoji Feedback Rating

FeedbackEmoji

A five-face satisfaction scale from angry to delighted, common in support widgets. Unselected faces are greyscale and the chosen one is shown in full colour and larger; each option has a text label so the meaning is not carried by the emoji alone.

View code
<fieldset class="emo">
  <legend>How was your support chat?</legend>
  <input type="radio" name="e" id="e1"><label for="e1"><span aria-hidden="true">&#128544;</span><small>Awful</small></label>
  <input type="radio" name="e" id="e2"><label for="e2"><span aria-hidden="true">&#128533;</span><small>Poor</small></label>
  <input type="radio" name="e" id="e3"><label for="e3"><span aria-hidden="true">&#128528;</span><small>Okay</small></label>
  <input type="radio" name="e" id="e4" checked><label for="e4"><span aria-hidden="true">&#128578;</span><small>Good</small></label>
  <input type="radio" name="e" id="e5"><label for="e5"><span aria-hidden="true">&#128525;</span><small>Great</small></label>
</fieldset>
.emo { display: flex; gap: 6px; margin: 0; padding: 0; border: 0; font-family: Inter, system-ui, sans-serif; }
.emo legend { margin-bottom: 10px; color: #101828; font-weight: 700; }
.emo input { position: absolute; opacity: 0; }
.emo label { display: grid; justify-items: center; gap: 4px; padding: 8px 6px; border-radius: 12px; cursor: pointer; }
.emo label span { font-size: 30px; filter: grayscale(1); opacity: .6; transition: transform .15s, filter .15s; }
.emo small { color: #5d6679; font-size: 12px; font-weight: 600; }
.emo input:checked + label { background: #fff7ed; }
.emo input:checked + label span { filter: none; opacity: 1; transform: scale(1.2); }
.emo input:checked + label small { color: #9a3412; }
.emo input:focus-visible + label { outline: 2px solid #ea580c; }

06. Review Card with Rating

ReviewsCard

A single customer review with a star rating, a verified purchase badge, the review text and a was-this-helpful button. The stars are one image with an accessible name, and the helpful count updates and disables the button after one click.

View code
<article class="rv">
  <header><b>Priya S.</b><span class="vf">Verified purchase</span></header>
  <p class="st" role="img" aria-label="5 out of 5 stars">&#9733;&#9733;&#9733;&#9733;&#9733;</p>
  <h3>Beautiful glaze, sturdy handle</h3>
  <p>Bought two as a gift and ended up keeping one. Holds heat well and the colour is even nicer in person.</p>
  <button type="button" class="hp">Helpful (<span>12</span>)</button>
</article>
.rv { box-sizing: border-box; max-width: 100%; width: min(380px, 90vw); padding: 18px; border: 1px solid #262d3e; border-radius: 16px; background: #12161f; color: #aab3c7; font: 14px/1.55 Inter, system-ui, sans-serif; }
.rv header { display: flex; align-items: center; gap: 10px; }
.rv header b { color: #fff; }
.vf { padding: 2px 8px; border-radius: 999px; background: rgba(52,211,153,.15); color: #6ee7b7; font-size: 11.5px; font-weight: 700; }
.st { margin: 8px 0 4px; color: #fbbf24; letter-spacing: 2px; }
.rv h3 { margin: 0 0 6px; color: #fff; font-size: 16px; }
.rv p { margin: 0 0 12px; }
.hp { padding: 7px 12px; border: 1px solid #343c51; border-radius: 8px; background: none; color: #c9d1e2; font: 600 13px Inter, sans-serif; cursor: pointer; }
.hp:disabled { opacity: .6; cursor: default; }
.hp:focus-visible { outline: 2px solid #7c9cff; }
const hp = document.querySelector('.hp');
hp.addEventListener('click', () => { const s = hp.querySelector('span'); s.textContent = +s.textContent + 1; hp.disabled = true; });

07. Compact Inline Rating

ProductBadge

The small rating shown on product cards: one star, the average and the review count in a single line. It takes very little space, keeps the number readable instead of drawing five stars, and the whole line is one accessible phrase.

View code
<div class="pc">
  <div class="im" aria-hidden="true"></div>
  <h3>Stoneware dinner plate</h3>
  <p class="cr" aria-label="Rated 4.8 out of 5 from 96 reviews"><span aria-hidden="true">&#9733; 4.8 <small>(96)</small></span></p>
  <p class="pr">$24.00</p>
</div>
.pc { width: 220px; font-family: Inter, system-ui, sans-serif; }
.im { aspect-ratio: 4 / 3; border-radius: 12px; background: linear-gradient(135deg, #e7e2d8, #c9bfae); }
.pc h3 { margin: 10px 0 4px; color: #101828; font-size: 15px; }
.cr { margin: 0; color: #101828; font-size: 13px; font-weight: 700; }
.cr span { display: inline-block; color: #101828; }
.cr span::first-letter { color: #d97706; }
.cr small { color: #5d6679; font-weight: 500; }
.pr { margin: 4px 0 0; color: #101828; font-weight: 800; }

08. Animated Star Fill on Hover

AnimationInteractive

Stars that fill with a sweeping gradient as you hover across them, rather than snapping colour. Each star is an SVG with a gradient fill whose stop animates, the chosen rating stays filled, and keyboard users get the same result with the arrow keys.

View code
<fieldset class="ast">
  <legend>Rate this course</legend>
  <div class="row">
    <input type="radio" name="a" id="a5"><label for="a5"><span class="sr">5 stars</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l3 6.6 7 .8-5.2 4.8 1.4 7L12 17.8 5.8 21.2l1.4-7L2 9.4l7-.8z"/></svg></label>
    <input type="radio" name="a" id="a4"><label for="a4"><span class="sr">4 stars</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l3 6.6 7 .8-5.2 4.8 1.4 7L12 17.8 5.8 21.2l1.4-7L2 9.4l7-.8z"/></svg></label>
    <input type="radio" name="a" id="a3" checked><label for="a3"><span class="sr">3 stars</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l3 6.6 7 .8-5.2 4.8 1.4 7L12 17.8 5.8 21.2l1.4-7L2 9.4l7-.8z"/></svg></label>
    <input type="radio" name="a" id="a2"><label for="a2"><span class="sr">2 stars</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l3 6.6 7 .8-5.2 4.8 1.4 7L12 17.8 5.8 21.2l1.4-7L2 9.4l7-.8z"/></svg></label>
    <input type="radio" name="a" id="a1"><label for="a1"><span class="sr">1 star</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l3 6.6 7 .8-5.2 4.8 1.4 7L12 17.8 5.8 21.2l1.4-7L2 9.4l7-.8z"/></svg></label>
  </div>
</fieldset>
.ast { margin: 0; padding: 0; border: 0; color: #e7ebf5; font: 600 15px Inter, system-ui, sans-serif; }
.ast legend { margin-bottom: 10px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.row { display: inline-flex; flex-direction: row-reverse; gap: 6px; }
.row input { position: absolute; opacity: 0; }
.row label { cursor: pointer; }
.row svg { width: 34px; height: 34px; fill: #262d3e; stroke: #4a5368; stroke-width: 1; transition: fill .2s, transform .2s; }
.row input:checked ~ label svg, .row label:hover svg, .row label:hover ~ label svg { fill: #facc15; stroke: #facc15; }
.row label:hover svg { transform: rotate(-12deg) scale(1.1); }
.row input:focus-visible + label svg { stroke: #fff; stroke-width: 2; }
@media (prefers-reduced-motion: reduce) { .row svg { transition: none; } .row label:hover svg { transform: none; } }

About these CSS Star Ratings

Star inputs are radio groups

A rating someone sets is a choice of one value, so build it from radio buttons in a fieldset. Each star's label says 4 stars and so on in hidden text, and the arrow keys move between values like any radio group.

Lighting stars up to the hover

Placing the inputs in reverse order and using flex-direction: row-reverse lets the general sibling selector ~ colour every star up to the hovered or checked one, with no JavaScript.

Displaying fractions

For a read-only 4.3 rating, stack two rows of stars and clip the coloured row to 86% width. Give the element role="img" and a label like Rated 4.3 out of 5, since the drawing itself is not readable.

Honest reviews

Only show ratings from real customers. Adding fake ratings or review counts is misleading, and search engines can penalise review markup that does not match genuine reviews on the page.

Frequently asked questions

Do the star inputs work with a keyboard?

Yes. They are radio buttons, so Tab reaches the group and arrow keys change the value.

Can I use half stars as input?

You can add ten radios with half-width labels, but whole stars are easier to use and to understand.

Which demo uses JavaScript?

Only the review card, to update the helpful count.