Next: , Previous: , Up: API  


1.11 (hygguile components field)

1.11.1 Procedures

Procedure: combobox KEY: #:label #:name #:id #:options #:value #:placeholder #:required #:help-text #:error #:valid #:multiple #:disabled #:classes

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.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

options

A list of strings or (value . label) pairs.

value

The initial value.

placeholder

A hint shown while the field is empty.

required

Whether the field is required.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

multiple

Whether several values may be entered (the datalist is unchanged; the field is a normal text input and this only sets multiple).

disabled

Whether the control is disabled.

classes

Classes for the field wrapper.

(combobox #:label "Colour" #:name "colour"
          #:options '("amber" "asparagus" "jeans-blue"))
Procedure: date-field KEY: #:label #:name #:id #:value #:min #:max #:required #:help-text #:error #:valid #:disabled #:classes

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.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

value

The initial value, in ISO form.

min

The earliest accepted date, in ISO form.

max

The latest accepted date, in ISO form.

required

Whether the field is required.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

classes

Classes for the field wrapper.

(date-field #:label "Starts" #:name "start" #:value "2026-10-11")
Procedure: date-range-field KEY: #:label #:name #:start #:end #:start-label #:end-label #:min #:max #:required #:help-text #:error #:valid #:disabled #:classes

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.

label

The group’s legend, e.g. "Stay".

name

The shared name; the halves become <name>-start and <name>-end.

start

The initial start date, in ISO form.

end

The initial end date, in ISO form.

start-label

The first half’s label. Defaults to "From".

end-label

The second half’s label. Defaults to "To".

min

The earliest accepted date for both halves.

max

The latest accepted date for both halves.

required

Whether both halves are required.

help-text

Optional helper copy for the group.

error

Optional error message for the group.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether both halves are disabled.

classes

Classes for the fieldset.

(date-range-field #:label "Stay" #:name "stay"
                  #:start "2026-10-11" #:end "2026-10-14")
Procedure: dropzone KEY: #:label #:name #:id #:accept #:multiple #:required #:help-text #:error #:valid #:disabled #:drop-label #:classes

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.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

accept

A native accept list.

multiple

Whether several files may be chosen. Defaults to true.

required

Whether a file is required.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

drop-label

The text inside the drop zone.

classes

Classes for the field wrapper.

(dropzone #:label "Attachments" #:name "files" #:accept "application/pdf")
Procedure: field-control-attributes wiring

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.

An invalid control carries both aria-invalid and aria-errormessage, pointing at the message itself. The two say different things – that the control is wrong, and which words say why – and a reader that understands only the first still gets the message through the description link, because the error is part of it.

Procedure: field-described-by wiring

Return the aria-describedby value for the control, or #f.

Procedure: field-error-id wiring

Return the identifier of the field’s error message.

Procedure: field-help-id wiring

Return the identifier of the field’s helper copy.

Procedure: field-id wiring

Return the identifier the control and its label share.

Procedure: field-wiring KEY: #:name #:id #:help-text #:error #:required

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.

name

The form field name.

id

An explicit identifier; defaults to name.

help-text

Optional helper copy, which gets an id and joins the description.

error

Optional error message, which gets an id and joins the description.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

required

Whether the control is required.

(field-wiring #:name "email" #:help-text "We never share it.")
Procedure: file-field KEY: #:label #:name #:id #:accept #:multiple #:capture #:required #:help-text #:error #:valid #:disabled #:dropzone? #:drop-label #:classes

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.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

accept

A native accept list, e.g. "image/*,.pdf".

multiple

Whether several files may be chosen.

capture

An optional capture hint for a camera on a phone.

required

Whether a file is required.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

dropzone?

Render the larger drop-zone treatment. Defaults to false.

drop-label

The text inside the drop zone.

classes

Classes for the field wrapper.

(file-field #:label "Avatar" #:name "avatar" #:accept "image/*")
(dropzone #:label "Attachments" #:name "files" #:multiple #t)
Procedure: form-field KEY: #:label #:name #:id #:help-text #:error #:valid #:required #:label-hidden? #:control #:classes #:label-css #:help-classes #:error-css #:valid-css

Render one labelled, described, validated field shell around a control.

The shell owns the 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 named by aria-errormessage, and the required state is both visible (a marker) and spoken (a visually hidden word plus aria-required on the control).

The shell also states which of the three states the field is in, as a class on the wrapper – hg-field-invalid, hg-field-valid, or neither. The classes are what the stylesheet colours the control’s border with, so a page never has to pick a border colour per state, and a field that is invalid looks invalid wherever it sits on the page.

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.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

help-text

Optional helper copy, rendered below the control.

error

Optional error message, rendered above the help text and announced. It wins over valid when both are given.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well. Rendered below the control with role="status".

required

Whether the field is required. Renders a visible marker and a spoken one.

label-hidden?

Render the label visually hidden but still available as the control’s name.

control

A procedure of one argument (the wiring) returning the control’s nodes.

classes

Classes for the field’s wrapper.

label-css

Classes for the label.

help-classes

Classes for the helper copy.

error-css

Classes for the error message.

valid-css

Classes for the success 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))))))))

(form-field #:label "Email"
            #:name "email"
            #:error "Enter an email address."
            #:control ...)
Procedure: input-group KEY: #:label #:name #:id #:value #:placeholder #:type #:prefix #:suffix #:required #:autocomplete #:input-mode #:help-text #:error #:valid #:disabled #:readonly #:classes #:input-css #:group-css

Render a text field with something written inside its edges.

An input group is one bordered box that holds the field and the marks that belong to it: the currency before an amount, the domain after a handle, the unit after a quantity. The marks are not part of the value, so a reader never has to type or delete them, and a screen reader reaches the field without walking over them – both sides are decorative text beside the control, not inside it.

prefix and suffix each take a string, which is rendered as the group’s own addon box, or SXML (a node or a list of nodes), which is rendered as written. That is what keeps the door open for a ‘select’ of units on the right of a number, or a ‘button’ on the right of a search.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

value

The initial value.

placeholder

A hint shown while the field is empty.

type

The native input type. Defaults to 'text.

prefix

What is written before the field: a string, or SXML.

suffix

What is written after the field: a string, or SXML.

required

Whether the field is required.

autocomplete

An optional autocomplete hint.

input-mode

An optional virtual-keyboard hint.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

readonly

Whether the control is read-only.

classes

Classes for the field wrapper.

input-css

Classes for the input itself, which carries no border of its own.

group-css

Classes for the bordered box around the field and its addons.

(input-group #:label "Price" #:name "price" #:type 'number
             #:prefix "€" #:suffix "/ month")
Procedure: multi-select KEY: #:label #:name #:id #:options #:selected #:size #:required #:help-text #:error #:valid #:disabled #:remove-href #:remove-label #:classes

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.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

options

A list of strings or (value . label) pairs.

selected

The values that start selected.

size

How many rows the menu shows. Defaults to the number of options, capped at 8.

required

Whether at least one value is required.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

remove-href

A procedure of one argument (a selected value) returning the URL that removes it, or #f for tags that are not removable.

remove-label

The accessible label template for a removal link. Defaults to "Remove".

classes

Classes for the field wrapper.

(multi-select #:label "Topics" #:name "topics"
              #:options '((a . "Accessibility") (s . "SSR"))
              #:selected '("ssr"))
Procedure: number-field KEY: #:label #:name #:id #:value #:min #:max #:step #:unit #:placeholder #:required #:help-text #:error #:valid #:disabled #:stepper? #:classes #:input-css

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.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

value

The initial value.

min

The lowest accepted value.

max

The highest accepted value.

step

The increment between accepted values.

unit

An optional unit shown beside the field, e.g. "kg".

placeholder

A hint shown while the field is empty.

required

Whether the field is required.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

stepper?

Add the decrement/increment buttons. Defaults to false.

classes

Classes for the field wrapper.

input-css

Classes for the input itself.

(number-field #:label "Seats" #:name "seats" #:min 1 #:max 12 #:stepper? #t)
Procedure: otp-field KEY: #:label #:name #:id #:value #:length #:required #:autocomplete #:help-text #:error #:valid #:disabled #:classes #:input-css

Render a one-time code field.

The code is one input, not one input per digit. A single field is the shape the platform expects: autocomplete="one-time-code" lets a phone offer the code it just received, a paste fills the whole code at once, and a screen reader reads one value rather than six boxes it has to count through. The digits are spaced apart by the stylesheet, which is the part of a six-box field that actually helps.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

value

The initial value.

length

How many digits the code has. Defaults to 6.

required

Whether the code is required.

autocomplete

The autocomplete hint. Defaults to "one-time-code".

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

classes

Classes for the field wrapper.

input-css

Classes for the input itself.

(otp-field #:label "Verification code" #:name "code" #:length 6)
Procedure: password-field KEY: #:label #:name #:id #:value #:placeholder #:required #:autocomplete #:reveal? #:help-text #:error #:valid #:disabled #:readonly #:classes #:input-css

Render a password field with an optional reveal control.

The field is a native <input type="password"> and stays one: the value is masked by the browser, the right keyboard and autofill behaviour come with the type, and the form works with scripting off. The reveal button is a progressive enhancement that flips the field to plain text while the reader asks it to – it carries js-only, so a page rendered without scripts shows the field and its label and nothing that cannot work.

Give the field an autocomplete that matches what it is ("current-password" to sign in, "new-password" to set a password), which is what lets a password manager fill it, and what tells a browser offering to generate a password which field it is generating one for.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

value

The initial value. A page should normally leave this empty.

placeholder

A hint shown while the field is empty.

required

Whether the field is required.

autocomplete

The autocomplete hint. Defaults to "current-password".

reveal?

Render the show/hide control. Defaults to true.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

readonly

Whether the control is read-only.

classes

Classes for the field wrapper.

input-css

Classes for the input itself.

(password-field #:label "Password" #:name "password" #:required #t
                #:autocomplete "current-password")
Procedure: search-field KEY: #:label #:name #:id #:value #:placeholder #:action #:method #:submit-label #:help-text #:error #:valid #:required #:label-hidden? #:classes

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.

label

The field’s name. Defaults to "Search".

name

The query parameter name.

id

An explicit identifier; defaults to name.

value

The current query, so a results page shows what was searched for.

placeholder

A hint shown while the field is empty.

action

The URL the search posts to.

method

'get (the default) or 'post.

submit-label

The submit button’s label, or #f for a field that submits on Enter only.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

required

Whether a query is required.

label-hidden?

Hide the label visually while keeping it as the field’s name. Defaults to true, which is what a compact search bar wants.

classes

Classes for the search form.

(search-field #:name "q" #:action "/search" #:value "cozy")
Procedure: slider KEY: #:label #:name #:id #:value #:min #:max #:step #:unit #:required #:help-text #:error #:valid #:disabled #:classes #:input-css

Render a range slider that prints the value it is at.

The control is a native <input type="range">: it drags, it takes arrow keys, and it submits a number with no script. The number beside it is an <output> bound to the field, which the browser reads out as the field’s result; a small script keeps it in step while the reader drags, and without one the number is simply the value the page rendered.

Always give a slider a start value and, where it matters, a scale worth reading: a reader cannot see the positions between the ends, so the number is the only place the current value is stated in words.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

value

The value it starts at. Defaults to 50.

min

The lowest value. Defaults to 0.

max

The highest value. Defaults to 100.

step

The increment. Defaults to 1.

unit

An optional unit printed beside the number, e.g. "°C".

required

Whether a value is required.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

classes

Classes for the field wrapper.

input-css

Classes for the slider itself.

(slider #:label "Warmth" #:name "warmth" #:value 21 #:min 16 #:max 26
        #:unit "°C")
Procedure: stepper KEY: #:label #:name #:id #:value #:min #:max #:step #:unit #:help-text #:error #:valid #:required #:disabled #:classes

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.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

value

The initial value.

min

The lowest accepted value.

max

The highest accepted value.

step

The increment. Defaults to 1.

unit

An optional unit shown beside the field.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

required

Whether the field is required.

disabled

Whether the control is disabled.

classes

Classes for the field wrapper.

(stepper #:label "Quantity" #:name "qty" #:value 1 #:min 1 #:max 9)
Procedure: text-field KEY: #:label #:name #:id #:value #:placeholder #:type #:required #:autocomplete #:input-mode #:pattern #:help-text #:error #:valid #:disabled #:readonly #:classes #:input-css

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.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

value

The initial value.

placeholder

A hint shown while the field is empty.

type

The native input type. Defaults to 'text.

required

Whether the field is required.

autocomplete

An optional autocomplete hint.

input-mode

An optional virtual-keyboard hint.

pattern

An optional validation pattern.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

readonly

Whether the control is read-only.

classes

Classes for the field wrapper.

input-css

Classes for the input itself.

(text-field #:label "Email" #:name "email" #:type 'email #:required #t)
Procedure: time-field KEY: #:label #:name #:id #:value #:min #:max #:step #:required #:help-text #:error #:valid #:disabled #:classes

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.

label

The field’s visible name.

name

The form field name.

id

An explicit identifier; defaults to name.

value

The initial value, in ISO time form.

min

The earliest accepted time.

max

The latest accepted time.

step

The increment between accepted times, in seconds.

required

Whether the field is required.

help-text

Optional helper copy.

error

Optional error message.

valid

#t to mark the field as filled in correctly, or a string to say so in words as well.

disabled

Whether the control is disabled.

classes

Classes for the field wrapper.

(time-field #:label "Starts at" #:name "starts-at" #:step 900)
Procedure: validation-errors checks

Return the errors among checks, in the order they were given.

checks is a list of (field-id . message) pairs in which the message is #f for a field that passed – the shape a predicate that answers with the words to show produces, so a server can write its rules once and read the failures off them:

(validation-errors
 (list (cons "email"
             (and (not (valid-email? email))
                  "Enter an email address."))
       (cons "password"
             (and (< (string-length password) 12)
                  "Use at least 12 characters."))))
;; => (("email" . "Enter an email address."))

The result is exactly what validation-summary takes, and an empty result means the form is fine: a page never has to remember which of its checks failed, or in what order to show them.

A pair whose first element is #f is a problem with the form rather than with one field, and is listed without a link.

Procedure: validation-summary KEY: #:errors #:title #:id #:focus? #:classes

Render the form-level summary of what went wrong, at the top of a form.

A field’s own error message is announced when the reader reaches the field; a summary is announced when the page loads, and it lists every problem in one place with a link to each. That is the pattern WCAG’s error identification asks for, and it is the difference between a form a reader has to hunt through and a form that says what to fix.

errors is a list of (field-id . message) pairs, which is also the shape a server already has when it re-renders a form it refused – validation-errors builds one from the rules themselves. The summary renders nothing at all when the list is empty, so a page can render it unconditionally.

errors

A list of (field-id . message) pairs; an empty list renders nothing.

title

The summary’s heading. Defaults to "There is a problem".

id

An explicit identifier; defaults to "hg-validation-summary".

focus?

Ask the page’s script to put focus on the summary when it loads, which is where a reader should start after a refused submit. Off by default: a page that renders a summary for reasons of its own does not get to move the reader’s focus.

classes

Classes for the summary box.

(validation-summary #:errors '(("email" . "Enter an email address")
                               ("password" . "Enter your password")))

Next: (hygguile components fly-out), Previous: (hygguile components feedback), Up: API