Beoordeling met rapportcijfer New
Rating with a Dutch school grade from 1 to 10 including what it means (8 is good), a comment field that adapts to the grade and stays optional, and every state: hover, focus, selected, error without a grade, text too long, sending and thank you. Real radio buttons, so arrow keys and Tab just work. The textarea grows with field-sizing; without support it is resizable by hand.
How to use it
- Paste the HTML where the forms & inputs should appear.
- Add the CSS to your stylesheet. All classes are prefixed, so nothing clashes with your own styles.
- Add the JavaScript and call init() with the element that contains the HTML.
<!-- Beoordeling met rapportcijfer · NedDev · eigen werk. -->
<div class="jouw-container">
<form class="cbr" novalidate>
<fieldset class="cbr__fs">
<legend class="cbr__q">Welk cijfer geef je je APK bij Garage Van Leeuwen?</legend>
<div class="cbr__scale"></div>
<p class="cbr__mean" aria-hidden="true"><span>1 slecht</span><b class="cbr__now"></b><span>10 uitmuntend</span></p>
</fieldset>
<p class="cbr__err" id="cbr-e" role="alert"></p>
<label class="cbr__lbl" for="cbr-t">Toelichting <span>(optioneel)</span></label>
<textarea class="cbr__ta" id="cbr-t" rows="2" aria-describedby="cbr-c"></textarea>
<div class="cbr__foot">
<span class="cbr__count" id="cbr-c">0 / 300</span>
<button type="submit" class="cbr__send" aria-disabled="true">Beoordeling versturen</button>
</div>
<div class="cbr__done" hidden tabindex="-1">
<p class="cbr__big"></p>
<p class="cbr__thx">Dank je wel. Je beoordeling staat binnen een dag op de site, met je voornaam en woonplaats.</p>
<button type="button" class="cbr__edit">Cijfer aanpassen</button>
</div>
</form>
</div>/* Beoordeling met rapportcijfer · NedDev · eigen werk. */
.cbr{--ink:#0a0a0a;--pop:#F5480F;--pd:#b83a0b;--mut:#5b5b5b;--line:rgba(10,10,10,.14);position:absolute;inset:0;margin:0;overflow:auto;padding:12px 14px;font-family:'Switzer',system-ui,sans-serif;font-size:12.5px;line-height:1.4;color:var(--ink);background:#fff;scrollbar-width:thin;text-align:left}
.cbr *,.cbr *::before,.cbr *::after{box-sizing:border-box}
.cbr__fs{margin:0;padding:0;border:0;min-width:0}
.cbr__q{padding:0;margin:0 0 8px;font-size:14px;font-weight:600;line-height:1.3}
.cbr__scale{display:flex;gap:3px}
.cbr__opt{position:relative;flex:1;min-width:0}
.cbr__opt input{position:absolute;inset:0;opacity:0;margin:0;cursor:pointer}
.cbr__opt span{display:grid;place-items:center;height:34px;border:1px solid var(--line);border-radius:4px;background:#fff;font-weight:600;font-size:13px;font-variant-numeric:tabular-nums;transition:border-color .15s,background-color .15s,color .15s}
.cbr__opt input:hover + span{border-color:var(--ink)}
.cbr__opt input:focus-visible + span{outline:2px solid var(--pop);outline-offset:2px}
.cbr__opt input:checked + span{background:var(--ink);border-color:var(--ink);color:#fff}
.cbr__opt.is-low input:checked + span{background:var(--pd);border-color:var(--pd)}
.cbr.is-err .cbr__opt span{border-color:var(--pd)}
.cbr__mean{display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin:5px 0 0;font-size:11px;color:var(--mut)}
.cbr__now{font-size:12px;color:var(--ink)}
.cbr__err{margin:6px 0 0;color:var(--pd);font-weight:500;font-size:12px}
.cbr__err:empty{display:none}
.cbr__lbl{display:block;margin:10px 0 3px;font-weight:600;font-size:12px}
.cbr__lbl span{font-weight:400;color:var(--mut)}
.cbr__ta{display:block;width:100%;padding:7px 8px;border:1px solid rgba(10,10,10,.3);border-radius:4px;font:500 12.5px/1.4 'Switzer',system-ui,sans-serif;color:var(--ink);resize:vertical;min-height:48px;transition:border-color .15s}
.cbr__ta:hover{border-color:var(--ink)}
@supports (field-sizing:content){.cbr__ta{field-sizing:content;max-height:120px}}
.cbr__ta[aria-invalid="true"]{border-color:var(--pd)}
.cbr__foot{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:8px}
.cbr__count{font-size:11px;color:var(--mut);font-variant-numeric:tabular-nums}
.cbr__count.is-over{color:var(--pd);font-weight:600}
.cbr__send{height:32px;padding:0 14px;border:0;border-radius:4px;background:var(--ink);color:#fff;font:600 12.5px/1 'Switzer',system-ui,sans-serif;cursor:pointer;transition:background-color .15s,opacity .15s}
.cbr__send:hover{background:#2a2a2a}
.cbr__send[aria-disabled="true"]{opacity:.4;cursor:not-allowed}
.cbr__send[aria-disabled="true"]:hover{background:var(--ink)}
.cbr__send[aria-busy="true"]{opacity:.6;cursor:progress}
.cbr button:focus-visible,.cbr textarea:focus-visible{outline:2px solid var(--pop);outline-offset:2px}
.cbr__done{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;align-items:flex-start;padding:16px 18px;background:#fff}
.cbr__done[hidden]{display:none}
.cbr__done:focus{outline:none}
.cbr__big{margin:0;font-size:28px;font-weight:700;letter-spacing:-.02em;line-height:1.1}
.cbr__thx{margin:6px 0 12px;max-width:34ch;color:#333}
.cbr__edit{padding:0;border:0;background:none;font:500 12.5px/1.2 'Switzer',system-ui,sans-serif;color:var(--ink);text-decoration:underline;text-underline-offset:2px;cursor:pointer}
.cbr__edit:hover{color:var(--pd)}
@media (max-width:420px){.cbr__opt span{height:30px;font-size:12px}.cbr__scale{gap:2px}.cbr__q{font-size:13px}}
@media (prefers-reduced-motion:reduce){.cbr *{transition:none!important}}// Beoordeling met rapportcijfer · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
const f = root.querySelector('.cbr'); if (!f) return;
const scale = f.querySelector('.cbr__scale'), now = f.querySelector('.cbr__now'), err = f.querySelector('.cbr__err');
const ta = f.querySelector('.cbr__ta'), cnt = f.querySelector('.cbr__count'), send = f.querySelector('.cbr__send');
const lbl = f.querySelector('.cbr__lbl'), done = f.querySelector('.cbr__done');
const MAX = 300;
const MEAN = ['', 'zeer slecht', 'slecht', 'zeer onvoldoende', 'onvoldoende', 'bijna voldoende', 'voldoende', 'ruim voldoende', 'goed', 'zeer goed', 'uitmuntend'];
scale.innerHTML = Array.from({ length: 10 }, (_, i) => '<label class="cbr__opt' + (i < 5 ? ' is-low' : '') + '"><input type="radio" name="cbr-cijfer" value="' + (i + 1) + '" aria-label="' + (i + 1) + ', ' + MEAN[i + 1] + '"><span aria-hidden="true">' + (i + 1) + '</span></label>').join('');
const val = () => { const c = f.querySelector('input[name="cbr-cijfer"]:checked'); return c ? +c.value : 0; };
function sync() {
const v = val(), n = ta.value.length;
now.textContent = v ? v + ', ' + MEAN[v] : '';
lbl.firstChild.textContent = !v ? 'Toelichting ' : v <= 5 ? 'Wat ging er mis? ' : v <= 7 ? 'Wat kan beter? ' : 'Wat ging goed? ';
cnt.textContent = n + ' / ' + MAX;
cnt.classList.toggle('is-over', n > MAX);
ta.setAttribute('aria-invalid', String(n > MAX));
send.setAttribute('aria-disabled', String(!v || n > MAX));
}
f.addEventListener('change', e => { if (e.target.name === 'cbr-cijfer') { err.textContent = ''; f.classList.remove('is-err'); sync(); } });
ta.addEventListener('input', () => { sync(); if (ta.value.length <= MAX && err.dataset.k === 'len') err.textContent = ''; });
f.addEventListener('submit', e => {
e.preventDefault();
if (send.getAttribute('aria-busy') === 'true') return;
const v = val();
if (!v) { err.dataset.k = 'cijfer'; err.textContent = 'Kies eerst een cijfer van 1 tot 10.'; f.classList.add('is-err'); f.querySelector('input[name="cbr-cijfer"]').focus(); return; }
if (ta.value.length > MAX) { err.dataset.k = 'len'; err.textContent = 'Je toelichting is ' + (ta.value.length - MAX) + ' tekens te lang.'; ta.focus(); return; }
send.setAttribute('aria-busy', 'true'); send.textContent = 'Bezig met versturen...';
setTimeout(() => {
send.removeAttribute('aria-busy'); send.textContent = 'Beoordeling versturen';
f.querySelector('.cbr__big').textContent = 'Je gaf een ' + v;
f.scrollTop = 0; done.hidden = false; done.focus();
}, 700);
});
f.querySelector('.cbr__edit').addEventListener('click', () => { done.hidden = true; const c = f.querySelector('input[name="cbr-cijfer"]:checked'); (c || f.querySelector('input')).focus(); });
sync();
}
init(document.querySelector('.jouw-container'));
More forms & inputs
Seen something you like?
Add it to your selection and send it over. We build your site around it, in your brand.
Get in touch →