Choice Cards
Cards to compare and pick, on real radio buttons or checkboxes, with a title, a line and an optional price. The picked card's ink border draws itself around it from the corner while a check draws in its mark. Pure CSS.
View codeA score on real radio buttons, from 0 to 10 or 1 to 5. The picked score fills with ink from the bottom, its end lights up, and an optional follow-up question unfolds and rolls to match the score. Pure CSS.
With your AI
Say it to your AI. It gets the exact code below, not its best guess, and knows where it goes.
Add the Kodza Rating Scale to my page.
claude mcp add -t http kodza https://kodza.dev/api/mcp
<fieldset class="kodza-rating-scale">
<legend class="kodza-rating-scale__legend">How likely are you to recommend Kodza to another dev?</legend>
<div class="kodza-rating-scale__scale">
<label class="kodza-rating-scale__option" data-end="low">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="0">
<span class="kodza-rating-scale__value">0<span class="kodza-rating-scale__sr"> (not likely)</span></span>
<span class="kodza-rating-scale__ink" aria-hidden="true">0</span>
</label>
<label class="kodza-rating-scale__option" data-end="low">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="1">
<span class="kodza-rating-scale__value">1</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">1</span>
</label>
<label class="kodza-rating-scale__option" data-end="low">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="2">
<span class="kodza-rating-scale__value">2</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">2</span>
</label>
<label class="kodza-rating-scale__option" data-end="low">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="3">
<span class="kodza-rating-scale__value">3</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">3</span>
</label>
<label class="kodza-rating-scale__option">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="4">
<span class="kodza-rating-scale__value">4</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">4</span>
</label>
<label class="kodza-rating-scale__option">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="5">
<span class="kodza-rating-scale__value">5</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">5</span>
</label>
<label class="kodza-rating-scale__option">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="6">
<span class="kodza-rating-scale__value">6</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">6</span>
</label>
<label class="kodza-rating-scale__option">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="7">
<span class="kodza-rating-scale__value">7</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">7</span>
</label>
<label class="kodza-rating-scale__option" data-end="high">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="8">
<span class="kodza-rating-scale__value">8</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">8</span>
</label>
<label class="kodza-rating-scale__option" data-end="high">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="9">
<span class="kodza-rating-scale__value">9</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">9</span>
</label>
<label class="kodza-rating-scale__option" data-end="high">
<input class="kodza-rating-scale__input" type="radio" name="recommend" value="10">
<span class="kodza-rating-scale__value">10<span class="kodza-rating-scale__sr"> (ship it to everyone)</span></span>
<span class="kodza-rating-scale__ink" aria-hidden="true">10</span>
</label>
</div>
<div class="kodza-rating-scale__ends" aria-hidden="true">
<span class="kodza-rating-scale__end kodza-rating-scale__end--low">Not likely</span>
<span class="kodza-rating-scale__end kodza-rating-scale__end--high">Ship it to everyone</span>
</div>
<div class="kodza-rating-scale__follow">
<div class="kodza-rating-scale__follow-inner">
<label class="kodza-rating-scale__box">
<span class="kodza-rating-scale__ask">
<span class="kodza-rating-scale__question kodza-rating-scale__question--low">What broke? Be blunt.</span>
<span class="kodza-rating-scale__question kodza-rating-scale__question--mid">What would make it a 10?</span>
<span class="kodza-rating-scale__question kodza-rating-scale__question--high">Nice. What sold you?</span>
</span>
<textarea class="kodza-rating-scale__text" name="recommend-why" rows="2"></textarea>
</label>
</div>
</div>
</fieldset>
<fieldset class="kodza-rating-scale kodza-rating-scale--fill">
<legend class="kodza-rating-scale__legend">How painful was the setup?</legend>
<div class="kodza-rating-scale__scale">
<label class="kodza-rating-scale__option" data-end="low">
<input class="kodza-rating-scale__input" type="radio" name="setup-pain" value="1">
<span class="kodza-rating-scale__value">1<span class="kodza-rating-scale__sr"> (barely noticed)</span></span>
<span class="kodza-rating-scale__ink" aria-hidden="true">1</span>
</label>
<label class="kodza-rating-scale__option" data-end="low">
<input class="kodza-rating-scale__input" type="radio" name="setup-pain" value="2" checked>
<span class="kodza-rating-scale__value">2</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">2</span>
</label>
<label class="kodza-rating-scale__option">
<input class="kodza-rating-scale__input" type="radio" name="setup-pain" value="3">
<span class="kodza-rating-scale__value">3</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">3</span>
</label>
<label class="kodza-rating-scale__option" data-end="high">
<input class="kodza-rating-scale__input" type="radio" name="setup-pain" value="4">
<span class="kodza-rating-scale__value">4</span>
<span class="kodza-rating-scale__ink" aria-hidden="true">4</span>
</label>
<label class="kodza-rating-scale__option" data-end="high">
<input class="kodza-rating-scale__input" type="radio" name="setup-pain" value="5">
<span class="kodza-rating-scale__value">5<span class="kodza-rating-scale__sr"> (send help)</span></span>
<span class="kodza-rating-scale__ink" aria-hidden="true">5</span>
</label>
</div>
<div class="kodza-rating-scale__ends" aria-hidden="true">
<span class="kodza-rating-scale__end kodza-rating-scale__end--low">Barely noticed</span>
<span class="kodza-rating-scale__end kodza-rating-scale__end--high">Send help</span>
</div>
</fieldset>
/* Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license */
/* Kodza — Rating Scale
A score on real radio buttons: NPS from 0 to 10, or 1 to 5 after a purchase or a ticket. The
score under the pointer lifts, the picked one fills with ink from the bottom (its number turns
white exactly at the ink line), and the word of its end lights up. An optional follow-up
question unfolds after the score, and rolls to match it: a low score, a middle one, a high one.
Which scores count as an end is in the HTML: data-end="low" or data-end="high" on an option.
--fill reads as a level: every score up to the picked one fills.
Pure CSS: the form gets the value as usual, and the keyboard is the radio group's own.
Everything is tuned with the --kz-rating-scale-* variables. */
.kodza-rating-scale {
--kz-rating-scale-bg: #ffffff;
--kz-rating-scale-fg: #0a0a0a;
--kz-rating-scale-muted: rgba(10, 10, 10, 0.58);
--kz-rating-scale-border: rgba(10, 10, 10, 0.1);
--kz-rating-scale-border-hover: rgba(10, 10, 10, 0.28);
--kz-rating-scale-ink: #0a0a0a;
--kz-rating-scale-on-ink: #ffffff;
--kz-rating-scale-soft: rgba(10, 10, 10, 0.06); /* the halo of the follow-up box */
--kz-rating-scale-shadow: rgba(10, 10, 10, 0.18); /* under a lifted score */
--kz-rating-scale-cell: 44px; /* a score's height, and its width when there is room */
--kz-rating-scale-gap: 4px;
--kz-rating-scale-lift: 3px; /* how far the score under the pointer rises */
display: block;
width: fit-content;
min-width: 0;
max-width: 100%;
margin: 0;
padding: 0;
border: 0;
color: var(--kz-rating-scale-fg);
}
.kodza-rating-scale__legend {
margin-bottom: 14px;
padding: 0;
font-size: 15px;
font-weight: 500;
line-height: 1.4;
text-wrap: pretty;
}
/* For screen readers only: what the two ends mean */
.kodza-rating-scale__sr {
position: absolute;
width: 1px;
height: 1px;
margin: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
/* Grid tracks that may shrink to zero: the scale's smallest width is only its gaps,
so it fits a narrow column even when a theme sets its own max-width on the root */
.kodza-rating-scale__scale {
display: grid;
grid-auto-flow: column;
grid-auto-columns: minmax(0, var(--kz-rating-scale-cell));
gap: var(--kz-rating-scale-gap);
}
.kodza-rating-scale__option {
position: relative;
display: grid;
place-items: center;
box-sizing: border-box;
min-width: 0;
height: var(--kz-rating-scale-cell);
margin: 0;
border-radius: calc(10px * var(--kz-radius-scale, 1));
background: var(--kz-rating-scale-bg);
box-shadow: inset 0 0 0 1px var(--kz-rating-scale-border);
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
transition:
transform calc(0.25s * var(--kz-motion-scale, 1)) cubic-bezier(0.22, 1, 0.36, 1),
box-shadow calc(0.25s * var(--kz-motion-scale, 1)) ease;
}
/* The real radio covers its score: a click anywhere picks it */
.kodza-rating-scale__input {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: inherit;
}
.kodza-rating-scale__value,
.kodza-rating-scale__ink {
font-family: var(--kz-font-label, inherit);
font-size: var(--kz-font-label-size, 15px);
font-weight: var(--kz-font-label-weight, 500);
line-height: 1;
letter-spacing: var(--kz-font-label-tracking, inherit);
font-variant-numeric: tabular-nums;
}
/* The ink rises from the bottom and carries a white copy of the number, cut at the same line */
.kodza-rating-scale__ink {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: inherit;
background: var(--kz-rating-scale-ink);
color: var(--kz-rating-scale-on-ink);
clip-path: inset(100% 0 0 0);
pointer-events: none;
transition: clip-path calc(0.45s * var(--kz-motion-scale, 1)) cubic-bezier(0.22, 1, 0.36, 1);
}
.kodza-rating-scale__option:has(.kodza-rating-scale__input:checked) .kodza-rating-scale__ink,
.kodza-rating-scale--fill .kodza-rating-scale__option:has(~ .kodza-rating-scale__option .kodza-rating-scale__input:checked) .kodza-rating-scale__ink {
clip-path: inset(0 0 0 0);
}
@media (hover: hover) {
.kodza-rating-scale__option:hover {
box-shadow:
inset 0 0 0 1px var(--kz-rating-scale-border-hover),
0 8px 14px -10px var(--kz-rating-scale-shadow);
transform: translateY(calc(-1 * var(--kz-rating-scale-lift)));
}
}
.kodza-rating-scale__option:active {
transform: none;
}
.kodza-rating-scale__option:has(.kodza-rating-scale__input:focus-visible) {
outline: 2px solid var(--kz-rating-scale-fg);
outline-offset: 2px;
}
.kodza-rating-scale__option:has(.kodza-rating-scale__input:disabled) {
cursor: not-allowed;
opacity: 0.4;
}
/* The two ends: the one on the picked side lights up, with a dot */
.kodza-rating-scale__ends {
display: flex;
justify-content: space-between;
gap: 16px;
margin-top: 10px;
font-family: var(--kz-font-label, var(--kz-font-mono, ui-monospace, "SF Mono", Menlo, monospace));
font-size: var(--kz-font-label-size, 12px);
font-weight: var(--kz-font-label-weight, inherit);
line-height: var(--kz-font-label-leading, 1.4);
letter-spacing: var(--kz-font-label-tracking, inherit);
text-transform: var(--kz-font-label-case, inherit);
color: var(--kz-rating-scale-muted);
}
.kodza-rating-scale__end {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
transition: color calc(0.25s * var(--kz-motion-scale, 1)) ease;
}
.kodza-rating-scale__end--high {
flex-direction: row-reverse;
}
.kodza-rating-scale__end::before {
content: "";
flex: none;
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
transform: scale(0);
transition: transform calc(0.35s * var(--kz-motion-scale, 1)) cubic-bezier(0.22, 1, 0.36, 1);
}
.kodza-rating-scale:has([data-end="low"] .kodza-rating-scale__input:checked) .kodza-rating-scale__end--low,
.kodza-rating-scale:has([data-end="high"] .kodza-rating-scale__input:checked) .kodza-rating-scale__end--high {
color: var(--kz-rating-scale-fg);
}
.kodza-rating-scale:has([data-end="low"] .kodza-rating-scale__input:checked) .kodza-rating-scale__end--low::before,
.kodza-rating-scale:has([data-end="high"] .kodza-rating-scale__input:checked) .kodza-rating-scale__end--high::before {
transform: scale(1);
}
/* The follow-up: folded away until a score is picked, then its height eases open. It never
widens the scale (contain), and keeps room for the box's focus halo */
.kodza-rating-scale__follow {
display: grid;
grid-template-rows: 0fr;
contain: inline-size;
visibility: hidden;
transition:
grid-template-rows calc(0.5s * var(--kz-motion-scale, 1)) cubic-bezier(0.22, 1, 0.36, 1),
visibility 0s linear calc(0.5s * var(--kz-motion-scale, 1));
}
.kodza-rating-scale:has(.kodza-rating-scale__input:checked) .kodza-rating-scale__follow {
grid-template-rows: 1fr;
visibility: visible;
transition-delay: 0s;
}
.kodza-rating-scale__follow-inner {
min-height: 0;
margin: 0 -4px;
padding: 0 4px;
overflow: hidden;
}
/* The box of the family's Text Field, its label folded up for good: the question */
.kodza-rating-scale__box {
position: relative;
display: block;
box-sizing: border-box;
margin: 18px 0 4px;
padding: 30px 16px 12px;
border-radius: calc(12px * var(--kz-radius-scale, 1));
background: var(--kz-rating-scale-bg);
box-shadow: inset 0 0 0 1px var(--kz-rating-scale-border);
cursor: text;
opacity: 0;
transform: translateY(-6px);
transition:
box-shadow calc(0.25s * var(--kz-motion-scale, 1)) ease,
opacity calc(0.3s * var(--kz-motion-scale, 1)) ease,
transform calc(0.45s * var(--kz-motion-scale, 1)) cubic-bezier(0.22, 1, 0.36, 1);
}
.kodza-rating-scale:has(.kodza-rating-scale__input:checked) .kodza-rating-scale__box {
opacity: 1;
transform: none;
transition-delay: 0s, calc(0.08s * var(--kz-motion-scale, 1)), calc(0.08s * var(--kz-motion-scale, 1));
}
.kodza-rating-scale__box:hover {
box-shadow: inset 0 0 0 1px var(--kz-rating-scale-border-hover);
}
.kodza-rating-scale__box:focus-within {
box-shadow: inset 0 0 0 1.5px var(--kz-rating-scale-fg), 0 0 0 4px var(--kz-rating-scale-soft);
}
/* The question rolls: a higher score sends the old one up and brings the new one from below,
a lower score the other way round */
.kodza-rating-scale__ask {
position: absolute;
top: 11px;
right: 16px;
left: 16px;
display: grid;
overflow: hidden;
font-family: var(--kz-font-label, inherit);
font-size: var(--kz-font-label-size, 12px);
font-weight: var(--kz-font-label-weight, inherit);
line-height: var(--kz-font-label-leading, 1.4);
letter-spacing: var(--kz-font-label-tracking, inherit);
text-transform: var(--kz-font-label-case, inherit);
color: var(--kz-rating-scale-muted);
pointer-events: none;
}
.kodza-rating-scale__question {
grid-area: 1 / 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transform: translateY(105%);
transition:
transform calc(0.42s * var(--kz-motion-scale, 1)) cubic-bezier(0.22, 1, 0.36, 1),
opacity calc(0.25s * var(--kz-motion-scale, 1)) ease,
visibility 0s linear calc(0.42s * var(--kz-motion-scale, 1));
}
.kodza-rating-scale:has(.kodza-rating-scale__option:not([data-end]) .kodza-rating-scale__input:checked) .kodza-rating-scale__question--low,
.kodza-rating-scale:has([data-end="high"] .kodza-rating-scale__input:checked) .kodza-rating-scale__question--low,
.kodza-rating-scale:has([data-end="high"] .kodza-rating-scale__input:checked) .kodza-rating-scale__question--mid {
transform: translateY(-105%);
}
.kodza-rating-scale:has([data-end="low"] .kodza-rating-scale__input:checked) .kodza-rating-scale__question--low,
.kodza-rating-scale:has(.kodza-rating-scale__option:not([data-end]) .kodza-rating-scale__input:checked) .kodza-rating-scale__question--mid,
.kodza-rating-scale:has([data-end="high"] .kodza-rating-scale__input:checked) .kodza-rating-scale__question--high {
opacity: 1;
visibility: visible;
transform: none;
transition-delay: 0s;
}
.kodza-rating-scale__text {
display: block;
box-sizing: border-box;
width: 100%;
min-height: 44px;
margin: 0;
padding: 0;
border: 0;
background: none;
color: inherit;
font: inherit;
font-size: 15px;
line-height: 22px;
outline: none;
resize: none;
}
@media (prefers-reduced-motion: reduce) {
.kodza-rating-scale__option,
.kodza-rating-scale__ink,
.kodza-rating-scale__end,
.kodza-rating-scale__end::before,
.kodza-rating-scale__follow,
.kodza-rating-scale__box,
.kodza-rating-scale__question {
transition-duration: 0.01ms !important;
transition-delay: 0s !important;
}
.kodza-rating-scale__option:hover {
transform: none;
}
}
/* Theming example — on a dark background:
.dark-section .kodza-rating-scale {
--kz-rating-scale-bg: #161616;
--kz-rating-scale-fg: #f2f2f0;
--kz-rating-scale-muted: rgba(242, 242, 240, 0.62);
--kz-rating-scale-border: rgba(255, 255, 255, 0.12);
--kz-rating-scale-border-hover: rgba(255, 255, 255, 0.3);
--kz-rating-scale-ink: #f2f2f0;
--kz-rating-scale-on-ink: #0a0a0a;
--kz-rating-scale-soft: rgba(255, 255, 255, 0.08);
--kz-rating-scale-shadow: rgba(0, 0, 0, 0.6);
}
*/
/* kodza-guard 04d7d9daf84c — generated by Kodza, do not edit.
Theme guard: keeps a site's own CSS (WordPress themes, Bootstrap, CSS resets…) off this
component. :not(#_) is always true and weighs as much as an id, so these lines win over
theme rules made of classes and tags. Customise the component through its --kz-* variables. */
/* What the browser gives, where the component sets it only in some states: at the lowest weight, so its own rules below put it back */
:where(.kodza-rating-scale__option):not(#_) {
outline: revert;
outline-offset: revert;
}
/* What the browser gives, on everything the component leaves to it */
.kodza-rating-scale:not(#_) {
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-height: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-rating-scale__legend:not(#_) {
border: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
margin-top: revert;
margin-right: revert;
margin-left: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-style: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-rating-scale__scale:not(#_),
.kodza-rating-scale__value:not(#_),
.kodza-rating-scale__sr:not(#_),
.kodza-rating-scale__ink:not(#_),
.kodza-rating-scale__ends:not(#_),
.kodza-rating-scale__end:not(#_),
.kodza-rating-scale__follow:not(#_),
.kodza-rating-scale__follow-inner:not(#_),
.kodza-rating-scale__ask:not(#_),
.kodza-rating-scale__question:not(#_) {
box-sizing: revert;
}
.kodza-rating-scale__option:not(#_) {
border: revert;
padding: revert;
text-decoration: revert;
list-style: revert;
float: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-rating-scale__input:not(#_) {
border: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
white-space: revert;
min-height: revert;
}
.kodza-rating-scale__box:not(#_) {
border: revert;
outline: revert;
text-decoration: revert;
list-style: revert;
float: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-rating-scale__text:not(#_) {
border-radius: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
float: revert;
position: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
white-space: revert;
}
.kodza-rating-scale__legend:not(#_):where(:not([hidden])),
.kodza-rating-scale__input:not(#_):where(:not([hidden])) {
display: revert;
}
.kodza-rating-scale__legend:not(#_):where([hidden]),
.kodza-rating-scale__input:not(#_):where([hidden]) {
display: none;
}
.kodza-rating-scale__end:not(#_)::before {
box-sizing: revert;
}
/* The component's own rules, one step stronger: real buttons and fields, what it sets on tags themes style (links, lists, headings, images…), and the root width (a theme may narrow it, never widen it) */
.kodza-rating-scale:not(#_) {
display: block;
padding: 0;
border: 0;
color: var(--kz-rating-scale-fg);
}
.kodza-rating-scale__legend:not(#_) {
margin-bottom: 14px;
padding: 0;
font-size: 15px;
font-weight: 500;
line-height: 1.4;
text-wrap: pretty;
}
.kodza-rating-scale__option:not(#_) {
position: relative;
display: grid;
box-sizing: border-box;
margin: 0;
border-radius: calc(10px * var(--kz-radius-scale, 1));
background: var(--kz-rating-scale-bg);
box-shadow: inset 0 0 0 1px var(--kz-rating-scale-border);
transition: transform calc(0.25s * var(--kz-motion-scale, 1)) cubic-bezier(0.22, 1, 0.36, 1),
box-shadow calc(0.25s * var(--kz-motion-scale, 1)) ease;
}
.kodza-rating-scale__input:not(#_) {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: inherit;
}
@media (hover: hover) {
.kodza-rating-scale__option:hover:not(#_) {
box-shadow: inset 0 0 0 1px var(--kz-rating-scale-border-hover),
0 8px 14px -10px var(--kz-rating-scale-shadow);
}
}
.kodza-rating-scale__option:has(.kodza-rating-scale__input:focus-visible):not(#_) {
outline: 2px solid var(--kz-rating-scale-fg);
outline-offset: 2px;
}
.kodza-rating-scale__box:not(#_) {
position: relative;
display: block;
box-sizing: border-box;
margin: 18px 0 4px;
padding: 30px 16px 12px;
border-radius: calc(12px * var(--kz-radius-scale, 1));
background: var(--kz-rating-scale-bg);
box-shadow: inset 0 0 0 1px var(--kz-rating-scale-border);
transition: box-shadow calc(0.25s * var(--kz-motion-scale, 1)) ease,
opacity calc(0.3s * var(--kz-motion-scale, 1)) ease,
transform calc(0.45s * var(--kz-motion-scale, 1)) cubic-bezier(0.22, 1, 0.36, 1);
}
.kodza-rating-scale:has(.kodza-rating-scale__input:checked) .kodza-rating-scale__box:not(#_) {
transition-delay: 0s, calc(0.08s * var(--kz-motion-scale, 1)), calc(0.08s * var(--kz-motion-scale, 1));
}
.kodza-rating-scale__box:hover:not(#_) {
box-shadow: inset 0 0 0 1px var(--kz-rating-scale-border-hover);
}
.kodza-rating-scale__box:focus-within:not(#_) {
box-shadow: inset 0 0 0 1.5px var(--kz-rating-scale-fg), 0 0 0 4px var(--kz-rating-scale-soft);
}
.kodza-rating-scale__text:not(#_) {
display: block;
box-sizing: border-box;
width: 100%;
min-height: 44px;
margin: 0;
padding: 0;
border: 0;
background: none;
color: inherit;
font: inherit;
font-size: 15px;
line-height: 22px;
outline: none;
resize: none;
}
@media (prefers-reduced-motion: reduce) {
.kodza-rating-scale__option:not(#_),
.kodza-rating-scale__box:not(#_) {
transition-duration: 0.01ms !important;
transition-delay: 0s !important;
}
}
'use client';
// Kodza — Rating Scale, for React. From https://kodza.dev/components/rating-scale/
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Next to this file: rating-scale.css, the component's CSS from Kodza, as they are.
// Yours to edit: the markup is plain JSX below.
import { useRef, type ComponentPropsWithoutRef, type ComponentRef, type CSSProperties } from 'react';
import './rating-scale.css';
type Variant = "fill";
export type RatingScaleProps = Omit<ComponentPropsWithoutRef<'fieldset'>, "children" | "variant" | "vars"> & {
/** Modifier class(es): .kodza-rating-scale--fill */
variant?: Variant | Variant[];
/** Settings, as CSS variables on the root */
vars?: {
/** default: #ffffff */
"--kz-rating-scale-bg"?: string | number;
/** default: #0a0a0a */
"--kz-rating-scale-fg"?: string | number;
/** default: rgba(10, 10, 10, 0.58) */
"--kz-rating-scale-muted"?: string | number;
/** default: rgba(10, 10, 10, 0.1) */
"--kz-rating-scale-border"?: string | number;
/** default: rgba(10, 10, 10, 0.28) */
"--kz-rating-scale-border-hover"?: string | number;
/** default: #0a0a0a */
"--kz-rating-scale-ink"?: string | number;
/** default: #ffffff */
"--kz-rating-scale-on-ink"?: string | number;
/** the halo of the follow-up box — default: rgba(10, 10, 10, 0.06) */
"--kz-rating-scale-soft"?: string | number;
/** under a lifted score — default: rgba(10, 10, 10, 0.18) */
"--kz-rating-scale-shadow"?: string | number;
/** a score's height, and its width when there is room — default: 44px */
"--kz-rating-scale-cell"?: string | number;
/** default: 4px */
"--kz-rating-scale-gap"?: string | number;
/** how far the score under the pointer rises — default: 3px */
"--kz-rating-scale-lift"?: string | number;
};
className?: string;
};
export function RatingScale({ variant, vars, className, style, ...rest }: RatingScaleProps) {
const ref = useRef<ComponentRef<'fieldset'>>(null);
const classes = ["kodza-rating-scale", ...([] as string[]).concat(variant ?? []).map((v) => `kodza-rating-scale--${v}`), className].filter(Boolean).join(' ');
return (
<fieldset {...rest} className={classes} style={{ ...vars, ...style } as CSSProperties} ref={ref}>
<legend className="kodza-rating-scale__legend">How likely are you to recommend Kodza to another dev?</legend>
<div className="kodza-rating-scale__scale">
<label className="kodza-rating-scale__option" data-end="low">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="0" />
{' '}
<span className="kodza-rating-scale__value">0<span className="kodza-rating-scale__sr"> (not likely)</span></span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">0</span>
</label>
{' '}
<label className="kodza-rating-scale__option" data-end="low">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="1" />
{' '}
<span className="kodza-rating-scale__value">1</span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">1</span>
</label>
{' '}
<label className="kodza-rating-scale__option" data-end="low">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="2" />
{' '}
<span className="kodza-rating-scale__value">2</span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">2</span>
</label>
{' '}
<label className="kodza-rating-scale__option" data-end="low">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="3" />
{' '}
<span className="kodza-rating-scale__value">3</span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">3</span>
</label>
{' '}
<label className="kodza-rating-scale__option">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="4" />
{' '}
<span className="kodza-rating-scale__value">4</span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">4</span>
</label>
{' '}
<label className="kodza-rating-scale__option">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="5" />
{' '}
<span className="kodza-rating-scale__value">5</span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">5</span>
</label>
{' '}
<label className="kodza-rating-scale__option">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="6" />
{' '}
<span className="kodza-rating-scale__value">6</span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">6</span>
</label>
{' '}
<label className="kodza-rating-scale__option">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="7" />
{' '}
<span className="kodza-rating-scale__value">7</span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">7</span>
</label>
{' '}
<label className="kodza-rating-scale__option" data-end="high">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="8" />
{' '}
<span className="kodza-rating-scale__value">8</span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">8</span>
</label>
{' '}
<label className="kodza-rating-scale__option" data-end="high">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="9" />
{' '}
<span className="kodza-rating-scale__value">9</span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">9</span>
</label>
{' '}
<label className="kodza-rating-scale__option" data-end="high">
<input className="kodza-rating-scale__input" type="radio" name="recommend" defaultValue="10" />
{' '}
<span className="kodza-rating-scale__value">10<span className="kodza-rating-scale__sr"> (ship it to everyone)</span></span>
{' '}
<span className="kodza-rating-scale__ink" aria-hidden="true">10</span>
</label>
</div>
<div className="kodza-rating-scale__ends" aria-hidden="true">
<span className="kodza-rating-scale__end kodza-rating-scale__end--low">Not likely</span>
{' '}
<span className="kodza-rating-scale__end kodza-rating-scale__end--high">Ship it to everyone</span>
</div>
<div className="kodza-rating-scale__follow">
<div className="kodza-rating-scale__follow-inner">
<label className="kodza-rating-scale__box">
<span className="kodza-rating-scale__ask">
<span className="kodza-rating-scale__question kodza-rating-scale__question--low">What broke? Be blunt.</span>
{' '}
<span className="kodza-rating-scale__question kodza-rating-scale__question--mid">What would make it a 10?</span>
{' '}
<span className="kodza-rating-scale__question kodza-rating-scale__question--high">Nice. What sold you?</span>
</span>
{' '}
<textarea className="kodza-rating-scale__text" name="recommend-why" rows={2} />
</label>
</div>
</div>
</fieldset>
);
}
Checked
Robots poked at it before you did. Every component goes through the same checks, and again each time it changes.