Free Rating Input Widget
Collect 1–5 star ratings and comments from visitors with one script tag.
A compact 5-star rating input with an optional comment box that POSTs each response to your own endpoint. Perfect for collecting product, service, or content feedback on any page. Paste one script tag where you want it to appear — no dependencies, no build step.
Embed this widget
Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.
<script src="https://hayneslab.dev/widgets/rating-input.js" data-hl-action="/api/widgets/rating-input" async></script>
<!-- Free Rating Input widget by haynes lab — https://hayneslab.dev/tools/widgets/rating-input --><!-- Free Rating Input widget by haynes lab — https://hayneslab.dev/tools/widgets/rating-input -->
<script data-hl-action="/api/widgets/rating-input">
/*!
* haynes lab — Rating Input widget (free)
* Docs & embed code: https://hayneslab.dev/tools/widgets/rating-input
*/
(function () {
'use strict';
var PREFIX = 'hlw-rating-input';
var STYLE_ID = 'hlw-rating-input-styles';
function findScript() {
return document.currentScript ||
document.querySelector('script[src*="widgets/rating-input.js"]');
}
function injectStyles() {
if (document.getElementById(STYLE_ID)) return;
var css =
'.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
'.' + PREFIX + ' { position: relative; max-width: 480px; margin: 0 auto; padding: 28px 24px 34px;' +
' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
' font-size: 16px; line-height: 1.5; color: #1f2937; background: #ffffff;' +
' border: 1px solid #e5e7eb; border-radius: 12px; text-align: center; }' +
'.' + PREFIX + '__question { margin: 0 0 14px; font-size: 17px; font-weight: 600; color: #111827; }' +
'.' + PREFIX + '__stars { display: flex; gap: 6px; justify-content: center; margin-bottom: 14px; }' +
'.' + PREFIX + '__star { border: 0; padding: 2px; background: none; cursor: pointer; font-size: 34px;' +
' line-height: 1; color: #d1d5db; transition: color 0.12s ease, transform 0.12s ease; }' +
'.' + PREFIX + '__star:hover { transform: scale(1.12); }' +
'.' + PREFIX + '__star--active { color: var(--hlw-accent); }' +
'.' + PREFIX + '__star:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; border-radius: 4px; }' +
'.' + PREFIX + '__label { min-height: 20px; margin-bottom: 12px; font-size: 14px; color: #6b7280; }' +
'.' + PREFIX + '__comment { display: block; width: 100%; min-height: 74px; margin: 0 0 14px; padding: 10px 12px;' +
' font: inherit; font-size: 14px; color: #1f2937; background: #ffffff; border: 1px solid #e5e7eb;' +
' border-radius: 8px; resize: vertical; text-align: left; }' +
'.' + PREFIX + '__comment:focus { outline: 2px solid var(--hlw-accent); outline-offset: 1px; border-color: var(--hlw-accent); }' +
'.' + PREFIX + '__submit { display: inline-block; padding: 10px 26px; font: inherit; font-size: 15px; font-weight: 600;' +
' color: #ffffff; background: var(--hlw-accent); border: 0; border-radius: 8px; cursor: pointer; }' +
'.' + PREFIX + '__submit:hover { filter: brightness(0.92); }' +
'.' + PREFIX + '__submit:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; }' +
'.' + PREFIX + '__submit:disabled { opacity: 0.55; cursor: default; }' +
'.' + PREFIX + '__error { margin: 10px 0 0; font-size: 13px; color: #b91c1c; }' +
'.' + PREFIX + '__thanks-stars { font-size: 30px; line-height: 1; color: var(--hlw-accent); margin-bottom: 10px; }' +
'.' + PREFIX + '__thanks-title { margin: 0 0 6px; font-size: 17px; font-weight: 600; color: #111827; }' +
'.' + PREFIX + '__thanks-average { margin: 0; font-size: 14px; color: #6b7280; }' +
'.' + PREFIX + '__credit { position: absolute; right: 10px; bottom: 6px; font-size: 11px;' +
' display: inline-flex; align-items: center; gap: 4px; color: #9ca3af; text-decoration: none; }' +
'.' + PREFIX + '__credit svg { display: block; }' +
'.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }' +
'@media (max-width: 560px) { .' + PREFIX + ' { padding: 22px 16px 32px; }' +
' .' + PREFIX + '__star { font-size: 30px; } }';
var style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = css;
document.head.appendChild(style);
}
function el(tag, className, text) {
var node = document.createElement(tag);
if (className) node.className = className;
if (text != null) node.textContent = text;
return node;
}
function logoSvg() {
var svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('viewBox', '0 0 32 32');
svg.setAttribute('width', '13');
svg.setAttribute('height', '13');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
function circle(cx, cy, r, attrs) {
var c = document.createElementNS(svgNS, 'circle');
c.setAttribute('cx', cx);
c.setAttribute('cy', cy);
c.setAttribute('r', r);
for (var key in attrs) c.setAttribute(key, attrs[key]);
svg.appendChild(c);
}
circle(16, 16, 16, { fill: '#0f0a0a' });
circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
circle(16, 16, 3, { fill: '#06b6d4' });
circle(24.5, 7.5, 2, { fill: '#f97316' });
circle(7, 16, 1.8, { fill: '#f97316' });
circle(20, 20, 1.5, { fill: '#f97316' });
return svg;
}
function build(script) {
var action = script.getAttribute('data-hl-action');
if (!action) {
console.warn('[haynes lab rating-input] Missing required data-hl-action attribute (endpoint URL). Nothing rendered.');
return;
}
var question = script.getAttribute('data-hl-question') || 'How would you rate your experience?';
var accent = script.getAttribute('data-hl-accent') || '#f97316';
injectStyles();
var LABELS = ['Poor', 'Fair', 'Good', 'Very good', 'Excellent'];
var root = el('div', PREFIX);
root.style.setProperty('--hlw-accent', accent);
var rating = 0;
var form = el('div', PREFIX + '__form');
form.appendChild(el('p', PREFIX + '__question', question));
var stars = el('div', PREFIX + '__stars');
stars.setAttribute('role', 'radiogroup');
stars.setAttribute('aria-label', question);
var label = el('div', PREFIX + '__label');
var starButtons = [];
function paint(value) {
starButtons.forEach(function (btn, i) {
var on = i < value;
btn.classList.toggle(PREFIX + '__star--active', on);
btn.setAttribute('aria-checked', value === i + 1 ? 'true' : 'false');
});
label.textContent = value ? LABELS[value - 1] : '';
}
for (var i = 1; i <= 5; i++) {
(function (value) {
var btn = el('button', PREFIX + '__star', '★');
btn.type = 'button';
btn.setAttribute('role', 'radio');
btn.setAttribute('aria-checked', 'false');
btn.setAttribute('aria-label', value + ' out of 5 stars — ' + LABELS[value - 1]);
btn.addEventListener('click', function () { rating = value; paint(value); });
btn.addEventListener('mouseenter', function () { paint(value); });
btn.addEventListener('focus', function () { paint(value); });
starButtons.push(btn);
stars.appendChild(btn);
})(i);
}
stars.addEventListener('mouseleave', function () { paint(rating); });
form.appendChild(stars);
form.appendChild(label);
var comment = el('textarea', PREFIX + '__comment');
comment.setAttribute('aria-label', 'Optional comment');
comment.placeholder = 'Tell us more (optional)';
form.appendChild(comment);
var submit = el('button', PREFIX + '__submit', 'Submit rating');
submit.type = 'button';
form.appendChild(submit);
var error = el('p', PREFIX + '__error');
error.setAttribute('role', 'alert');
function showThanks(average, count) {
root.removeChild(form);
if (error.parentNode) error.parentNode.removeChild(error);
var thanks = el('div', PREFIX + '__thanks');
thanks.setAttribute('role', 'status');
var starsText = '';
for (var s = 1; s <= 5; s++) starsText += s <= rating ? '★' : '☆';
thanks.appendChild(el('div', PREFIX + '__thanks-stars', starsText));
thanks.appendChild(el('p', PREFIX + '__thanks-title', 'Thanks for your feedback!'));
if (typeof average === 'number' && isFinite(average)) {
var text = 'Average rating: ' + average.toFixed(1) + ' out of 5';
if (typeof count === 'number' && isFinite(count)) text += ' (' + count + ' ratings)';
thanks.appendChild(el('p', PREFIX + '__thanks-average', text));
}
root.appendChild(thanks);
}
submit.addEventListener('click', function () {
if (!rating) {
error.textContent = 'Please select a star rating first.';
if (!error.parentNode) form.appendChild(error);
return;
}
if (error.parentNode) error.parentNode.removeChild(error);
submit.disabled = true;
submit.textContent = 'Sending…';
fetch(action, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
rating: rating,
comment: comment.value.trim(),
question: question,
url: window.location.href,
timestamp: new Date().toISOString()
})
}).then(function (res) {
if (!res.ok) throw new Error('Request failed with status ' + res.status);
return res.json().catch(function () { return {}; });
}).then(function (data) {
showThanks(data && data.average, data && data.count);
}).catch(function (e) {
console.warn('[haynes lab rating-input] Failed to submit rating.', e);
submit.disabled = false;
submit.textContent = 'Submit rating';
error.textContent = 'Something went wrong. Please try again.';
form.appendChild(error);
});
});
root.appendChild(form);
var credit = el('a', PREFIX + '__credit');
credit.appendChild(logoSvg());
credit.appendChild(document.createTextNode('Powered by haynes lab'));
credit.href = 'https://hayneslab.dev/tools/widgets/rating-input';
credit.target = '_blank';
credit.rel = 'noopener';
root.appendChild(credit);
if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
script.insertAdjacentElement('afterend', root);
} else {
document.body.appendChild(root);
}
}
function init() {
var script = findScript();
if (!script) {
console.warn('[haynes lab rating-input] Could not locate its own script tag. Nothing rendered.');
return;
}
build(script);
}
if (document.body) {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
})();
</script>Configuration
Every option is an attribute on the script tag — the same attributes the customizer above exposes. All attributes are optional unless marked required — defaults apply when omitted.
| Attribute | Default | Description |
|---|---|---|
data-hl-action | (required) | URL the widget POSTs the rating, comment, and question to as JSON. |
data-hl-question | "How would you rate your experience?" | The prompt shown above the five stars. |
data-hl-accent | "#f97316" | Color used for the selected stars and the submit button. |
Frequently asked questions
How do I install the Rating Input?
Paste the script tag where you want the widget to appear and set data-hl-action to your endpoint URL. The rating card renders inline right after the script tag.
Can I change the question and colors?
Yes. Use data-hl-question to set the prompt text and data-hl-accent to set the star and button color. The layout adapts to any light site design.
Does it work on WordPress, Shopify, or Squarespace?
Yes — it is a single vanilla JS file with no dependencies. Drop the script tag into an HTML block on any platform and point data-hl-action at your endpoint.
More free widgets
Feedback Widget
A floating side tab that opens a quick rating and comment form.
View toolForms & Lead CaptureSurvey Widget
A one-question-at-a-time survey that posts answers to your endpoint.
View toolForms & Lead CaptureContact Form
A name, email, and message contact form you can embed anywhere with one tag.
View toolNeed a custom widget?
We build custom widgets, integrations, and whole products. Tell us what your site needs and we'll scope it.
Get in Touch