Forms

RadioGroup

One choice of several: real radio buttons in a fieldset, in a column, a row or as cards.

#forms#input#select#no-js

Demo

Preview
Size

Sizes run small.

Delivery
Plan

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.qwe
main.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>}
Thread
56Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.