Checkbox
A checkbox drawn by CSS on a real input, with an indeterminate state, and a group of them in a fieldset.
Demo
For 30 days on this device.
You need to accept them to go on.
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.qwemain.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>}Related
AddressFormStreet, city, region, postcode and country in the usual layout, with the autocomplete names browsers fill from.5 views · 7 downloadsAutocompleteA free-text box that offers matches as one types, with the arrow keys and Enter; whatever is typed may stay.5 views · 7 downloadsCardInputA payment card as it is typed: the number grouped, the brand, MM/YY and the code, with typos said.6 views · 6 downloadsCharacterCounterA text box or textarea with a live 120 / 280 count that warns near the limit, as a hard stop or a soft one that blocks sending.5 views · 6 downloadsCheckboxCardsAny number of choices as cards with a title, a line of detail and a check, each a real checkbox a form sends.5 views · 6 downloadsChecklistInputCheckboxes under a progress line that says how many are done and fills as they are ticked; each posts with a form.5 views · 7 downloads
Thread
Thread 0 posts
On Checkbox: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.