Next: (hygguile components fly-out), Previous: (hygguile components feedback), Up: API
Render a free-text field with a list of suggestions.
The suggestion list is a native <datalist>: the browser draws it
in its own way, filters it as the reader types, and leaves the field
free to accept a value that is not on the list. That is the correct
behaviour for a combobox, and none of it needs a script. A page that
needs a fixed set of choices wants select instead; a page that
needs several values wants multi-select.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
A list of strings or (value . label) pairs.
The initial value.
A hint shown while the field is empty.
Whether the field is required.
Optional helper copy.
Optional error message.
Whether several values may be entered (the datalist is unchanged; the
field is a normal text input and this only sets multiple).
Whether the control is disabled.
Classes for the field wrapper.
(combobox #:label "Colour" #:name "colour"
#:options '("amber" "asparagus" "jeans-blue"))
Render a date field backed by the browser’s own date control.
The value travels in ISO form (2026-10-11), which is the only
form a <input type="date"> accepts and the only form a server
should have to parse. The browser draws a calendar in the reader’s own
locale, so the page never has to ship a date-picker script or guess at a
format.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
The initial value, in ISO form.
The earliest accepted date, in ISO form.
The latest accepted date, in ISO form.
Whether the field is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
Classes for the field wrapper.
(date-field #:label "Starts" #:name "start" #:value "2026-10-11")
Render a from/to date pair as one field.
The pair is a <fieldset> with a <legend>, because that is
what a group of related controls is, and each half keeps its own label
and its own id. The names are derived from name, so a server
reads start=...&end=... without the page inventing a second
convention.
Both halves are native date inputs, which means the range is usable with scripting off – a page that wants the second date to follow the first can add that behaviour on top, but it is not required for the field to work.
The group’s legend, e.g. "Stay".
The shared name; the halves become <name>-start and
<name>-end.
The initial start date, in ISO form.
The initial end date, in ISO form.
The first half’s label. Defaults to "From".
The second half’s label. Defaults to "To".
The earliest accepted date for both halves.
The latest accepted date for both halves.
Whether both halves are required.
Optional helper copy for the group.
Optional error message for the group.
Whether both halves are disabled.
Classes for the fieldset.
(date-range-field #:label "Stay" #:name "stay"
#:start "2026-10-11" #:end "2026-10-14")
Render a file field as a drop zone.
This is file-field with #:dropzone? #t and multi-file
selection on by default. It is still a native file input under a label,
so the picker, the accept filter and the upload work with scripting off;
the drop-zone treatment and the list of chosen names are the
enhancement.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
A native accept list.
Whether several files may be chosen. Defaults to true.
Whether a file is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
The text inside the drop zone.
Classes for the field wrapper.
(dropzone #:label "Attachments" #:name "files" #:accept "application/pdf")
Return the attribute pairs a control should carry for wiring.
Splice them into the control’s attribute list:
`((input (@ ,@(field-control-attributes wiring)
(type "email"))))
The result carries the shared id, the name, the
description and error links, required and aria-invalid
when they apply, and nothing else – a control’s own type, value and
classes are its own.
Return the aria-describedby value for the control, or #f.
Return the identifier of the field’s error message.
Return the identifier of the field’s helper copy.
Return the identifier the control and its label share.
Return the identifiers and attributes one control shares with its field shell.
The wiring is an association list rather than an opaque object, so it
can be inspected, logged and passed between procedures. Read it through
the accessors below, or hand it to field-control-attributes to
get the attribute pairs a control should carry.
When name and id are both absent, a unique id is generated, so a field can be rendered without a form name (a filters bar, say) and still have a label bound to it.
The form field name.
An explicit identifier; defaults to name.
Optional helper copy, which gets an id and joins the description.
Optional error message, which gets an id and joins the description.
Whether the control is required.
(field-wiring #:name "email" #:help-text "We never share it.")
Render a file picker, optionally as a drop zone.
The control is a native <input type="file">, so the browser’s own
picker, its filter for accept, its multi-file handling and its
multipart/form-data upload all work without a script. The
visible box is a <label> bound to the input, which means clicking
it opens the picker and a keyboard reader reaches the input itself.
With dropzone? the box is bigger, says it accepts dropped files, and carries the attributes a small script uses to list the chosen names. Dropping is an enhancement: with scripting off the label still opens the picker, and the filename is still shown by the browser.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
A native accept list, e.g. "image/*,.pdf".
Whether several files may be chosen.
An optional capture hint for a camera on a phone.
Whether a file is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
Render the larger drop-zone treatment. Defaults to false.
The text inside the drop zone.
Classes for the field wrapper.
(file-field #:label "Avatar" #:name "avatar" #:accept "image/*") (dropzone #:label "Attachments" #:name "files" #:multiple #t)
Render one labelled, described, validated field shell around a control.
The shell owns the four things every field needs and every hand-written
field eventually gets wrong: the label is bound to the control’s id, the
helper copy and the error are linked through aria-describedby,
the error is announced with role="alert", and the required state
is both visible (a marker) and spoken (a visually hidden word plus
aria-required on the control).
control is a procedure of one argument, the wiring, and returns the control’s SXML – a node or a list of nodes. A control that renders more than one element (a select and its tags, an input and a dropzone) returns them all and the shell places them in order.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
Optional helper copy, rendered below the control.
Optional error message, rendered above the help text and announced.
Whether the field is required. Renders a visible marker and a spoken one.
Render the label visually hidden but still available as the control’s name.
A procedure of one argument (the wiring) returning the control’s nodes.
Classes for the field’s wrapper.
Classes for the label.
Classes for the helper copy.
Classes for the error message.
(form-field #:label "Email"
#:name "email"
#:required #t
#:help-text "We never share it."
#:control
(lambda (w)
`((input (@ ,@(field-control-attributes w)
(type "email")
(class ,(mk-class '(p-3 rounded-xl))))))))
Render a multi-value choice control with the chosen values shown as tags.
The control is a native <select multiple>, which is the only
multi-select that works without a script and is announced correctly by
assistive technology. The tags above it are a *second view* of the same
selection, not a replacement: they show what is chosen without opening
the menu, and, when remove-href is given, each tag is a link that
removes that value – a plain navigation, so removal works with
scripting off too.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
A list of strings or (value . label) pairs.
The values that start selected.
How many rows the menu shows. Defaults to the number of options, capped at 8.
Whether at least one value is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
A procedure of one argument (a selected value) returning the URL that
removes it, or #f for tags that are not removable.
The accessible label template for a removal link. Defaults to
"Remove".
Classes for the field wrapper.
(multi-select #:label "Topics" #:name "topics"
#:options '((a . "Accessibility") (s . "SSR"))
#:selected '("ssr"))
Render a numeric field with an optional unit and optional stepper buttons.
The control is a native <input type="number">: the browser’s own
spinner, number keyboard on touch, and native validation all come for
free. With stepper? the field also gets a pair of buttons that
step the value by one step; those are a progressive enhancement
(they carry js-only), so with scripting off the native input is
still the whole control rather than a broken one.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
The initial value.
The lowest accepted value.
The highest accepted value.
The increment between accepted values.
An optional unit shown beside the field, e.g. "kg".
A hint shown while the field is empty.
Whether the field is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
Add the decrement/increment buttons. Defaults to false.
Classes for the field wrapper.
Classes for the input itself.
(number-field #:label "Seats" #:name "seats" #:min 1 #:max 12 #:stepper? #t)
Render a search control as a real search landmark.
The form carries role="search", so assistive technology can jump
straight to it, and the field is a native <input type="search">:
pressing Enter submits, the browser offers its own clear control, and
nothing depends on a script. The submit button is a real submit button,
so the form still works when it is the only way to send the query.
The field’s name. Defaults to "Search".
The query parameter name.
An explicit identifier; defaults to name.
The current query, so a results page shows what was searched for.
A hint shown while the field is empty.
The URL the search posts to.
'get (the default) or 'post.
The submit button’s label, or #f for a field that submits on
Enter only.
Optional helper copy.
Optional error message.
Whether a query is required.
Hide the label visually while keeping it as the field’s name. Defaults to true, which is what a compact search bar wants.
Classes for the search form.
(search-field #:name "q" #:action "/search" #:value "cozy")
Render a numeric field with the decrement/increment buttons already on.
This is number-field with #:stepper? #t, named for the
thing a page asks for. The buttons are an enhancement over the native
input, so the control keeps working with scripting off.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
The initial value.
The lowest accepted value.
The highest accepted value.
The increment. Defaults to 1.
An optional unit shown beside the field.
Optional helper copy.
Optional error message.
Whether the field is required.
Whether the control is disabled.
Classes for the field wrapper.
(stepper #:label "Quantity" #:name "qty" #:value 1 #:min 1 #:max 9)
Render a single-line text field through the field shell.
‘input’ is still the right component when a page does not need the shell’s wiring (an inline search box, a bare numeric filter); this one is for a form, where the label, the hint, the error and the required state have to agree with every other field beside it.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
The initial value.
A hint shown while the field is empty.
The native input type. Defaults to 'text.
Whether the field is required.
An optional autocomplete hint.
An optional virtual-keyboard hint.
An optional validation pattern.
Optional helper copy.
Optional error message.
Whether the control is disabled.
Whether the control is read-only.
Classes for the field wrapper.
Classes for the input itself.
(text-field #:label "Email" #:name "email" #:type 'email #:required #t)
Render a time field backed by the browser’s own time control.
The value is the ISO time form (09:30, or 09:30:00 with
seconds), and the browser draws it in the reader’s locale. step
is in seconds, which is how a time input says whether it wants minutes
or seconds.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
The initial value, in ISO time form.
The earliest accepted time.
The latest accepted time.
The increment between accepted times, in seconds.
Whether the field is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
Classes for the field wrapper.
(time-field #:label "Starts at" #:name "starts-at" #:step 900)
Next: (hygguile components fly-out), Previous: (hygguile components feedback), Up: API