Forms

LikertScale

A statement with a row of points from Strongly disagree to Strongly agree: real radios in a fieldset, posted with a form.

#forms#input#no-js

Demo

Preview
The docs were easy to follow

1 is strongly disagree, 5 is strongly agree.

I would recommend Quillo to a colleague

No answer yet.

How often do you deploy?

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/likert_scale.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/likert_scale.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/likert_scale.qwe
component LikertScaleDemoLive() {    state answer = null    <div.demo-stack>        <LikertScale name="recommend" label="I would recommend Quillo to a colleague" points={7} value={answer}                     field="likert-scale-demo-live" on_change={(v) => { answer = v }}/>        <p.demo-note>{answer == null ? "No answer yet." : `You chose ${answer} of 7.`}</p>    </div>}view LikertScaleDemo() =    <div.demo-stack>        <LikertScale name="docs" label="The docs were easy to follow" value={4} field="likert-scale-demo-five"                     hint="1 is strongly disagree, 5 is strongly agree." required={true}/>        <LikertScaleDemoLive/>        <LikertScale name="often" label="How often do you deploy?" field="likert-scale-demo-words"                     points={["Never", "Monthly", "Weekly", "Daily", "Hourly"]} low={null} high={null}                     error="Pick how often."/>    </div>

Source

ui/likert_scale.qwe
// LikertScale: one statement and a row of points to agree or disagree with it,// from "Strongly disagree" to "Strongly agree". They are real radio buttons in a// <fieldset>, so a plain <form> sends the point's value (1 to 5) as `name`, and// the arrow keys move between them.////   <LikertScale name="q1" label="The docs were easy to follow"/>//   <LikertScale name="q2" label="I would recommend it" points={7} value={5} required={true}/>//   <LikertScale name="q3" label="How often?" points={["Never", "Rarely", "Sometimes", "Often", "Always"]}//                low={null} high={null} on_change={(v) => { answer = v }}/>//// points: how many (5 by default, or 7; the values are 1 to that), or a list of// the points' own labels, written under each. low and high: the words at the two// ends ("Strongly disagree", "Strongly agree"; null leaves one out). value: the// point chosen to start with (1-based). Each point has a name for screen readers// ("3 of 5", or its own label). hint, error, required, disabled and field (the// ids' start, default "likert-scale-" + name) as in RadioGroup.// on_change(value): inside one of your components, given the point chosen (its// number, 1-based, as a string: what the form sends).import ui/theme.qwe use ui_clslet likert_scale_css = css`.ui-likert-scale { min-width: 0; margin: 0; padding: 0; border: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-likert-scale-legend { padding: 0; margin-bottom: 10px; font-weight: 500; }.ui-likert-scale-req { margin-left: 2px; color: var(--ui-danger); }.ui-likert-scale-row { display: flex; align-items: flex-start; gap: 12px; }.ui-likert-scale-end { flex: 1 1 0; font-size: 13px; color: var(--ui-muted); padding-top: 4px; }.ui-likert-scale-end:last-child { text-align: right; }.ui-likert-scale-points { display: flex; gap: 6px; }.ui-likert-scale-opt { position: relative; display: grid; justify-items: center; gap: 4px; min-width: 36px; cursor: pointer; }.ui-likert-scale-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }.ui-likert-scale-dot {  display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 50%;  border: 1.5px solid var(--ui-strong); background: var(--ui-surface); color: var(--ui-muted);  font-size: 12px; font-weight: 500; font-variant-numeric: tabular-nums; transition: border-color .15s, background .15s, box-shadow .15s;}.ui-likert-scale-opt:hover .ui-likert-scale-dot { border-color: var(--ui-accent); color: var(--ui-accent); }.ui-likert-scale-opt input:checked + .ui-likert-scale-dot { border-color: var(--ui-accent); background: var(--ui-accent); color: var(--ui-accent-fg); }.ui-likert-scale-opt input:focus-visible + .ui-likert-scale-dot { box-shadow: var(--ui-focus); }.ui-likert-scale-word { font-size: 12px; color: var(--ui-muted); text-align: center; max-width: 76px; }.ui-likert-scale-opt input:checked ~ .ui-likert-scale-word { color: var(--ui-fg); font-weight: 500; }.ui-likert-scale-opt:has(input:disabled) { opacity: .55; cursor: not-allowed; }.ui-likert-scale-invalid .ui-likert-scale-dot { border-color: var(--ui-danger); }.ui-likert-scale-hint, .ui-likert-scale-error { margin: 8px 0 0; font-size: 13px; color: var(--ui-muted); }.ui-likert-scale-error { color: var(--ui-danger); }@media (max-width: 560px) {  .ui-likert-scale-row { flex-wrap: wrap; }  .ui-likert-scale-points { order: -1; width: 100%; justify-content: space-between; }  .ui-likert-scale-end { flex: 1 1 40%; }}@media (prefers-reduced-motion: reduce) { .ui-likert-scale-dot { transition: none; } }`view LikertScale(name: str, label: str? = null, points: int | list = 5, low: str? = "Strongly disagree",                 high: str? = "Strongly agree", value: int | str | null = null, hint: str? = null, error: str? = null,                 required: bool = false, disabled: bool = false, field: str? = null, on_change = null,                 class: str? = null) {    style global likert_scale_css    let id = field ?? `likert-scale-${name}`    let words = type_of(points) == "list" ? points | map((p) => str(p)) : null    let n = words != null ? len(words) : points    let chosen = value != null ? str(value) : null    let described = ui_cls([hint != null && `${id}-hint`, error != null && `${id}-error`])    <fieldset class={ui_cls(["ui-likert-scale", error != null && "ui-likert-scale-invalid", class])} id={id} disabled={disabled}              role="radiogroup" aria-required={required ? "true" : null} aria-invalid={error != null ? "true" : null}              aria-describedby={described != "" ? described : null}>        {label != null && <legend.ui-likert-scale-legend>            {label}{required && <span.ui-likert-scale-req aria-hidden="true">*</span>}        </legend>}        <div.ui-likert-scale-row>            {low != null && <span.ui-likert-scale-end aria-hidden="true">{low}</span>}            <div.ui-likert-scale-points>                {for i in range(n) => {                    let v = str(i + 1)                    let word = words != null ? words[i] : null                    let said = word ?? (i == 0 && low != null ? `${v} of ${n}, ${low}`                                       : i == n - 1 && high != null ? `${v} of ${n}, ${high}` : `${v} of ${n}`)                    <label.ui-likert-scale-opt for={`${id}-${v}`}>                        <input type="radio" id={`${id}-${v}`} name={name} value={v} checked={v == chosen} required={required}                               aria-label={said} @change={(e) => { on_change && on_change(e.value) }}/>                        <span.ui-likert-scale-dot aria-hidden="true">{v}</span>                        {word != null && <span.ui-likert-scale-word aria-hidden="true">{word}</span>}                    </label>                }}            </div>            {high != null && <span.ui-likert-scale-end aria-hidden="true">{high}</span>}        </div>        {hint != null && <p.ui-likert-scale-hint id={`${id}-hint`}>{hint}</p>}        {error != null && <p.ui-likert-scale-error id={`${id}-error`} role="alert">{error}</p>}    </fieldset>}
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

On LikertScale: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.