UI Components Easy
Rating Stars
Interactive star rating component — hover preview, click to set, half-star display support, readonly mode.
Open in Lab
MCP
css vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Inter, system-ui, sans-serif;
background: #050910;
color: #f2f6ff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.demo {
width: 100%;
max-width: 420px;
}
.demo-title {
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 0.375rem;
}
.demo-sub {
color: #475569;
font-size: 0.875rem;
margin-bottom: 2rem;
}
.section {
margin-bottom: 1.75rem;
}
.section-label {
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #475569;
margin-bottom: 0.75rem;
}
/* ── Rating container ── */
.rating {
display: flex;
align-items: center;
gap: 0.25rem;
}
/* ── Interactive star buttons ── */
.star-btn {
background: none;
border: none;
padding: 0.125rem;
cursor: pointer;
color: #2d3d55;
transition: color 0.1s, transform 0.1s;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.star-btn:focus-visible {
outline: 2px solid rgba(99, 179, 237, 0.5);
outline-offset: 2px;
}
.star-btn.is-hovered,
.star-btn.is-selected {
color: #f59e0b;
}
.star-btn.is-hovered {
transform: scale(1.15);
}
/* ── SVG star ── */
.star {
width: 1.75rem;
height: 1.75rem;
fill: currentColor;
stroke: currentColor;
stroke-width: 1;
display: block;
}
/* ── Rating label ── */
.rating-label {
margin-left: 0.5rem;
font-size: 0.8125rem;
color: #475569;
min-width: 5rem;
font-weight: 500;
}
/* ── Readonly stars ── */
.rating--readonly {
gap: 0.25rem;
}
.star-static {
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
}
.star-static--full {
color: #f59e0b;
}
.star-static--empty {
color: #2d3d55;
}
/* ── Half star ── */
.star-static--half {
color: #2d3d55;
position: relative;
}
.star-static--half .star--empty {
fill: #2d3d55;
stroke: #2d3d55;
}
.star-static--half .star--fill-half {
position: absolute;
inset: 0;
fill: #f59e0b;
stroke: #f59e0b;
clip-path: inset(0 50% 0 0);
}
/* ── Rating value label ── */
.rating-value {
margin-left: 0.625rem;
font-size: 0.875rem;
font-weight: 700;
color: #f59e0b;
}var LABELS = [
"No rating",
"1 star — Poor",
"2 stars — Fair",
"3 stars — Good",
"4 stars — Very good",
"5 stars — Excellent",
];
document.querySelectorAll(".rating:not(.rating--readonly)").forEach(function (widget) {
var buttons = widget.querySelectorAll(".star-btn");
var labelEl = widget.querySelector(".rating-label");
var selected = parseInt(widget.dataset.rating, 10) || 0;
function paint(hovered) {
var active = hovered > 0 ? hovered : selected;
buttons.forEach(function (btn) {
var v = parseInt(btn.dataset.value, 10);
btn.classList.toggle("is-selected", v <= active && hovered === 0);
btn.classList.toggle("is-hovered", v <= active && hovered > 0);
});
}
function updateLabel(val) {
if (labelEl) labelEl.textContent = LABELS[val] || LABELS[0];
}
buttons.forEach(function (btn) {
btn.addEventListener("mouseover", function () {
paint(parseInt(btn.dataset.value, 10));
});
btn.addEventListener("click", function () {
selected = parseInt(btn.dataset.value, 10);
widget.dataset.rating = selected;
buttons.forEach(function (b) {
b.setAttribute("aria-checked", b === btn ? "true" : "false");
});
updateLabel(selected);
paint(0);
});
btn.addEventListener("keydown", function (e) {
var idx = Array.from(buttons).indexOf(btn);
if (e.key === "ArrowRight" || e.key === "ArrowUp") {
e.preventDefault();
var next = buttons[Math.min(idx + 1, buttons.length - 1)];
next.focus();
next.click();
}
if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
e.preventDefault();
var prev = buttons[Math.max(idx - 1, 0)];
prev.focus();
prev.click();
}
});
});
widget.addEventListener("mouseleave", function () {
paint(0);
});
// Initialise
paint(0);
updateLabel(selected);
});<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rating Stars</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h1 class="demo-title">Rating Stars</h1>
<p class="demo-sub">Hover to preview, click to set. Half-star display in readonly mode.</p>
<!-- Interactive -->
<section class="section">
<p class="section-label">Interactive</p>
<div class="rating" role="radiogroup" aria-label="Rate this item" data-rating="0">
<button class="star-btn" data-value="1" aria-label="1 star" role="radio" aria-checked="false">
<svg class="star" viewBox="0 0 24 24" aria-hidden="true"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</button>
<button class="star-btn" data-value="2" aria-label="2 stars" role="radio" aria-checked="false">
<svg class="star" viewBox="0 0 24 24" aria-hidden="true"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</button>
<button class="star-btn" data-value="3" aria-label="3 stars" role="radio" aria-checked="false">
<svg class="star" viewBox="0 0 24 24" aria-hidden="true"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</button>
<button class="star-btn" data-value="4" aria-label="4 stars" role="radio" aria-checked="false">
<svg class="star" viewBox="0 0 24 24" aria-hidden="true"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</button>
<button class="star-btn" data-value="5" aria-label="5 stars" role="radio" aria-checked="false">
<svg class="star" viewBox="0 0 24 24" aria-hidden="true"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</button>
<span class="rating-label" aria-live="polite">No rating</span>
</div>
</section>
<!-- Readonly 4.5 -->
<section class="section">
<p class="section-label">Read-only — 4.5 stars</p>
<div class="rating rating--readonly" aria-label="Rating: 4.5 out of 5 stars">
<span class="star-static star-static--full" aria-hidden="true">
<svg class="star" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</span>
<span class="star-static star-static--full" aria-hidden="true">
<svg class="star" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</span>
<span class="star-static star-static--full" aria-hidden="true">
<svg class="star" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</span>
<span class="star-static star-static--full" aria-hidden="true">
<svg class="star" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</span>
<span class="star-static star-static--half" aria-hidden="true">
<svg class="star star--empty" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
<svg class="star star--fill-half" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</span>
<span class="rating-value">4.5</span>
</div>
</section>
<!-- Readonly 3 -->
<section class="section">
<p class="section-label">Read-only — 3 stars</p>
<div class="rating rating--readonly" aria-label="Rating: 3 out of 5 stars">
<span class="star-static star-static--full" aria-hidden="true">
<svg class="star" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</span>
<span class="star-static star-static--full" aria-hidden="true">
<svg class="star" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</span>
<span class="star-static star-static--full" aria-hidden="true">
<svg class="star" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</span>
<span class="star-static star-static--empty" aria-hidden="true">
<svg class="star" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</span>
<span class="star-static star-static--empty" aria-hidden="true">
<svg class="star" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/></svg>
</span>
<span class="rating-value">3.0</span>
</div>
</section>
</div>
<script src="script.js"></script>
</body>
</html>Rating Stars
Star rating widget with hover preview and click-to-set interaction. Supports half-star display in readonly mode. Keyboard accessible via arrow keys.
Variants
| Variant | Description |
|---|---|
| Interactive 5-star | Click or hover to set rating |
| Readonly 4.5 stars | Half-star support via CSS clip-path |
| Readonly 3 stars | Static display |
Implementation
Stars are rendered as SVG paths. Interactive mode uses mouseover / click events. The half-star in readonly mode is achieved by overlaying a filled star clipped to 50% width over an empty star.