RadioGroup
One choice of several: real radio buttons in a fieldset, in a column, a row or as cards.
Demo
You picked pro.
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/radio_group.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/radio_group.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/radio_group.qwe
component RadioGroupDemoLive() { state plan = "pro" <div.demo-stack> <RadioGroup name="plan" label="Plan" cards={true} direction="row" value={plan} options={[{ label: "Hobby", value: "hobby", hint: "Free, one project" }, { label: "Pro", value: "pro", hint: "$12 a month" }, { label: "Team", value: "team", hint: "$8 a seat" }]} on_change={(v) => { plan = v }}/> <p.demo-note>You picked {plan}.</p> </div>}view RadioGroupDemo() = <div.demo-stack> <div.demo-row> <RadioGroup name="size" label="Size" value="m" direction="row" hint="Sizes run small." options={[{ label: "Small", value: "s" }, { label: "Medium", value: "m" }, { label: "Large", value: "l" }, { label: "XL (sold out)", value: "xl", disabled: true }]}/> </div> <RadioGroup name="ship" label="Delivery" required={true} error="Choose how it gets to you." options={["Standard, 3 to 5 days", "Express, next day", "Pick up in store"]}/> <RadioGroupDemoLive/> </div>Source
ui/radio_group.qwe
// RadioGroup: one choice of several, as radio buttons in a <fieldset> under a// legend. They are real radios, so a plain <form> sends the chosen `value` as// `name`, and the arrow keys move between them.//// <RadioGroup name="size" label="Size" value="m" options={["s", "m", "l"]}/>// <RadioGroup name="plan" label="Plan" cards={true} direction="row"// options={[{ label: "Hobby", value: "hobby", hint: "Free, one project" },// { label: "Pro", value: "pro", hint: "$12 a month" }]}/>//// options: a list of strings, or of { label, value, hint?, disabled? }. value: the// chosen one. direction: "column" (default) or "row". cards: each option is a// bordered card, for choices with a line of detail. hint, error, required,// disabled and field (the ids' start, default "radio-group-" + name) as in TextField.// on_change(value): inside one of your components, given the chosen value.import ui/theme.qwe use ui_clslet radio_group_css = css`.ui-radio-group { min-width: 0; margin: 0; padding: 0; border: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-radio-group-legend { padding: 0; margin-bottom: 8px; font-size: 13px; font-weight: 500; }.ui-radio-group-req { margin-left: 2px; color: var(--ui-danger); }.ui-radio-group-list { display: flex; flex-direction: column; gap: 10px; }.ui-radio-group-row .ui-radio-group-list { flex-direction: row; flex-wrap: wrap; gap: 10px 20px; }.ui-radio-group-opt { position: relative; display: flex; align-items: flex-start; gap: 10px; cursor: pointer; width: fit-content; }.ui-radio-group-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }.ui-radio-group-dot { flex: none; position: relative; width: 18px; height: 18px; margin-top: 1px; border-radius: 50%; background: var(--ui-surface); border: 1px solid var(--ui-strong); transition: border-color .15s, box-shadow .15s;}.ui-radio-group-dot::after { content: ""; position: absolute; inset: 4px; border-radius: 50%; background: var(--ui-accent); transform: scale(0); transition: transform .12s;}.ui-radio-group-opt:hover .ui-radio-group-dot { border-color: var(--ui-accent); }.ui-radio-group-opt input:checked + .ui-radio-group-dot { border-color: var(--ui-accent); border-width: 1.5px; }.ui-radio-group-opt input:checked + .ui-radio-group-dot::after { transform: scale(1); }.ui-radio-group-opt input:focus-visible + .ui-radio-group-dot { box-shadow: var(--ui-focus); }.ui-radio-group-text { display: grid; gap: 2px; }.ui-radio-group-opt-hint { color: var(--ui-muted); font-size: 13px; }.ui-radio-group-opt:has(input:disabled) { opacity: .55; cursor: not-allowed; }.ui-radio-group-opt:has(input:disabled):hover .ui-radio-group-dot { border-color: var(--ui-strong); }.ui-radio-group-cards .ui-radio-group-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; }.ui-radio-group-cards.ui-radio-group-column .ui-radio-group-list { grid-template-columns: 1fr; }.ui-radio-group-cards .ui-radio-group-opt { width: auto; padding: 12px 14px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); transition: border-color .15s, background .15s, box-shadow .15s;}.ui-radio-group-cards .ui-radio-group-opt:hover { border-color: var(--ui-strong); }.ui-radio-group-cards .ui-radio-group-opt:has(input:checked) { border-color: var(--ui-accent); background: color-mix(in srgb, var(--ui-accent-soft) 60%, var(--ui-surface)); box-shadow: inset 0 0 0 1px var(--ui-accent);}.ui-radio-group-cards .ui-radio-group-opt:has(input:focus-visible) { box-shadow: var(--ui-focus); }.ui-radio-group-cards .ui-radio-group-opt input:focus-visible + .ui-radio-group-dot { box-shadow: none; }.ui-radio-group-cards .ui-radio-group-text > :first-child { font-weight: 500; }.ui-radio-group-invalid .ui-radio-group-dot,.ui-radio-group-invalid.ui-radio-group-cards .ui-radio-group-opt { border-color: var(--ui-danger); }.ui-radio-group-hint, .ui-radio-group-error { margin: 8px 0 0; font-size: 13px; color: var(--ui-muted); }.ui-radio-group-error { color: var(--ui-danger); }@media (prefers-reduced-motion: reduce) { .ui-radio-group-dot, .ui-radio-group-dot::after, .ui-radio-group-cards .ui-radio-group-opt { transition: none; }}`view RadioGroup(name: str, label: str? = null, value: str | num | null = null, hint: str? = null, error: str? = null, required: bool = false, disabled: bool = false, field: str? = null, options: list = [], direction: str = "column", cards: bool = false, on_change = null, class: str? = null) { style global radio_group_css let id = field ?? `radio-group-${name}` let described = ui_cls([hint != null && `${id}-hint`, error != null && `${id}-error`]) let chosen = value != null ? str(value) : null <fieldset class={ui_cls(["ui-radio-group", `ui-radio-group-${direction}`, cards && "ui-radio-group-cards", error != null && "ui-radio-group-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-radio-group-legend> {label}{required && <span.ui-radio-group-req aria-hidden="true">*</span>} </legend>} <div.ui-radio-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) let oid = `${id}-${i}` <label.ui-radio-group-opt for={oid}> <input type="radio" id={oid} name={name} value={v} checked={v == chosen} required={required} disabled={it.disabled ?? false} @change={(e) => { on_change && on_change(e.value) }}/> <span.ui-radio-group-dot aria-hidden="true"></span> <span.ui-radio-group-text> <span>{it.label ?? v}</span> {it.hint != null && <span.ui-radio-group-opt-hint>{it.hint}</span>} </span> </label> }} </div> {hint != null && <p.ui-radio-group-hint id={`${id}-hint`}>{hint}</p>} {error != null && <p.ui-radio-group-error id={`${id}-error`} role="alert">{error}</p>} </fieldset>}Related
CheckboxCardsAny 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 downloadsComboboxA text box that filters a list of options as you type, or asks for them (a server, a map service), with the arrow keys, Enter and Escape.5 views · 7 downloadsDependentSelectTwo drop-downs where the second's options depend on the first, both real selects a form sends.5 views · 8 downloadsPhoneInputA country code select and a number box, sent together in international form (+351912345678) with the country's ISO code.6 views · 6 downloadsRadioCardsOne choice of several as cards with a title, a line of detail and a dot, each a real radio a form sends.5 views · 7 downloadsAddressFormStreet, city, region, postcode and country in the usual layout, with the autocomplete names browsers fill from.5 views · 7 downloads
Thread
Thread 0 posts
On RadioGroup: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.