Forms

Checkbox

A checkbox drawn by CSS on a real input, with an indeterminate state, and a group of them in a fieldset.

#forms#input#no-js

Demo

Preview

For 30 days on this device.

Days

When we may call you.

Email me about

A note when a version ships.

Subscribed to 1 of 3.

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/checkbox.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/checkbox.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/checkbox.qwe
let checkbox_demo_topics = [{ label: "Releases", value: "releases", hint: "A note when a version ships." },                            { label: "Events", value: "events" }, { label: "Jobs", value: "jobs" }]component CheckboxDemoLive() {    state picked = ["releases"]    let all = len(picked) == len(checkbox_demo_topics)    <div.demo-stack>        <Checkbox label="All topics" checked={all} indeterminate={!all && len(picked) > 0}                  on_change={(on) => { picked = on ? checkbox_demo_topics | map((t) => t.value) : [] }}/>        <CheckboxGroup name="topics" label="Email me about" options={checkbox_demo_topics} values={picked}                       on_change={(v, on) => { picked = on ? picked | push(v) : picked | filter((p) => p != v) }}/>        <p.demo-note>Subscribed to {len(picked)} of {len(checkbox_demo_topics)}.</p>    </div>}view CheckboxDemo() =    <div.demo-stack>        <Checkbox name="remember" label="Remember me" checked={true} hint="For 30 days on this device."/>        <div.demo-row>            <Checkbox name="mixed" label="Some files" indeterminate={true}/>            <Checkbox name="off" label="Disabled" disabled={true}/>        </div>        <Checkbox name="terms" label="I accept the terms of service" required={true}                  error="You need to accept them to go on."/>        <CheckboxGroup name="days" label="Days" direction="row" values={["Mon", "Wed"]}                       options={["Mon", "Tue", "Wed", "Thu", "Fri"]} hint="When we may call you."/>        <CheckboxDemoLive/>    </div>

Source

ui/checkbox.qwe
// Checkbox: a labelled checkbox, and CheckboxGroup: several under one heading.// Each is a real checkbox drawn by CSS, so it works in a plain <form> (`name` is// sent as "on", or `value`, when it is checked) and with the keyboard.////   <Checkbox name="terms" label="I accept the terms" required={true}/>//   <Checkbox label="Select all" indeterminate={true}/>//   <CheckboxGroup name="topics" label="Email me about" values={["releases"]}//                  options={[{ label: "Releases", value: "releases" }, { label: "Events", value: "events" }]}/>//// Checkbox: checked, value, hint (a line under it), error (a message under it; it// also marks it aria-invalid), required, disabled, field (the input's id, default// "checkbox-" + name). indeterminate: shown as neither on nor off (a dash) while it// is not checked, as for a "select all" with some chosen; screen readers hear// "partly selected" after the label. on_change(checked): inside one of your components,// given true or false.//// CheckboxGroup: a <fieldset> whose `label` is its legend. options: a list of// strings or of { label, value, hint?, disabled? }; values: the checked ones; each// is sent as `name`. direction: "column" (default) or "row". hint and error are// for the group. on_change(value, checked): given the option's value and whether// it is now checked, inside one of your components.import ui/theme.qwe use ui_clslet checkbox_css = css`.ui-checkbox { display: grid; gap: 2px; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-checkbox-label { display: inline-flex; align-items: flex-start; gap: 10px; cursor: pointer; width: fit-content; }.ui-checkbox-label input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }.ui-checkbox-box {  flex: none; position: relative; width: 18px; height: 18px; margin-top: 1px;  background: var(--ui-surface); border: 1px solid var(--ui-strong); border-radius: 4px;  transition: background .15s, border-color .15s, box-shadow .15s;}.ui-checkbox-box::after {  content: ""; position: absolute; left: 5px; top: 1.5px; width: 5px; height: 10px;  border: solid var(--ui-accent-fg); border-width: 0 2px 2px 0; transform: rotate(45deg) scale(0);  transition: transform .12s;}.ui-checkbox-label:hover .ui-checkbox-box { border-color: var(--ui-accent); }.ui-checkbox-label input:checked + .ui-checkbox-box,.ui-checkbox-label input[data-indeterminate]:not(:checked) + .ui-checkbox-box { background: var(--ui-accent); border-color: var(--ui-accent); }.ui-checkbox-label input:checked + .ui-checkbox-box::after { transform: rotate(45deg) scale(1); }.ui-checkbox-label input[data-indeterminate]:not(:checked) + .ui-checkbox-box::after {  left: 3px; top: 7px; width: 10px; height: 0; border-width: 0 0 2px; transform: none;}.ui-checkbox-label input:focus-visible + .ui-checkbox-box { box-shadow: var(--ui-focus); }.ui-checkbox-req { margin-left: 2px; color: var(--ui-danger); }.ui-checkbox-hint, .ui-checkbox-error { margin: 0 0 0 28px; font-size: 13px; color: var(--ui-muted); }.ui-checkbox-error { color: var(--ui-danger); }.ui-checkbox-invalid .ui-checkbox-box { border-color: var(--ui-danger); }.ui-checkbox-disabled { opacity: .55; }.ui-checkbox-disabled .ui-checkbox-label { cursor: not-allowed; }.ui-checkbox-disabled .ui-checkbox-label:hover .ui-checkbox-box { border-color: var(--ui-strong); }.ui-checkbox-group { min-width: 0; margin: 0; padding: 0; border: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-checkbox-group-legend { padding: 0; margin-bottom: 8px; font-size: 13px; font-weight: 500; }.ui-checkbox-group-list { display: flex; flex-direction: column; gap: 10px; }.ui-checkbox-group-row .ui-checkbox-group-list { flex-direction: row; flex-wrap: wrap; gap: 10px 20px; }.ui-checkbox-group-hint, .ui-checkbox-group-error { margin: 8px 0 0; font-size: 13px; color: var(--ui-muted); }.ui-checkbox-group-error { color: var(--ui-danger); }@media (prefers-reduced-motion: reduce) { .ui-checkbox-box, .ui-checkbox-box::after { transition: none; } }`view Checkbox(name: str? = null, label: str = "", value: str? = null, hint: str? = null, error: str? = null,              checked: bool = false, indeterminate: bool = false, required: bool = false, disabled: bool = false,              field: str? = null, on_change = null, class: str? = null) {    style global checkbox_css    let id = field ?? `checkbox-${name ?? re.replace(lower(label != "" ? label : "box"), "[^a-z0-9]+", "-")}`    let described = ui_cls([hint != null && `${id}-hint`, error != null && `${id}-error`])    <div class={ui_cls(["ui-checkbox", error != null && "ui-checkbox-invalid", disabled && "ui-checkbox-disabled", class])}>        <label.ui-checkbox-label for={id}>            <input type="checkbox" id={id} name={name} value={value} checked={checked} required={required}                   disabled={disabled} data-indeterminate={indeterminate}                   aria-invalid={error != null ? "true" : null}                   aria-describedby={described != "" ? described : null}                   @change={(e) => { on_change && on_change(e.checked) }}/>            <span.ui-checkbox-box aria-hidden="true"></span>            <span>{label}{required && <span.ui-checkbox-req aria-hidden="true">*</span>}{indeterminate && !checked && <span.ui-sr>, partly selected</span>}</span>        </label>        {hint != null && <p.ui-checkbox-hint id={`${id}-hint`}>{hint}</p>}        {error != null && <p.ui-checkbox-error id={`${id}-error`} role="alert">{error}</p>}    </div>}view CheckboxGroup(name: str? = null, label: str? = null, options: list = [], values: list = [],                   hint: str? = null, error: str? = null, disabled: bool = false, field: str? = null,                   direction: str = "column", on_change = null, class: str? = null) {    style global checkbox_css    let id = field ?? `checkbox-group-${name ?? re.replace(lower(label ?? "group"), "[^a-z0-9]+", "-")}`    let described = ui_cls([hint != null && `${id}-hint`, error != null && `${id}-error`])    let chosen = values | map((v) => str(v))    <fieldset class={ui_cls(["ui-checkbox-group", direction == "row" && "ui-checkbox-group-row", class])}              id={id} disabled={disabled} aria-describedby={described != "" ? described : null}>        {label != null && <legend.ui-checkbox-group-legend>{label}</legend>}        <div.ui-checkbox-group-list>            {for [i, o] in enumerate(options) => {                let it = type_of(o) == "map" ? o : { label: str(o), value: str(o) }                let v = str(it.value ?? it.label)                <Checkbox name={name} label={it.label ?? v} value={v} hint={it.hint} field={`${id}-${i}`}                          checked={contains(chosen, v)} disabled={it.disabled ?? false}                          class={error != null ? "ui-checkbox-invalid" : null}                          on_change={(c) => { on_change && on_change(v, c) }}/>            }}        </div>        {hint != null && <p.ui-checkbox-group-hint id={`${id}-hint`}>{hint}</p>}        {error != null && <p.ui-checkbox-group-error id={`${id}-error`} role="alert">{error}</p>}    </fieldset>}
Thread
56Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.