Forms

FormField

A label, a hint and an error around any control, with a required mark, the label beside or above it.

#forms#input#layout#no-js

Demo

Preview

Letters, digits and dashes.

Why?

Shown to the team only.

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

Example

The code of the preview above.

demos/form_field.qwe
view FormFieldDemo() =    <div.demo-stack.demo-narrow>        <FormField label="Username" field="form-field-demo-user" hint="Letters, digits and dashes." required={true}>            <input id="form-field-demo-user" name="username" type="text" aria-describedby="form-field-demo-user-hint" required={true}/>        </FormField>        <FormField label="Country" field="form-field-demo-country" error="Choose a country." aside={<a href="#form_field">Why?</a>}>            <select id="form-field-demo-country" name="country" aria-describedby="form-field-demo-country-error" aria-invalid="true">                <option value="">Choose one</option>                <option value="PT">Portugal</option>                <option value="JP">Japan</option>            </select>        </FormField>        <FormField label="Nickname" field="form-field-demo-nick" optional={true} inline={true} width="20ch">            <input id="form-field-demo-nick" name="nickname" type="text"/>        </FormField>        <FormField label="Notes" field="form-field-demo-notes" inline={true} hint="Shown to the team only.">            <textarea id="form-field-demo-notes" name="notes" aria-describedby="form-field-demo-notes-hint"></textarea>        </FormField>    </div>

Source

ui/form_field.qwe
// FormField: a label, a hint and an error around any control, with a mark when// it is required, for a field the kit does not have (a plain <input>, a <select>// with your own options, a library's widget). The control is its children; give it// the id `field`, so the label points to it, and aria-describedby of "<field>-hint"// and "<field>-error" where you give those, so screen readers read them.////   <FormField label="Username" field="username" hint="Letters, digits and dashes." required={true}>//       <input id="username" name="username" aria-describedby="username-hint" required/>//   </FormField>//   <FormField label="Country" field="country" error="Choose a country." inline={true}>//       <select id="country" name="country" aria-describedby="country-error" aria-invalid="true">...</select>//   </FormField>//// label: the text of the <label>. field: the control's id, which the label is for.// hint: a line of help under the control; error: what is wrong, in red, under it// (role alert). required: a red mark after the label; optional: "(optional)" after// it instead, for forms where most fields are required. aside: HTML at the label's// right ("Forgot?" link, a counter). inline: the label beside the control, in a// column of its own, instead of above it. full: in a Form with layout "grid", takes// the whole row. width: a CSS width for the control's column ("12ch", "50%").import ui/theme.qwe use ui_clslet form_field_css = css`.ui-form-field { display: grid; gap: 6px; min-width: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-form-field-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }.ui-form-field-label { font-size: 13px; font-weight: 500; color: var(--ui-fg); }.ui-form-field-req { margin-left: 2px; color: var(--ui-danger); }.ui-form-field-opt { margin-left: 4px; color: var(--ui-muted); font-weight: 400; }.ui-form-field-aside { font-size: 13px; color: var(--ui-muted); }.ui-form-field-control { min-width: 0; }.ui-form-field-control > input:not([type=checkbox]):not([type=radio]):not([type=file]), .ui-form-field-control > select, .ui-form-field-control > textarea {  box-sizing: border-box; width: 100%; max-width: var(--ui-form-field-width, none); height: 38px; margin: 0; padding: 0 12px;  border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg);  font: 14px var(--ui-font); transition: border-color .15s, box-shadow .15s;}.ui-form-field-control > textarea { height: auto; min-height: 76px; padding: 8px 12px; line-height: 1.5; resize: vertical; }.ui-form-field-control > input:hover, .ui-form-field-control > select:hover, .ui-form-field-control > textarea:hover { border-color: var(--ui-strong); }.ui-form-field-control > input:focus-visible, .ui-form-field-control > select:focus-visible, .ui-form-field-control > textarea:focus-visible {  outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus);}.ui-form-field-invalid .ui-form-field-control > input, .ui-form-field-invalid .ui-form-field-control > select,.ui-form-field-invalid .ui-form-field-control > textarea { border-color: var(--ui-danger); }.ui-form-field-hint, .ui-form-field-error { margin: 0; font-size: 13px; color: var(--ui-muted); }.ui-form-field-error { color: var(--ui-danger); }.ui-form-field-inline { grid-template-columns: minmax(120px, 1fr) 3fr; align-items: start; column-gap: 16px; }.ui-form-field-inline .ui-form-field-head { padding-top: 9px; }.ui-form-field-inline .ui-form-field-hint, .ui-form-field-inline .ui-form-field-error { grid-column: 2; }@media (max-width: 559px) {  .ui-form-field-inline { grid-template-columns: minmax(0, 1fr); }  .ui-form-field-inline .ui-form-field-head { padding-top: 0; }  .ui-form-field-inline .ui-form-field-hint, .ui-form-field-inline .ui-form-field-error { grid-column: 1; }}@media (prefers-reduced-motion: reduce) { .ui-form-field-control > input, .ui-form-field-control > select, .ui-form-field-control > textarea { transition: none; } }`view FormField(label: str, field: str, hint: str? = null, error: str? = null, required: bool = false,               optional: bool = false, aside = null, inline: bool = false, full: bool = false, width: str? = null,               class: str? = null, children) {    style global form_field_css    <div class={ui_cls(["ui-form-field", inline && "ui-form-field-inline", full && "ui-form-field-full",                        error != null && "ui-form-field-invalid", class])}>        <div.ui-form-field-head>            <label.ui-form-field-label for={field}>                {label}                {required && <span.ui-form-field-req aria-hidden="true">*</span>}                {optional && !required && <span.ui-form-field-opt>(optional)</span>}            </label>            {aside != null && <span.ui-form-field-aside>{aside}</span>}        </div>        <div.ui-form-field-control style={width != null ? `--ui-form-field-width: ${width}` : null}>{children}</div>        {hint != null && <p.ui-form-field-hint id={`${field}-hint`}>{hint}</p>}        {error != null && <p.ui-form-field-error id={`${field}-error`} role="alert">{error}</p>}    </div>}
Thread
55Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.