Next: , Previous: , Up: API  


1.30 (hygguile hygguile)

1.30.1 Procedures

Procedure: a content KEY: #:weight #:size #:color #:family #:hover #:extra-classes #:href

Create an HTML anchor link (<a>) component.

If the href key argument evaluates to false, the hyperlink target location defaults directly to the structural evaluation of content.

content

The text label or component displayed inside the interactive anchor boundary.

weight

List of symbols representing font weight CSS classes.

size

List of symbols representing text sizing CSS classes.

color

List of symbols representing base link text color CSS classes.

family

List of symbols representing font family CSS classes.

hover

List of symbols representing interactive state hover coloring CSS rules.

extra-classes

List of base text formatting utility attributes.

href

Optional explicit destination target path URL string.

(a "https://gnu.org" #:extra-classes '(underline))
Procedure: abbr content KEY: #:title #:weight #:color #:size #:decoration #:abbr-classes

Create an inline HTML <abbr> fragment expanding an abbreviation.

The expansion travels in the title attribute, which browsers show on hover and assistive technology can read out.

content

The abbreviation text, e.g. "HTTP".

title

The expansion, e.g. "HyperText Transfer Protocol".

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

size

List of symbols representing text sizing CSS classes.

decoration

List of symbols representing text decoration classes.

abbr-classes

List of additional typography utility classes.

(abbr "HTTP" #:title "HyperText Transfer Protocol")
Procedure: accordion KEY: #:items #:accordion-classes

Stack several accordion-item rows into a JavaScript-free accordion.

Because each row is a native details element, the whole accordion expands and collapses without any scripting.

items

A list of accordion-item or details nodes.

accordion-classes

List of symbols representing the wrapper CSS classes.

(accordion #:items (list (accordion-item #:summary "A" #:content (list (p "Alpha")))
                        (accordion-item #:summary "B" #:content (list (p "Beta")))))
Procedure: accordion-item KEY: #:summary #:content #:open

Create a single accordion row backed by a native details element.

summary

The always-visible row title.

content

The collapsible row body.

open

A boolean expanding the row by default.

(accordion-item #:summary "How do I install it?" #:content (list (p "Use Guix.")))
Procedure: alert KEY: #:title #:body #:variant #:role #:icon #:alert-classes

Create an accessible inline alert or callout box.

Alerts are plain HTML and CSS, so they continue to render and be announced by assistive technology when JavaScript is disabled.

title

An optional short, bold heading for the alert.

body

The alert message text or inline nodes.

variant

A semantic keyword: 'info (default), 'success, 'warning, 'danger or 'neutral.

role

An optional ARIA role override. Defaults to alert for danger and status for every other variant.

icon

An optional Phosphor icon name overriding the variant default.

alert-classes

List of symbols representing container CSS classes.

(alert #:title "Heads up" #:body "Your session expires soon." #:variant 'warning)
Procedure: aspect-ratio content KEY: #:ratio #:rounding #:overflow? #:tag #:classes #:attributes

Reserve a box with a fixed shape for its content, before that content arrives.

The reserved box is what keeps a page from jumping when an image, a video or an embed finally loads: the browser knows the shape from the first byte and lays out around it. A ratio the component does not understand is written through as given, so CSS’s own syntax is always available.

content

The children, as a list of nodes.

ratio

The shape: 'video (default), 'square, 'photo, 'wide, 'golden, a pair, a number, or a CSS string.

rounding

A list of rounding utilities. Defaults to (rounded-xl).

overflow?

Whether the box hides anything that outgrows it. Defaults to true.

tag

The element to build. Defaults to div.

classes

Extra classes for the box.

attributes

Extra attributes, as pairs of name and value.

(aspect-ratio (list (img #:src "hero.jpg" #:alt ""))
              #:ratio 'video)
Procedure: avatar KEY: #:src #:alt #:initials #:size #:shape #:ring #:classes

Create a circular (or rounded) avatar image with an initials fallback.

When src is false, the initials are shown instead, which is useful for user accounts without a profile picture. Everything is plain HTML and CSS, so avatars render with JavaScript disabled.

src

The optional image source URL.

alt

The alternative text describing the depicted person.

initials

Fallback initials shown when no image source is supplied.

size

A size keyword: 'xs, 'sm, 'md, 'lg or 'xl.

shape

A shape keyword: 'circle (default), 'rounded or 'square.

ring

An optional boolean adding a subtle border ring around the avatar.

classes

List of additional utility CSS classes.

(avatar #:src "me.png" #:alt "Josep" #:size 'lg #:ring #t)
Procedure: avatar-group avatars KEY: #:max #:size #:total #:label #:classes

Stack several avatars into one group, with an overflow count.

The group shows max faces and then how many more there are. The overflow is text, not an avatar, so it is read out as a count rather than as a person; the group carries an optional accessible label so a screen reader knows what the row of faces represents.

avatars

A list of avatar nodes.

max

How many avatars to show before the overflow badge. Defaults to 4.

size

The size of the overflow badge’s text. Defaults to 'md.

total

The real number of people, when avatars is a truncated list. Defaults to the length of avatars.

label

An optional accessible name for the group.

classes

Extra classes for the group.

(avatar-group (list (avatar #:src "a.png" #:alt "Ada")
                    (avatar #:src "b.png" #:alt "Grace")))
Procedure: badge content KEY: #:variant #:size

Create a small status pill or label.

The variant argument picks a semantic color treatment while staying readable in both light and dark themes: the fill is the dark end of the ramp with near-white type on the light theme, and the light end with dark type on the dark theme, so the label clears the 4.5:1 ratio of WCAG AA.

content

The label text or inline nodes.

variant

A semantic keyword: 'neutral, 'info, 'success, 'warning or 'danger.

size

A size keyword: 'sm, 'md or 'lg.

(badge "New" #:variant 'success)
Procedure: bdi content KEY: #:classes

Isolate an inline run whose direction the browser should decide.

A username, a file name, a number next to a currency symbol: anything a page does not control the script of. <bdi> keeps it from rearranging the sentence around it.

content

The inline content.

classes

Extra classes.

(p (list "Signed in as " (bdi "@ada")))
Procedure: bdo content KEY: #:dir #:classes

Override the bidirectional algorithm for content.

This is deliberately explicit: it is for displaying a string in the direction opposite to its own, not for laying out a page. Pages that need a whole region mirrored want locale instead.

content

The inline content.

dir

'ltr or 'rtl. Defaults to 'rtl.

classes

Extra classes.

(bdo "abc" #:dir 'rtl)
Procedure: breadcrumbs KEY: #:items #:separator #:nav-class #:list-class

Create an accessible breadcrumb trail from a list of nodes.

The trail is rendered as a <nav> with an ordered list, and the final item is marked with aria-current="page". Breadcrumbs are static markup and work without JavaScript.

items

An ordered list of link or text nodes, from root to current page.

separator

The string shown between items. Defaults to "/".

nav-class

List of symbols representing the nav wrapper CSS classes.

list-class

List of symbols representing the list CSS classes.

(breadcrumbs #:items (list (a "Home" #:href "/") (a "Docs" #:href "/docs") (span "API")))
Procedure: button content KEY: #:id #:type #:hidden #:on-click #:loading #:loading-label #:background #:hover-background #:border-size #:border-color #:shadow #:color #:size #:active #:family #:weight #:rounding #:extra-classes

Create a standard HTML button element.

content

The inner content (text string or HTML fragments) of the button.

id

An optional HTML identifier string.

type

A symbol representing the HTML button type (e.g., 'button, 'submit).

hidden

A boolean indicating whether the button should be hidden.

on-click

A JavaScript handler string triggered on click events.

loading

A boolean putting the button in its loading state: a spinner appears, the control is disabled, and aria-busy says why.

loading-label

The spoken label of the loading state. Defaults to "Loading".

background

List of symbols for background CSS classes.

hover-background

List of symbols for hover background CSS classes.

border-size

List of symbols for border width CSS classes.

border-color

List of symbols for border color CSS classes.

shadow

List of symbols for container shadow CSS classes.

color

List of symbols for text color CSS classes.

size

List of symbols for text sizing CSS classes.

active

List of symbols for active state transformation CSS classes.

family

List of symbols for font family CSS classes.

weight

List of symbols for font weight CSS classes.

rounding

List of symbols for border radius CSS classes.

extra-classes

List of utility formatting layout classes.

(button "Click Me" #:id "btn-primary" #:on-click "alert('Hello!')")
(button "Saving" #:loading #t)
Procedure: caption content KEY: #:position #:classes

Create an HTML <caption> naming a data table.

A caption is the accessible name of the table: assistive technology announces it when the table is reached, so it is worth writing even when a nearby heading says almost the same thing.

content

The caption text string or inline nodes.

position

A symbol placing the caption above ('top, the default) or below ('bottom) the table, purely through the caption-side property.

classes

List of symbols representing typography CSS classes.

(caption "Quarterly revenue")
Procedure: card content KEY: #:card-classes

Create a surface container grouping related content.

Compose a card from card-header, card-body and card-footer fragments, or place any content inside directly.

content

The inner content or section fragments of the card.

card-classes

List of symbols representing container CSS classes.

(card (list (card-header (h3 "Title")) (card-body (p "Body"))))
Procedure: card-body content KEY: #:body-classes

Create the main body section of a card.

content

The body content fragments.

body-classes

List of symbols representing body CSS classes.

(card-body (p "Everything you need to know."))
Procedure: card-description content KEY: #:classes

Create muted descriptive copy inside a card.

content

The description text or inline nodes.

classes

List of symbols representing typography CSS classes.

(card-description "Updated 3 minutes ago.")
Procedure: card-footer content KEY: #:footer-classes

Create the footer section of a card, typically holding actions.

content

The footer content fragments.

footer-classes

List of symbols representing footer CSS classes.

(card-footer (list (submit-button "Save")))
Procedure: card-header content KEY: #:header-classes

Create the header section of a card.

content

The header content fragments.

header-classes

List of symbols representing header CSS classes.

(card-header (h3 "Recent activity"))
Procedure: card-title content KEY: #:classes

Create a card heading.

content

The heading text or inline nodes.

classes

List of symbols representing typography CSS classes.

(card-title "Account summary")

Construct a carousel UI component.

Items are centred on a scroll-snap track that pans with a swipe, and the previous/next arrows page exactly one item at a time using the live gap between items. The arrows themselves are frosted glass discs: compact on phones, where the track needs every pixel it can get, and larger from the small breakpoint up. A disc wears the framework’s own frost layer, at the gentle end of its strength, so the glass is round where the disc is round.

id

The unique HTML string identifier for the inner carousel list element.

carousel-list-css

List of symbols representing CSS classes for the <ul> element.

item-width

List of symbols specifying the width CSS classes for each carousel item.

item-height

List of symbols specifying the height CSS classes for each carousel item.

carousel-item-css

List of symbols representing CSS classes for each item wrapper <li>.

carousel-item-content-css

List of symbols representing CSS classes for the wrapper <div> of the content.

carousel-list-wrapper-css

List of symbols representing CSS classes for the outer wrapper <div>.

carousel-button-css

List of symbols representing base CSS classes for the previous/next buttons.

carousel-left-button-css

List of symbols representing additional CSS classes for the left (previous) button.

carousel-right-button-css

List of symbols representing additional CSS classes for the right (next) button.

no-js-hint

Fallback copy shown inside a <noscript> element when JavaScript is disabled.

fragments

A list of HTML/component S-expression fragments to be rendered as the carousel items.

(carousel #:id "image-carousel"
          #:fragments '(((img (@ (src "img1.png"))))
                        ((img (@ (src "img2.png"))))))

Undocumented procedure.

Procedure: center content KEY: #:max-width #:padding #:axis #:tag #:classes #:attributes

Centre content horizontally (and optionally vertically) with a maximum reading width.

Horizontal centring is the default, because that is the one that keeps a page readable: prose stops growing at max-w-prose and the gutter on either side grows instead. Vertical centring – a sign-in form in the middle of a page – is asked for with #:axis 'both.

content

The children, as a list of nodes.

max-width

A list of max-width utilities. Defaults to (max-w-prose).

padding

A list of horizontal padding utilities. Defaults to (px-4).

axis

'horizontal (default) or 'both.

tag

The element to build. Defaults to div.

classes

Extra classes for the wrapper.

attributes

Extra attributes, as pairs of name and value.

(center (list (h1 "Sign in") (form ...)) #:max-width '(max-w-128) #:axis 'both)
Procedure: checkbox KEY: #:label #:name #:id #:value #:checked #:required #:disabled #:help-text #:error #:input-css #:label-css #:container-css

Create a labelled HTML checkbox input.

Native checkboxes work without JavaScript and are keyboard accessible.

label

The descriptive text shown next to the checkbox.

name

The form field name.

id

An optional explicit HTML identifier; defaults to name.

value

The value submitted when the box is checked.

checked

A boolean rendering the pre-checked state.

required

A boolean making the checkbox mandatory.

disabled

A boolean disabling the control.

help-text

Optional helper copy linked through aria-describedby.

error

Optional validation message rendered with role="alert".

input-css

List of symbols styling the checkbox control.

label-css

List of symbols styling the label text.

container-css

List of symbols styling the wrapping row.

(checkbox #:label "I accept the terms" #:name "terms" #:required #t)
Procedure: chip content KEY: #:variant #:size #:href #:icon #:remove-href #:remove-label #:classes

Create a small tag or chip, optionally removable.

A chip is a badge that belongs to the content rather than to a status: a topic on a post, a filter that is on, a recipient in a compose window. It can be a link (a filter that navigates to its own removal) and it can carry a remove control, which is also a link, so removing a filter is a plain navigation and works with scripting off.

content

The chip’s label.

variant

'neutral (default), 'info, 'success, 'warning or 'danger.

size

'sm, 'md or 'lg.

href

An optional destination, turning the chip into a link.

icon

An optional Phosphor icon name.

remove-href

An optional URL that removes the chip.

remove-label

The accessible name of the remove control.

classes

Extra classes for the chip.

(chip "Scheme" #:variant 'info)
(chip "Draft" #:remove-href "/posts/1?remove=draft")
Procedure: circular-progress KEY: #:value #:max #:size #:label #:show-value #:stroke-width #:classes

Create a circular progress ring.

The ring is an SVG with a normalised pathLength of 100, so the fraction is written as a dash of that length and the browser does the geometry; no trigonometric value has to be computed in Scheme. With no value the ring is indeterminate: it spins through CSS and the accessible value is left out, which is what tells assistive technology that the work has no known end.

value

The current value, or #f for an indeterminate ring.

max

The maximum value. Defaults to 100.

size

'xs, 'sm, 'md, 'lg or 'xl.

label

An accessible name for the ring.

show-value

Whether the percentage is written in the middle. Defaults to false.

stroke-width

The ring’s stroke width in the SVG’s own units. Defaults to 4.

classes

Extra classes for the wrapper.

(circular-progress #:value 68 #:label "Importing" #:show-value #t)
Procedure: cluster content KEY: #:gap #:align #:justify #:tag #:classes #:attributes

Lay children out in a row that wraps when it runs out of room.

A cluster is for the things that belong beside each other but not in a grid: a row of badges, a toolbar, a list of tags. It wraps rather than overflowing, which is what keeps a toolbar usable on a phone without a breakpoint of its own.

content

The children, as a list of nodes.

gap

A list of gap utilities. Defaults to (gap-2).

align

Cross-axis alignment. Defaults to 'center.

justify

Main-axis distribution: 'start (default), 'center, 'end or 'between.

tag

The element to build. Defaults to div.

classes

Extra classes for the cluster itself.

attributes

Extra attributes, as pairs of name and value.

(cluster (list (badge "new") (badge "beta" #:variant 'warning)))
Procedure: code content KEY: #:weight #:color #:size #:background #:rounding #:shadow #:padding-y #:padding-x #:code-classes

Create an inline HTML <code> fragment.

content

The code instruction text string.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

size

List of symbols representing text sizing CSS classes.

background

List of symbols representing inline code surface container background colors.

rounding

List of symbols representing container boundary radius options.

shadow

List of symbols representing structural layout presentation depth drops.

padding-y

List of symbols controlling top/bottom baseline cell padding spaces.

padding-x

List of symbols controlling left/right horizontal boundary alignment padding.

code-classes

List of font typography rules enforcing monospaced code metrics.

(code "(car list-expression)")
Procedure: code-sample form KEY: #:value #:id #:label #:copy-label #:notice-title #:classes #:preview-classes #:panel-classes #:pre-classes #:toggle-classes

Show a component and the form that produced it, in one card.

The specimen is the form rendered, and the source beside it is the same form pretty-printed, so the two can never disagree: pass the expression, not a copy of its output. The source is folded away until a reader asks for it, and it carries a copy button, which is what a documentation page is for.

The form is evaluated for display, in an environment holding both the caller’s bindings and (hygguile hygguile), so a page only writes its specimen once. Pass value to hand over an already-rendered fragment instead – useful when the specimen is not something that can be evaluated, or when you would rather it not be.

form

The S-expression to show and to render, e.g. '(button "Save").

value

An already-rendered fragment to show as the specimen, or #f to render form.

id

A stable HTML identifier, or #f for a generated one. Give one when the page has to name the specimen, e.g. to link to it.

label

The accessible name of the control that unfolds the source.

copy-label

The text of the button that copies the source.

notice-title

The title of the notification a copy raises.

classes

List of CSS classes for the card.

preview-classes

List of CSS classes for the row holding the specimen and the fold control.

panel-classes

List of CSS classes for the panel the source lives in.

pre-classes

List of CSS classes for the <pre> itself, replacing the ones pre chooses; empty leaves the source in the mono face it is set in.

toggle-classes

List of CSS classes for the fold control.

(code-sample '(button "Save"
                      #:on-click "save()"))
Procedure: code-sample-js

Undocumented procedure.

Procedure: col content KEY: #:gap #:shadow #:wrap #:padding-y #:padding-x #:rounding #:background #:items #:col-classes

Create a vertical column container using flexbox layout.

content

The inner content elements or fragments to stack vertically.

gap

List of symbols for spacing gaps between elements.

shadow

List of symbols for shadow effect CSS classes.

wrap

List of symbols defining wrap properties.

padding-y

List of symbols for vertical padding CSS classes.

padding-x

List of symbols for horizontal padding CSS classes.

rounding

List of symbols for container border-radius CSS classes.

background

List of symbols for container background CSS classes.

items

List of symbols for cross-axis alignment CSS classes.

col-classes

List of base vertical flex layout CSS classes.

(col '((div "Top Item") (div "Bottom Item")) #:background '(bg-transparent))
Procedure: collapse KEY: #:summary #:content #:open #:classes

A disclosure whose body animates open and closed.

This is a native <details> with its content in a grid track that moves between zero and one fraction of its height, so the browser animates the real height without a measuring script. The reduced-motion block in the companion stylesheet sets the transition off, and the disclosure still opens instantly – the behaviour was never the animation.

summary

The always-visible caption.

content

The collapsible body, as a list of nodes.

open

Whether it starts open.

classes

Extra classes for the <details>.

(collapse #:summary "Advanced" #:content (list (input #:label "Pattern" #:name "p")))
Procedure: color-swatch KEY: #:color #:label #:aria-label #:swatch-classes #:label-classes

Create a clickable colour tile that steps through the shades of a palette colour.

The tile carries bg-<color> and shows that colour’s name; the script it calls replaces the class with the next variant of the same colour every time it is clicked – 100, 300, 500, 700, 900 and back to 100 – and relabels the tile to match, so the tile always names the shade it is showing.

color

The palette colour to show, named the way a class names it: apple-green-500.

label

The text on the tile, or #f for the colour’s own name.

aria-label

The accessible name of the tile, or #f for a sentence naming the colour and what a click does.

swatch-classes

List of CSS classes for the tile itself.

label-classes

List of CSS classes for the frosted caption inside it.

(color-swatch #:color 'apple-green-500)
Procedure: combobox KEY: #:label #:name #:id #:options #:value #:placeholder #:required #:help-text #:error #: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.

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: command-item label KEY: #:href #:hint #:keywords

Create one command in a command palette.

A command is a link: choosing one is a navigation, and the palette is only a quicker way to find it. keywords are extra words the filter should match (a synonym, an abbreviation) and are kept out of the visible label.

label

The command’s visible text.

href

Where the command goes.

hint

An optional keyboard shortcut or category shown at the end of the row.

keywords

Optional extra text for the filter to match.

(command-item "Open settings" #:href "/settings" #:hint "G S")
Procedure: command-palette KEY: #:id #:label #:placeholder #:commands #:empty-text #:close-label #:classes

Create a command palette, opened by a link and filtered as you type.

The palette is a dialog revealed by :target, like the drawer: an anchor to #commands opens it and an anchor to "#" closes it, so it works with scripting off and the URL states what is open. The input filters the list in the browser, and the script that ships with the framework adds arrow-key navigation and the common keyboard shortcut.

The list is a list of real links, which is what makes the palette useful in a server-rendered page: every row is something that can be followed, not a button whose meaning lives in a client-side handler.

id

The identifier the opening link targets.

label

The accessible name of the dialog and its list.

placeholder

The input’s placeholder. Defaults to "Type a command…".

commands

A list of command-item nodes.

empty-text

What the palette says when the filter matches nothing.

close-label

The accessible name of the backdrop’s close control.

classes

Extra classes for the panel.

(command-palette #:id "commands"
                 #:commands (list (command-item "Home" #:href "/")))
Procedure: copy-button KEY: #:text #:label #:copied-label #:show-text? #:classes

Render a button that copies text to the clipboard.

The button is a progressive enhancement and says so: it carries js-only, so the no-script stylesheet hides it, and a <noscript> beside it shows the value as selectable text instead. A control that silently does nothing is worse than no control at all.

The value lives in a data-hg-copy attribute, not in an inline handler, so the same script serves every copy button on the page and a page with a Content Security Policy does not have to allow inline JavaScript.

text

The value to copy.

label

The accessible label, shown as text only when show-text? is true.

copied-label

The accessible label after a successful copy. Defaults to "Copied".

show-text?

Whether the label is visible beside the icon. Defaults to false.

classes

Extra classes for the button.

(copy-button #:text "(use-modules (hygguile hygguile))")
Procedure: copy-pre-inner-text-js

Undocumented procedure.

Procedure: custom-fonts KEY: #:preconnects #:fonts

Generate S-expression tags to optimize and load external web typography families asynchronously.

preconnects

A list of S-expression initialization link tags specifying network domains to warm up.

fonts

A list of configuration strings selecting families and weights to include from the external repository API.

display-swap

A string directive identifying font rendering behaviors during file downloads.

Procedure: danger-button content KEY: #:loading #:loading-label #:type #:id #:on-click #:extra-classes

A destructive action: the colour is a warning, and the label says what it does.

content

The button’s label or nodes.

loading

A boolean putting the button in its loading state.

loading-label

The spoken label of the loading state.

type

The HTML button type.

id

An optional identifier.

on-click

A JavaScript handler.

extra-classes

Extra layout classes.

(danger-button "Delete project")
Procedure: danger-submit-button content

Create a red-themed HTML form submission button meant for destructive actions.

content

The inner content (text string or HTML fragments) of the button.

(danger-submit-button "Delete Repository")
Procedure: date-field KEY: #:label #:name #:id #:value #:min #:max #:required #:help-text #:error #: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.

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 #: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.

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: dd content KEY: #:dd-classes #:weight #:family #:color #:size

Create an HTML description details (<dd>) element.

A description is indented behind a subtle left rule, which visually ties it to the term above and keeps longer prose easy to scan.

content

The detailed explanation text string or fragment.

dd-classes

List of symbols for layout and indentation CSS classes.

weight

List of symbols for font weight CSS classes.

family

List of symbols for font family CSS classes.

color

List of symbols for text color CSS classes.

size

List of symbols for text sizing CSS classes.

(dd "An implementation of the Scheme programming language.")
Procedure: density-js

Undocumented procedure.

Procedure: density-toggle KEY: #:current #:label #:classes

Render the control that switches a page between comfortable and compact density.

The control is a progressive enhancement: it carries js-only, and the no-script stylesheet hides it, because a density preference that cannot be remembered is a button that lies. The script stores the choice and sets data-density on the root element before the page is drawn, so a reader never sees one density and then the other.

A page opts in by reading the same attribute in its own stylesheet; the framework’s data grid, field shell and sidebar already do.

current

The density the server rendered the page in. Defaults to 'comfortable.

label

The accessible label of the control.

classes

Extra classes for the button.

(density-toggle #:current 'comfortable)
Procedure: density-value density

Return the scale of a density mode as a string, defaulting to comfortable for a mode the table does not name.

density

'comfortable or 'compact.

Procedure: design-tokens-css KEY: #:class

Return the SXML <style> block that publishes every design token.

The block has three parts: the light theme on :root, the dark theme on [data-theme="dark"], and a reduced-motion block that sets every duration to zero. A page that loads it can override any single decision from its own stylesheet – or from markup, with an inline custom property – and the components that read var(--hg-space-4, 1rem) follow.

The class on the style element is only there to make the block easy to find in a document; the tokens themselves are declared on the root element, not scoped to it.

class

A class name for the <style> element itself.

(design-tokens-css)
Procedure: details content KEY: #:summary #:open #:details-classes #:body-classes

Create a native HTML <details> disclosure element.

The element expands and collapses with no JavaScript at all, relying on the browser’s built-in disclosure behaviour.

content

The collapsible body content.

summary

The summary caption rendered in the always-visible header. Accepts a string or a summary node.

open

A boolean expanding the disclosure by default.

details-classes

List of symbols representing the wrapper CSS classes.

body-classes

List of symbols representing the collapsible body CSS classes.

(details (list (p "Hidden details")) #:summary "Show more")
Procedure: disclosure-controls KEY: #:selector #:expand-label #:collapse-label #:classes

Create the pair of buttons that open or close every disclosure of a tray.

selector is any CSS selector, so the pair works for a grid of expandable-cards, for a stack of accordions, or for the plain details elements of a page. Each disclosure is still an ordinary disclosure that opens and closes on its own; these two buttons only save a reader the clicking.

selector

A CSS selector matching the elements to open or close.

expand-label

The label of the button that opens them all.

collapse-label

The label of the button that closes them all.

classes

List of CSS classes for the pair.

(disclosure-controls #:selector "details.hg-expandable-card")
Procedure: dl content KEY: #:dl-classes

Create an HTML description list (<dl>) element.

Terms and descriptions are stacked in a single column with a little vertical rhythm and a comfortable reading width. Pair it with dt for the term and dd for its description.

content

The inner term (<dt>) and description (<dd>) elements.

dl-classes

List of symbols for layout and style CSS classes.

(dl '((dt "Framework") (dd "Hygguile")))
Procedure: drawer KEY: #:id #:title #:content #:footer #:side #:close-label #:label #:classes

Create a drawer that slides in from an edge, opened by a link.

The drawer is a real dialog, revealed by the CSS :target pseudo-class: link to it with (a "Filters" #:href "#filters") and it appears, follow a link to "#" and it is gone. That means the whole overlay works with scripting off, and the URL says which overlay is open, which is the same contract the framework’s modal already uses.

The drawer is placed at the end of the page, so the link that opens it is never itself covered by it. A drawer that carries a form is the common case – filters, a cart, a settings panel – and the footer is where its actions go.

id

The identifier the opening link targets.

title

The dialog’s title, also its accessible name.

content

The drawer body.

footer

An optional list of actions.

side

'right (default), 'left or 'bottom.

close-label

The accessible name of the close control.

label

An optional accessible name overriding the title.

classes

Extra classes for the panel.

(drawer #:id "filters" #:title "Filters"
        #:content (list (checkbox #:label "In stock" #:name "stock")))
Procedure: dropdown-item label KEY: #:href #:on-click #:danger? #:disabled? #:classes

Create one item of a dropdown menu.

The item is a link when it has an href, and a button otherwise, so a menu is a list of ordinary controls rather than a list of scripted rows. Items carry role="menuitem"; the menu itself is the native disclosure around them.

label

The item’s text.

href

The destination, for a navigation item.

on-click

A JavaScript handler, for an item that acts in place.

danger?

Whether the item is destructive.

disabled?

Whether the item is disabled.

classes

Extra classes for the item.

(dropdown-item "Duplicate" #:on-click "duplicate()")
Procedure: dropdown-menu KEY: #:label #:items #:position #:open? #:classes

Create a dropdown menu that opens without JavaScript.

The menu is a native <details>: its summary is the trigger, its body is a list of menu items, and the browser does the opening and closing. The script that ships with the framework adds the keyboard behaviour a role="menu" promises – arrow keys, Home and End, Escape – and leaves the menu usable when it is not there.

label

The trigger’s visible text.

items

A list of dropdown-item nodes.

position

'bottom-start (default), 'bottom-end, 'top-start or 'top-end.

open?

Whether the menu starts open. Defaults to false; a menu on a page normally opens when its summary is clicked, but a demo or a menu that should draw attention can start expanded.

classes

Extra classes for the menu wrapper.

(dropdown-menu #:label "Actions"
               #:items (list (dropdown-item "Rename" #:href "/rename")
                             (dropdown-item "Delete" #:danger? #t)))
Procedure: dropzone KEY: #:label #:name #:id #:accept #:multiple #:required #:help-text #:error #: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.

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: dt content KEY: #:dt-classes #:weight #:family #:style #:color #:size

Create an HTML description term (<dt>) element.

A term renders as a compact, semi-bold label so it reads as a key above its description. The small top margin separates one term/description pair from the next.

content

The term text string or fragment.

dt-classes

List of symbols for default structural spacing CSS classes.

weight

List of symbols for font weight CSS classes.

family

List of symbols for font family CSS classes.

style

List of symbols for font style CSS classes (empty by default).

color

List of symbols for text color CSS classes.

size

List of symbols for text sizing CSS classes.

(dt "Guile Scheme" #:weight '(font-bold))
Procedure: em content KEY: #:weight #:color #:family #:leading #:style #:size

Create an HTML emphasis (<em>) layout block.

content

The inner text content or target inline nodes.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

leading

List of symbols representing line-height spacing CSS classes.

style

List of symbols representing text element style transformations.

size

List of symbols representing text sizing CSS classes.

(em "Please notice this crucial contextual exception.")
Procedure: empty-state KEY: #:icon #:title #:body #:action #:classes

Create the first-class empty state a data component falls back to.

An empty list is not an error and should not look like one: the icon is a quiet object, the copy says what is missing and, when there is one, the action says what to do about it. Assistive technology gets the heading it needs to understand the region, because the title is a real <h3>.

icon

The Phosphor icon name. Defaults to 'tray.

title

The heading that names the state.

body

Optional explanatory copy.

action

An optional node – usually a link or a button – offering the next step.

classes

Extra classes for the wrapper.

(empty-state #:title "No projects yet"
             #:body "Create one to get started."
             #:action (primary-button "New project"))
Procedure: error-state KEY: #:title #:body #:retry #:retry-label #:details #:classes

Create the error state a data component shows when a request failed.

It is an empty state with a different voice – a warning mark, a danger ink, and a retry action – because "there is nothing here" and "this did not load" call for different responses from the reader. The retry is just a link or a button the page passes, so it works with a normal navigation and no client-side store has to be asked to try again.

title

The heading. Defaults to a generic failure sentence.

body

The explanation. Defaults to a generic one.

retry

An optional retry node, usually a link back to the same URL.

retry-label

The text of the default retry link, used when no retry node is given.

details

Optional technical detail, folded away in a disclosure.

classes

Extra classes for the wrapper.

(error-state #:retry (a "Try again" #:href "/reports"))
Procedure: expandable-card content KEY: #:id #:title #:icon #:tag #:open #:wide? #:data #:card-classes #:summary-classes #:body-classes

Create a card that opens in place, its header doubling as the disclosure control.

The card is a native <details>, so it opens and closes with no JavaScript at all, and id is placed on the <summary> rather than on the <details>: a link to that id then points *inside* the card, which is what makes a browser open it as it scrolls there, and the summary is the header a reader should land on.

content

The nodes revealed while the card is open.

id

An optional HTML identifier, placed on the summary, for a link to land on.

title

The card’s title, rendered as a real heading inside the summary.

icon

An optional Phosphor icon name shown before the title, e.g. 'cards.

tag

An optional short label shown as a badge beside the title, such as the category a card belongs to.

open

Whether the card starts open.

wide?

Whether the card spans the whole row of a grid it is placed in.

data

An association list of data-* attributes rendered on the <details>, named without the data- prefix. These are what a sort-control orders a tray of cards by, and a card carries as many as the orderings you intend to offer: '((order . "0") (title . "Cards")).

card-classes

List of additional container CSS classes.

summary-classes

List of CSS classes for the header, which is the disclosure control.

body-classes

List of CSS classes for the layer holding content.

(expandable-card (p "Cards group related content together.")
                 #:id "cards"
                 #:title "Cards"
                 #:icon 'cards
                 #:tag "Layout"
                 #:open #t
                 #:data '((order . "0") (title . "Cards")))
Procedure: fade-in content KEY: #:duration #:delay #:tag #:classes #:attributes

Fade content in, and do nothing at all when motion is reduced.

The duration is read from the design tokens, so the reduced-motion block that sets every token to zero turns the animation off without a second media query here. That is the whole point of the primitive: motion is a token, not a commitment sprinkled through the markup.

content

The children, as a list of nodes.

duration

A motion token: 'fast, 'base (default), 'slow or 'slower.

delay

An optional delay token or CSS time.

tag

The element to build. Defaults to div.

classes

Extra classes for the wrapper.

attributes

Extra attributes, as pairs of name and value.

(fade-in (list (alert ...)))
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.

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.

required

Whether the control is required.

(field-wiring #:name "email" #:help-text "We never share it.")
Procedure: fieldset content KEY: #:legend #:fieldset-classes

Create an HTML <fieldset> grouping related form controls.

content

The list of form controls or fragments contained by the group.

legend

An optional caption rendered as the fieldset legend.

fieldset-classes

List of symbols representing container CSS classes.

(fieldset (list (input #:label "City" #:name "city")) #:legend "Address")
Procedure: figcaption content KEY: #:classes

Create an HTML <figcaption> caption element.

content

The caption text string or inline nodes.

classes

List of symbols representing typography CSS classes.

(figcaption "Figure 1: the hygguile logo")
Procedure: figure content KEY: #:caption #:caption-classes #:align #:figure-classes

Create an HTML <figure> element with an optional caption.

content

The figure body, typically an img or picture node.

caption

An optional caption rendered as a figcaption below the body.

caption-classes

List of symbols representing caption typography CSS classes.

align

A symbol controlling alignment, one of 'start, 'center or 'end.

figure-classes

List of symbols representing the wrapper layout CSS classes.

(figure (img #:src "logo.png" #:alt "The logo") #:caption "Figure 1")
Procedure: file-field KEY: #:label #:name #:id #:accept #:multiple #:capture #:required #:help-text #:error #: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.

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: fly-out KEY: #:aria-label #:nav-class #:top-ul-class #:items

Construct an outer fly-out navigation menu shell.

aria-label

The accessibility text label string assigned to the <nav> component.

nav-class

List of symbols representing CSS classes for the structural <nav> wrapper.

top-ul-class

List of symbols representing CSS classes for the top-level container <ul>.

items

A list of sub-components or list items to nest within the top-level menu container.

(fly-out #:aria-label "Main Navigation"
         #:items (list (fly-out-item "Home")))
Procedure: fly-out-css

Undocumented procedure.

Procedure: fly-out-item content KEY: #:li-class

Construct a single leaf item within a fly-out menu.

content

The text description, link, or component fragment inside the item container.

li-class

List of symbols representing CSS classes for the wrapping <li> item.

(fly-out-item "Documentation" #:li-class '(px-2 py-2))
Procedure: fly-out-js

Undocumented procedure.

Procedure: fly-out-submenu KEY: #:li-class #:button-class #:ul-class #:button-content #:items

Construct a fly-out sub-menu dropdown block.

The dropdown is a frosted glass surface built by glass-panel: the frost (the translucent tint and the backdrop blur) is the panel’s, so a dropdown hanging over the page shows the page through it. The menu keeps its own shape – it is absolutely placed under its trigger, and the rounding, shadow and padding stay in ul-class. It is still the same <ul> with the flyout-submenu class and the hidden attribute that fly-out.js opens and closes, so nothing about the behaviour changes.

li-class

List of symbols representing CSS classes for the wrapper <li> component.

button-class

List of symbols representing CSS classes for the trigger <button> element.

ul-class

List of symbols representing the dropdown’s own shape and layout. A colour utility among them replaces the panel’s tint.

button-content

S-expression content or fragments rendered inside the dropdown trigger button.

items

A list of nested child items (typically fly-out-item items) belonging to this sub-menu.

(fly-out-submenu #:button-content '((span "Settings"))
                 #:items (list (fly-out-item "Account Profile")))

Create an HTML footer element.

content

The inner content or layout fragments of the footer.

background

List of symbols for background CSS classes.

shadow

List of symbols for shadow effect CSS classes.

footer-classes

List of utility formatting and layout CSS classes.

(footer "© 2026 Hygguile Framework")
Procedure: form content KEY: #:action #:method #:enctype #:target #:id #:name #:autocomplete #:novalidate #:aria-label #:submit #:form-classes

Create a working HTML <form> around a group of controls.

This is the wrapper for the common case: a form that posts its fields to a server-rendered page. It works with JavaScript disabled, because the browser itself serialises the controls and navigates to action, and it opts into the browser’s native validation unless you pass #:novalidate #t and handle the messages yourself.

Pass #:submit to append a ready-made submit button, or place your own buttons in content when a form needs several actions.

content

The controls and fieldsets that make up the form.

action

The URL the browser navigates to on submission.

method

A symbol or string, 'post by default; use 'get for searches.

enctype

An optional encoding symbol such as 'multipart/form-data, required for file uploads.

target

An optional browsing context string, e.g. "_blank".

id

An optional HTML identifier.

name

An optional form name string.

autocomplete

An optional string ("on" or "off") for the whole form.

novalidate

A boolean switching off native validation, for forms validated by hand.

aria-label

An optional accessible name when no visible heading describes the form.

submit

An optional label for a submit button appended after the fields.

form-classes

List of symbols representing the form layout CSS classes.

(form (list (fieldset (list (input #:label "Email" #:name "email"
                                   #:type 'email #:required #t
                                   #:autocomplete "email"))
                   #:legend "Newsletter"))
      #:action "/subscribe"
      #:submit "Subscribe")
Procedure: form-error content KEY: #:classes

Create an accessible inline form validation message.

The message is announced by assistive technology through role="alert".

content

The error message string or inline nodes.

classes

List of symbols representing typography CSS classes.

(form-error "Please provide a valid email address.")
Procedure: form-field KEY: #:label #:name #:id #:help-text #:error #:required #:label-hidden? #:control #:classes #:label-css #:help-classes #:error-css

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.

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.

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.

(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))))))))
Procedure: forms-js

Undocumented procedure.

Procedure: get-short-commit-sha KEY: #:cmd

Retrieve the short Git commit SHA or execute a custom version control command.

cmd

The shell command string to execute, defaulting to the command that fetches the 7-character short commit hash.

(get-short-commit-sha)
Procedure: ghost-button content KEY: #:loading #:loading-label #:type #:id #:on-click #:extra-classes

A quiet action that only shows its surface on hover or focus.

content

The button’s label or nodes.

loading

A boolean putting the button in its loading state.

loading-label

The spoken label of the loading state.

type

The HTML button type.

id

An optional identifier.

on-click

A JavaScript handler.

extra-classes

Extra layout classes.

(ghost-button "Skip for now")
Procedure: glass-panel content KEY: #:washes #:drift? #:accent #:tag #:attributes #:position #:frost #:tint #:rounding #:border #:shadow #:clip? #:padding #:classes #:content-classes

Create a frosted glass surface, with colour washes behind it if you want them.

The frost is the panel’s own translucent tint plus hg-frost, the framework’s rounded frost layer (see hygguile-extras.css): whatever sits behind the panel arrives blurred and coloured through it, so the panel reads as a pane of glass rather than an opaque card. How strong the frost is is the panel’s to say with frost, or a page’s in markup the component did not build: it is written as --hg-frost-blur, and four rems stand until something says otherwise.

The frost is not a backdrop-filter on the surface itself, and that is the point of the layer. A browser paints a filtered backdrop in the element’s square box and ignores its radius, which leaves a square patch of frost at every rounded corner; neither the radius nor a clip path on that element stops it, and clipping the surface would trap whatever it is meant to let out of its own corner. The layer therefore paints the frost, clips itself to the surface’s own radius, and leaves the surface free.

The surface never clips. The decorations (the washes and the accent) are wrapped in their own rounded clip layer, which is what keeps those oversized discs inside the glass, and that layer is a plain element, which browsers clip to its radius reliably.

The layer that holds the content does not clip unless the panel asks it to with clip?, and that default is deliberate. That layer’s edge falls exactly on the content’s own edge – the surface’s padding is on the surface – so a clip there shaves whatever sits flush with it by a device pixel: the left arc of a line’s first glyph, the stroke of a table’s first cell. Clipping is a knob a panel reaches for, not a tax every panel pays.

The washes are the wallpaper trick: a few oversized, heavily blurred discs of colour thrown across the panel, each described by glass-wash. They are decoration, so they are hidden from assistive technology and never take a pointer event, and the content sits in a positioned layer of its own above them. With drift? the discs move very slowly, out of phase with one another, so the glass has something to frost without ever stealing attention; left alone they are still, which is the calmer default for a panel that is often just a bit of background. Either way the drift stops for anyone who asks for less motion: prefers-reduced-motion turns the animation off in the stylesheet.

The layer the discs ride in wears the panel’s own rounding and clips, and that is still not enough for a blurred disc: a browser draws a filter’s blur in the square box the blur reaches, so without a clip of its own the wash leaves a faint square of ink beside each rounded corner. The layer’s class names that clip, and a clip has to name a radius, so it carries the framework’s default rounding as its own default. A panel that rounds its glass differently says so in --hg-glass-clip, on the panel or on any ancestor of the layer. See hygguile-extras.css for the whole story, and for the measurements that leave an explicit clip as the only cure.

A panel with no decorations has no layers, and then there is nothing to sit above: the content is the panel’s own children and no layer is added. That is what lets this one component be a <header>, a <ul> or a <div> without leaving an extra element inside, which a <ul> may not legally hold. A panel that does have decorations gets its decorations in a rounded <div> of their own and its content in a second one.

content

The panel’s contents.

washes

A list of glass-wash descriptors, or the empty list (the default) for a plain frosted panel.

drift?

A boolean; #t lets the washes drift.

accent

An optional decorative node drawn inside the clipped surface, above the washes and below the content: a hairline of gradient along the top edge, for instance.

tag

The element the surface is built from. Defaults to div; a bar wants header and a menu wants ul.

attributes

Extra attributes for the surface, as pairs of name and value.

position

List of symbols saying how the surface is placed. Defaults to (relative), which is what gives the washes something to be absolute against. A surface pinned some other way – (sticky top-0) for a bar – passes its own, or the empty list to place itself with the layout classes alone.

frost

How strong the frost is: a number of rems, or any CSS length as a string. Defaults to #f, which declares nothing and leaves the strength to the stylesheet’s own 4rem – the value Olive’s backdrop-blur-md would set. A pane that wants a gentler or heavier glass than the framework’s own passes its own, and carries no style attribute at all when it does not.

tint

List of symbols representing the frosted tint. Defaults to (bg-stone-100/75 dark:bg-onyx-900/75).

rounding

List of symbols representing the corner rounding. Defaults to (rounded-3xl).

border

List of symbols representing the border, or the empty list for none. Defaults to a two-pixel hairline in the stone ramp.

shadow

List of symbols representing the shadow, or the empty list for none. Defaults to (shadow-lg).

clip?

Whether the panel hides its content’s overflow, in the layer holding the content. Defaults to false: the washes are oversized but it is the layer they are painted in that keeps them inside the glass, so the content layer has no work to do unless a panel hands it some. Leaving it open is what keeps a line’s first glyph whole – that layer’s edge is the content’s own edge – and it keeps a surface free to let a child escape, a bar holding a dropdown being the case worth naming. The surface itself never carries overflow-hidden, whichever way clip? is set.

padding

List of symbols representing the padding classes. Defaults to (p-6 sm:p-8).

classes

Extra classes for the surface itself. A colour utility here replaces the corresponding default rather than piling onto it (see override-classes), so a surface can recolour its own glass.

content-classes

Extra classes for the layer holding the content, when a layer is added, so a panel can lay its contents out without reaching for the surface – a (flex flex-col gap-6) hero, for instance, spaces its rows with it.

(glass-panel (list (h1 "Hygguile"))
             #:washes (list (glass-wash '(bg-naples-yellow-500/75
                                          w-96 h-96 left-4 top-4)
                                        #:delay -6))
             #:frost 3
             #:drift? #t)

(glass-panel (list (nav-link "Home" #:href "/"))
             #:tag 'header
             #:position '(sticky top-0)
             #:rounding '()
             #:border '()
             #:padding '(px-4 py-4))
Procedure: glass-wash classes KEY: #:delay #:pace #:distance

Describe one colour wash for glass-panel.

A wash is a description, not markup: it says what the disc is made of and when it sets off, and the panel supplies the rest. The classes given here are read by Olive the way any other utility classes are, so the size (w-96), the place inside the panel (left-4 top-4) and the colour (bg-naples-yellow-500/75) are all just names. The panel adds the mechanics – the absolute positioning, the heavy blur, the rounding, the fact that the disc is decoration and never takes a pointer event – and decides whether it drifts at all, so one wash can stand still on one panel and move on another.

classes

List of symbols: the disc’s size, position and colour.

delay

A number of seconds, or any CSS time as a string. A negative delay starts the disc part way through its cycle, which is how several washes are kept out of phase with one another; #f (the default) leaves it in step with the rest.

pace

How long one drift takes: a number of seconds or any CSS time as a string, emitted as the disc’s --hg-blob-duration, which is what the stylesheet reads for the pace and is where its own default sits. Omitted, a panel drifts at that default. This is what lets one panel breathe slowly and another wander on a short leash, without a second stylesheet or a new class.

distance

How far the disc travels: a pair of lengths – horizontally and vertically – or a single length, which moves it along the horizontal axis and leaves the stylesheet’s own upward drift in place. A bare number counts as rems, so '(2 -1) travels 2rems right and 1rem up. Omitted, the disc travels as far as the stylesheet’s default.

(glass-wash '(bg-china-rose-500/50 w-72 h-72 right-4 top-4)
            #:delay -14 #:pace 40 #:distance '(3 -2))
Procedure: glossary-term term KEY: #:href #:note #:position #:classes

Create an inline term that may link to its documentation and may spell itself out.

A page’s prose names a technology once and this puts the two other things that name implies on that same word: where the term is documented, and, for an abbreviation, what it stands for. The note is a tooltip, so it appears on hover and on keyboard focus alike, and it wraps the link rather than sitting beside it – one word carries both, the reader who has never met the abbreviation learns what it stands for without leaving the page, and the reader who wants the manual follows that same word.

term

The word as the prose writes it.

href

Where the term is documented, or #f for a term that is only text.

note

What the term stands for, or #f for a term with nothing to spell out.

position

Where the note appears: 'top (default), 'bottom, 'left or 'right.

classes

List of additional utility CSS classes for the note.

(glossary-term "SXML"
               #:href "https://en.wikipedia.org/wiki/SXML"
               #:note "Scheme XML: XML written as S-expressions")
Procedure: glyph-option KEY: #:value #:glyph #:content #:family #:selected-value #:disabled

Construct an HTML option introduced by an inline glyph.

The glyph and the label are written as the option’s own text, because that is all an <option> may hold: a native dropdown is drawn by the browser or the operating system, which lays out the text of a row and ignores any element nested inside it. A glyph here is therefore a character or an emoji rather than an icon component, and it appears wherever the option is shown.

value

The value submitted when this option is chosen.

glyph

The character or emoji introducing the row, e.g. "🇳🇱".

content

The text of the row. Defaults to value.

family

List of symbols representing font family CSS classes for the option.

selected-value

The value the dropdown is currently showing.

disabled

A boolean marking the option as not choosable.

(glyph-option #:value "nl" #:glyph "🇳🇱" #:content "Nederlands")
Procedure: grid content KEY: #:columns #:gap #:min-column #:dense? #:tag #:classes #:attributes

Lay children out in a responsive grid.

Two modes, and the difference matters. Given columns, the grid is one column on a phone, two from the small breakpoint and the requested number from the medium one – the familiar shape, expressed with Olive’s breakpoint variants. Given min-column, the browser fits as many columns as the track can hold at that minimum width, which is what a gallery of cards wants: no breakpoint has to guess how wide a card should be, and a wide screen simply shows more of them.

content

The children, as a list of nodes.

columns

The number of columns from the medium breakpoint up. Defaults to 2.

gap

A list of gap utilities. Defaults to (gap-4).

min-column

An optional CSS length, such as "16rem", switching the grid to intrinsic sizing instead of breakpoints.

dense?

Whether the grid may backfill holes left by items spanning several tracks.

tag

The element to build. Defaults to div.

classes

Extra classes for the grid itself.

attributes

Extra attributes, as pairs of name and value.

(grid (list (card ...) (card ...) (card ...)) #:columns 3)
(grid (list (card ...) (card ...)) #:min-column "18rem")
Procedure: h1 content KEY: #:weight #:color #:family #:size #:leading

Create an HTML <h1> heading.

content

The inner text string or child elements of the heading.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

size

List of symbols representing text sizing CSS classes.

leading

List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size (see heading-leading). A size the scale does not name is led by its tightest ratio, so the ink of a heading a page has resized for itself still fits its line box.

(h1 "Main Page Title" #:color '(text-onyx-700 dark:text-stone-100))
Procedure: h2 content KEY: #:weight #:color #:family #:size #:leading

Create an HTML <h2> heading.

content

The inner text string or child elements of the heading.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

size

List of symbols representing text sizing CSS classes.

leading

List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.

(h2 "Section Subtitle" #:weight '(font-semibold))
Procedure: h3 content KEY: #:weight #:color #:family #:size #:leading

Create an HTML <h3> heading.

content

The inner text string or child elements of the heading.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

size

List of symbols representing text sizing CSS classes.

leading

List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.

(h3 "Subsection Heading")
Procedure: h4 content KEY: #:weight #:color #:family #:size #:leading

Create an HTML <h4> heading.

content

The inner text string or child elements of the heading.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

size

List of symbols representing text sizing CSS classes.

leading

List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.

(h4 "Minor Subsection Title")
Procedure: help-text content KEY: #:classes

Create a small helper paragraph providing extra field guidance.

content

The helper copy string or inline nodes.

classes

List of symbols representing typography CSS classes.

(help-text "We will never share your email.")
Procedure: hide content KEY: #:from #:tag #:classes #:attributes

Hide content from a breakpoint up.

Olive’s responsive utilities are min-width, so there is no plain utility that means "gone above this width"; the companion stylesheet names the five classes this needs (hg-hidden-from-sm and friends), and this component is the name a page uses instead of remembering them.

content

The children, as a list of nodes.

from

The breakpoint: 'sm, 'md (default), 'lg, 'xl or '2xl.

tag

The element to build. Defaults to div.

classes

Extra classes for the wrapper.

attributes

Extra attributes, as pairs of name and value.

(hide (list (toolbar ...)) #:from 'md)
Procedure: hover-card KEY: #:trigger #:title #:content #:position #:label #:classes

Show a small card of richer content when a trigger is hovered or focused.

A tooltip is a sentence; a hover card is a little more – a name, a role, a couple of links. It appears on hover and on keyboard focus, because :focus-within is part of the same rule, and its content is real markup inside the page, not generated content, so assistive technology can reach it.

The card is not a modal and does not trap focus: a reader can ignore it, tab past it, or follow what is inside it. That is the right shape for a preview; a surface that must be dealt with is a dialog.

trigger

The label or node the card is attached to.

title

An optional heading inside the card.

content

The card body.

position

'bottom-start (default), 'bottom, 'top-start or 'top.

label

The accessible name of the card.

classes

Extra classes for the card.

(hover-card #:trigger "@ada" #:title "Ada Lovelace"
            #:content (list (p "Wrote the first algorithm.")))
Procedure: hr KEY: #:margin #:border #:color

Create an HTML horizontal rule (<hr>) element.

margin

List of symbols representing margin CSS classes.

border

List of symbols representing border width CSS classes.

color

List of symbols representing border color CSS classes.

(hr #:margin '(my-4) #:color '(border-redwood-500))
Procedure: hygguile-recommended-meta

Return an S-expression list containing basic framework-recommended HTML metadata tags.

Includes mobile-responsive viewport parameters and typical character set configurations.

(hygguile-recommended-meta)
Procedure: hygguile-recommended-resources KEY: #:fonts

Assemble a list of recommended UI resource fragments, including standard web typography assets and component script blocks.

fonts

An S-expression block managing custom font loading configurations, defaulting to the result of (custom-fonts).

(hygguile-recommended-resources)
Procedure: hygguile-version-string

Return the one-line version banner for hygguile, as ‘maak version’ prints it.

Procedure: icon KEY: #:type #:weight #:icon-css

Construct an inline HTML icon element using Phosphor formatting.

type

A symbol designating the core identifier name of the Phosphor icon asset (e.g., 'star, 'gear).

weight

A configuration symbol defining line weight thickness variants. Accepts 'bold, 'light, or defaults to 'regular.

icon-css

List of symbols representing additional layout formatting rules or sizing CSS classes.

(icon #:type 'heart #:weight 'bold #:icon-css '(text-redwood-500 text-2xl))
Procedure: icon-button KEY: #:id #:icon #:content #:hidden #:on-click #:type #:background #:hover-background #:border-size #:border-color #:shadow #:size #:family #:weight #:color #:rounding #:active #:icon-css #:extra-classes #:icon-text-container-classes

Create a button containing both an icon and text.

id

An optional HTML identifier string.

icon

A symbol identifying the type of icon to render.

content

The text string or components positioned next to the icon.

hidden

A boolean indicating whether the button should be hidden.

on-click

A JavaScript handler string triggered on click events.

type

A symbol representing the HTML button type.

background

List of symbols for background CSS classes.

hover-background

List of symbols for hover background CSS classes.

border-size

List of symbols for border width CSS classes.

border-color

List of symbols for border color CSS classes.

shadow

List of symbols for shadow CSS classes.

size

List of symbols for text sizing CSS classes.

family

List of symbols for font family CSS classes.

weight

List of symbols for font weight CSS classes.

color

List of symbols for text color CSS classes.

rounding

List of symbols for border radius CSS classes.

active

List of symbols for active state transformation CSS classes.

icon-css

List of symbols targeting icon coloring and sizing classes.

extra-classes

List of utility layout CSS classes.

icon-text-container-classes

List of symbols applied to the inner layout wrapper surrounding the icon and content.

(icon-button #:icon 'download #:content "Fetch Data" #:on-click "downloadData()")
Procedure: icon-only-button KEY: #:icon #:label #:id #:type #:on-click #:loading #:loading-label #:background #:hover-background #:color #:border-color #:icon-css #:extra-classes

An icon button with no visible text, named for assistive technology.

The label is not decoration: an icon-only control has no accessible name without it, so it is rendered as a visually hidden <span> and the button carries aria-label as well.

icon

The Phosphor icon name.

label

The accessible name.

id

An optional identifier.

type

The HTML button type.

on-click

A JavaScript handler.

loading

A boolean putting the button in its loading state.

loading-label

The spoken label of the loading state.

background

Background classes.

hover-background

Hover background classes.

color

Text and icon colour classes.

border-color

Border colour classes.

icon-css

Icon sizing and colour classes.

extra-classes

Extra layout classes.

(icon-only-button #:icon 'gear #:label "Settings")
Procedure: image-gallery images KEY: #:columns #:gap

Create a responsive, evenly spaced gallery grid for a list of image nodes.

images

A list of image or figure nodes to lay out.

columns

The number of columns to request on wide viewports.

gap

List of symbols representing gap CSS classes.

(image-gallery (list (img #:src "a.png" #:alt "A")
                    (img #:src "b.png" #:alt "B")))
Procedure: img KEY: #:src #:alt #:title #:srcset #:sizes #:width #:height #:loading #:decoding #:fetch-priority #:referrer-policy #:crossorigin #:aspect-ratio #:fit #:rounding #:shadow #:img-classes #:extra-classes

Create an accessible HTML <img> element.

Unlike a bare image tag, alt is always rendered, even when empty, so decorative images are announced correctly by assistive technology. The image is lazy-loaded and asynchronously decoded by default.

src

The image source URL string.

alt

The alternative text. Use an empty string for purely decorative images.

title

An optional advisory title string.

srcset

An optional responsive candidate set string, e.g. "a.png 1x, a@2x.png 2x".

sizes

An optional source-size list describing layout widths to the browser.

width

An optional intrinsic width in pixels, helping avoid layout shift.

height

An optional intrinsic height in pixels, helping avoid layout shift.

loading

A symbol controlling native lazy loading, either 'lazy or 'eager.

decoding

A symbol requesting 'async or 'sync image decoding.

fetch-priority

An optional string such as "high" for above-the-fold hero images.

referrer-policy

An optional referrer policy string.

crossorigin

An optional CORS setting string such as "anonymous".

aspect-ratio

An optional aspect ratio class value, e.g. 'square or "16/9".

fit

A symbol mapping to an object-* fit class: 'cover, 'contain, 'fill or 'none.

rounding

List of symbols representing border radius CSS classes.

shadow

List of symbols representing shadow CSS classes.

img-classes

List of symbols representing base image CSS classes.

extra-classes

List of additional utility CSS classes.

(img #:src "avatar.png" #:alt "Portrait" #:width 320 #:height 320)
Procedure: input KEY: #:label #:name #:id #:value #:placeholder #:type #:required #:autocomplete #:autocapitalize #:spellcheck #:input-mode #:disabled #:readonly #:autofocus #:pattern #:min #:max #:step #:list #:help-text #:error #:input-css #:label-css #:input-flex-css

Construct an HTML <input> component with a matching label.

The generated field always carries an id, and the label is bound to it through the for attribute, so clicking the label focuses the input. Pass #:autocomplete to help browsers and password managers fill the field with the right data (e.g. "name", "email", "street-address", "current-password", "one-time-code").

label

The descriptive text string displaying the identity of the field.

name

The name mapping configuration string bound onto form serialization attributes.

id

An optional explicit HTML identifier; defaults to name.

value

The standard default textual configuration payload string.

placeholder

The ambient string text descriptive hint applied when the field boundary is unpopulated.

type

A configuration symbol managing browser input rendering choices (e.g., 'text, 'password).

required

A boolean property indicating if form verification forces evaluation validation.

autocomplete

An optional string forwarding the HTML autocomplete hint to the browser.

autocapitalize

An optional string forwarding the HTML autocapitalize behaviour (e.g. "none", "words").

spellcheck

An optional string ("true" or "false") controlling native spell checking.

input-mode

An optional string suggesting a virtual keyboard layout (e.g. "email", "numeric").

disabled

A boolean that disables the control.

readonly

A boolean that makes the control read-only.

autofocus

A boolean that focuses the control on page load.

pattern

An optional regular expression string enforcing the value format.

min

An optional lower bound for numeric or date inputs.

max

An optional upper bound for numeric or date inputs.

step

An optional numeric increment for numeric inputs.

list

An optional datalist identifier offering autocomplete suggestions.

help-text

Optional helper copy rendered below the field and linked with aria-describedby.

error

Optional validation message rendered below the field with role="alert".

input-css

List of symbols representing layout utility CSS classes for the input field.

label-css

List of symbols representing presentation utility CSS classes for the label field.

input-flex-css

List of symbols regulating structure container box margins and gaps.

(input #:label "Email Address" #:name "email" #:type 'email #:autocomplete "email" #:required #t)
Procedure: interactions-js

Undocumented procedure.

Procedure: kbd content KEY: #:weight #:size #:color #:background #:border-color #:rounding #:shadow #:padding-y #:padding-x #:kbd-classes

Create an inline HTML <kbd> fragment for user keyboard input.

Use kbd for keys the user presses (Ctrl, Enter, Esc). Marking them up lets assistive technology announce them as input rather than as ordinary prose.

content

The key name or inline nodes.

weight

List of symbols representing font weight CSS classes.

size

List of symbols representing text sizing CSS classes.

color

List of symbols representing text color CSS classes.

background

List of symbols representing the key surface background colors.

border-color

List of symbols representing the key border colors.

rounding

List of symbols representing container boundary radius options.

shadow

List of symbols representing the key depth shadow.

padding-y

List of symbols controlling top/bottom padding.

padding-x

List of symbols controlling left/right padding.

kbd-classes

List of structural typography rules for keyboard keys.

(kbd "Ctrl")
Procedure: layer content KEY: #:level #:id #:tag #:classes #:attributes

Place content in one of the framework’s named layers.

Every overlay has to answer the same question – which of us is on top – and the answer stops being obvious the moment a page has a modal, a dropdown and a toast at once. Naming a level reads the framework’s own scale (sticky, dropdown, overlay, modal, popover, toast, tooltip) through a custom property, so a page can raise or lower a whole layer without hunting for a component’s z-index.

content

The children, as a list of nodes.

level

A layer name. Defaults to 'modal.

id

An optional HTML identifier, for a portal target a script fills.

tag

The element to build. Defaults to div.

classes

Extra classes for the element.

attributes

Extra attributes, as pairs of name and value.

(layer (list (drawer ...)) #:level 'modal)
Procedure: layer-root KEY: #:id #:content #:classes

The single mount point a page gives its overlays.

Rendered once, near the end of the body, this is where scripts append a toast or a command palette. In a server-rendered page it is mostly a declaration of intent: overlays rendered by the server can sit here too, so they all share one stacking context and one place to look for them.

id

The identifier scripts use to find the root.

content

Any overlays rendered by the server, as a list of nodes.

classes

Extra classes for the root.

(layer-root)
Procedure: legend content KEY: #:classes

Create an HTML <legend> caption for a fieldset.

content

The legend caption string or inline nodes.

classes

List of symbols representing typography CSS classes.

(legend "Shipping address")
Procedure: light-button content

Create a button pre-configured with a light font weight.

content

The inner content (text string or HTML fragments) of the button.

(light-button "Read More...")
Procedure: line-clamp content KEY: #:lines #:title #:tag #:classes

Clamp text to a number of lines, with an ellipsis.

This is truncate-text under the name the CSS property has, for a page that would rather read line-clamp at the call site. The full text stays in the DOM, so assistive technology and find-in-page still reach it.

content

The text or inline nodes.

lines

How many lines to keep. Defaults to 2.

title

An optional title attribute carrying the untruncated text.

tag

The element to build. Defaults to p.

classes

Extra classes for the text.

(line-clamp "A long summary" #:lines 3)
Procedure: linear-gradient content KEY: #:container-class #:direction #:steps

Create a container styled with a CSS linear gradient background.

content

The inner layout components or text elements inside the container.

container-class

A string containing additional CSS utility classes for the wrapper.

direction

A symbol or string designating the gradient vector direction (e.g., 'right, 'bottom).

steps

A list of color string steps making up the gradient stops.

(linear-gradient "Gradient Text" #:direction 'right #:steps '("#ff0000" "#0000ff"))
Procedure: locale content KEY: #:lang #:dir #:tag #:classes #:attributes

Mark a region’s language and writing direction.

Both attributes are inherited, so wrapping the part of a page that is in another language – or in a right-to-left script – is enough for browsers, screen readers, hyphenation and the CSS logical properties the components are written with. The framework’s own layout primitives use logical spacing, so a split or a drawer mirrors itself when it is told to.

content

The children, as a list of nodes.

lang

A BCP 47 language tag, such as "ar" or "pt-BR".

dir

'ltr, 'rtl or 'auto.

tag

The element to build. Defaults to div.

classes

Extra classes for the element.

attributes

Extra attributes, as pairs of name and value.

(locale (list (p "مرحبا")) #:lang "ar" #:dir 'rtl)
Procedure: mark content KEY: #:weight #:color #:size #:background #:rounding #:padding-x #:mark-classes

Create an inline HTML <mark> fragment highlighting relevant text.

The highlight is a soft yellow wash rather than the browser’s default neon, so it reads as an annotation instead of a warning.

content

The highlighted text string or inline nodes.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

size

List of symbols representing text sizing CSS classes.

background

List of symbols representing the highlight background colors.

rounding

List of symbols representing container boundary radius options.

padding-x

List of symbols controlling left/right padding.

mark-classes

List of additional typography utility classes.

(mark "today")
Procedure: mk-class xs

Return a sorted, space-separated string of unique class names from xs.

Takes a list of symbols xs, removes any duplicates, converts them to strings, sorts them alphabetically, and returns them as a single, space-separated string. This is useful for creating a canonical CSS class attribute value.

If xs is the empty list, an empty string is returned.

xs

A list of symbols.

(mk-class '(p-2 bg-stone-900/75))
Procedure: modal KEY: #:id #:title #:content #:footer #:close-label

Create a modal dialog that opens with no JavaScript.

The modal is revealed by the CSS :target pseudo-class: link to it with an anchor such as (a "Open" #:href "#my-modal") and it appears; follow a link to "#" to dismiss it. The dialog carries the proper role, aria-modal and aria-labelledby attributes so assistive technology announces it, and it keeps working with scripting disabled.

id

The unique element id targeted by the opening link.

title

The dialog title, also used as its accessible name.

content

The dialog body content.

footer

An optional list of action nodes shown at the bottom.

close-label

The accessible label for the close control. Defaults to "Close".

(modal #:id "confirm" #:title "Are you sure?" #:content (list (p "This cannot be undone.")))
Procedure: mono content KEY: #:weight #:color #:size #:mono-classes

Create a structural paragraph element wrapped in a monospace text layout.

content

The inner code text string or inline elements.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

size

List of symbols representing text sizing CSS classes.

mono-classes

List of configuration framework tokens defining a fixed-width layout style.

(mono "$ guix pull")
Procedure: mono-button content

Create a button pre-configured with a monospace font family.

content

The inner content (text string or HTML fragments) of the button.

(mono-button "git commit")
Procedure: multi-select KEY: #:label #:name #:id #:options #:selected #:size #:required #:help-text #:error #: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.

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"))

Create a navigation link with an optional active state.

When active is true, the link also carries aria-current="page" so assistive technology announces the current section. Links never require JavaScript.

content

The link label.

href

The destination URL.

active

A boolean marking this link as the current page.

classes

List of base link CSS classes.

active-classes

List of symbols applied when the link is active.

inactive-classes

List of symbols applied when the link is inactive.

(nav-link "Pricing" #:href "/pricing")
Procedure: nav-rail KEY: #:items #:label #:current #:classes

Create a narrow icon rail for a primary navigation.

Each item is a triple, (icon label href): the icon is what is drawn, and the label is what assistive technology reads and what a mouse reader sees in the browser’s own tooltip. A rail is the right shape for a desktop application with a persistent set of destinations and no room for words; the labels are still there in the markup, which is what keeps it usable.

items

A list of (icon label href) triples.

label

The accessible name of the navigation region.

current

The href of the current page.

classes

Extra classes for the rail.

(nav-rail #:items '((house "Home" "/") (gear "Settings" "/settings")))
Procedure: navbar content KEY: #:brand #:sticky #:navbar-classes

Create a top navigation bar shell.

The bar is a frosted glass surface like every other one in the framework: it is built by glass-panel, which owns the frost (the translucent tint and the backdrop blur), so the tint in navbar-classes is not the bar’s to invent. What the bar owns is its silhouette – it spans the viewport, so it has no rounding, no border and no padding of the panel’s own, and its layout lives in navbar-classes.

content

The navigation content, typically a list of nav-link elements.

brand

An optional brand/logo node shown at the start of the bar.

sticky

A boolean pinning the bar to the top of the viewport. Defaults to true.

navbar-classes

List of symbols representing the bar’s layout and place. A colour utility among them replaces the panel’s tint, so a bar can recolour its own glass.

(navbar (list (nav-link "Home" #:href "/" #:active #t)) #:brand "Hygguile")
Procedure: noscript content KEY: #:classes

Wrap content in an HTML <noscript> fallback element.

The contents are rendered and executed by browsers only when JavaScript is disabled, which makes this component the natural home for no-script notices or alternative navigation. Pair it with the recommended JavaScript-free stylesheet so JavaScript-dependent controls can hide themselves gracefully.

content

The fallback content shown when scripting is unavailable.

classes

List of symbols representing utility CSS classes applied to the element.

(noscript fallback-fragment)
Procedure: notification-card KEY: #:header-classes #:close-icon-on-click #:close-icon-classes #:title-container-classes #:card-classes

Build the frosted card a notification arrives in.

A notification interrupts a page from above it, which is the case glass is for: the card is a glass-panel, so it blurs and tints whatever it covers, and it wears a slightly more opaque tint than the framework’s own so it holds its own over any page.

The title and the body are empty in the markup: the script fills them from the query string with textContent, so a title cannot smuggle markup into the page. The two placeholders it looks for are notification-title and notification-body.

The card arrives and then holds still. It is deliberately quiet – no bounce, no pulse, nothing that keeps moving once it has landed – because a notification is information, and motion at the edge of the eye competes with the page the reader was already looking at. The only thing that changes is whether it is there.

Its silhouette is quiet too: the same hairline border as every other pane of glass rather than a heavy outline, and padding sized so the card is a note passed to the reader rather than a second page competing with theirs.

header-classes

The title’s own classes, on its <h4>.

close-icon-on-click

The script run when the dismiss icon is clicked.

close-icon-classes

The classes of the dismiss icon, which needs its Phosphor icon names.

title-container-classes

The classes of the row holding the title and the dismiss icon.

card-classes

Extra classes for the card itself, appended to the panel’s own.

(notification-card #:header-classes "text-lg font-bold")
Procedure: number-field KEY: #:label #:name #:id #:value #:min #:max #:step #:unit #:placeholder #:required #:help-text #:error #: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.

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: optgroup KEY: #:label #:options #:disabled

Construct an HTML <optgroup>: a heading inside a menu.

A long menu reads as a list of headings rather than a list of rows once its options are grouped, and the group is the browser’s own idea rather than a class of ours – a native dropdown draws its header and indents the rows under it, so there is nothing in the companion stylesheet to go with this.

The label is the group’s whole purpose and the attribute is required by HTML; it is written when given, and omitted rather than written as #f when it is not, so a group with no heading is a group with no heading attribute rather than one claiming to be called #f.

A disabled group takes its rows out of play with it: none of them can be chosen, which is why a menu never states one of them as its selection.

label

The heading shown above the group’s rows.

options

List of option or glyph-option S-expressions making up the group.

disabled

A boolean disabling every row of the group at once.

(optgroup #:label "Europe"
          #:options (list (glyph-option #:value "nl" #:glyph "🇳🇱" #:content "Nederlands")
                          (glyph-option #:value "es" #:glyph "🇪🇸" #:content "Español")))
Procedure: option KEY: #:value #:content #:family #:selected-value #:disabled

Construct an HTML <option> element.

The option is selected when its value names the selected-value, which is how a server-rendered page says which row a dropdown is showing. The two are compared as the text they are written as, so en_US and "en_US" are the same option; comparing them as Scheme values is how a selection goes missing between the server and the browser.

value

The value submitted when this option is chosen.

content

The text shown for the option. Defaults to value.

family

List of symbols representing font family CSS classes for the option.

selected-value

The value the dropdown is currently showing, so that the matching option renders as selected.

disabled

A boolean marking the option as not choosable, which is what a placeholder row such as a bare "Choose one..." wants.

(option #:value "light" #:content "Cozy Light Theme" #:selected-value "dark")
Procedure: overlays-js

Undocumented procedure.

Procedure: p content KEY: #:weight #:color #:family #:leading #:size

Create an HTML paragraph (<p>) component.

content

The text block or inner nodes rendered inside the paragraph boundaries.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

leading

List of symbols representing line-height spacing CSS classes.

size

List of symbols representing text sizing CSS classes.

(p "This framework focuses on modular, cozy web architectures.")
Procedure: pagination KEY: #:current #:total #:base-url #:prev-label #:next-label #:nav-class #:link-class #:active-class

Create page navigation for a paginated result set.

current

The current page number, starting at 1.

total

The total number of pages.

base-url

The URL prefix each page number is appended to. Defaults to "#".

prev-label

The label for the previous-page link.

next-label

The label for the next-page link.

nav-class

List of symbols representing the nav wrapper CSS classes.

link-class

List of symbols representing a page link’s base CSS classes.

active-class

List of symbols highlighting the current page link.

(pagination #:current 2 #:total 5 #:base-url "/blog?page=")
Procedure: parse-sort params KEY: #:columns #:default-direction

Read the requested sort state out of a decoded query.

This is the other half of sortable-table: the component writes the order into the URL, and this reads it back on the following request, so a route only has to hand over the query it decoded. Only the two prefixed names are read, so a sort or a dir belonging to the page’s own query is left to the page.

A key no column declares is reported as no sort at all rather than passed through, so a hand-edited, stale or truncated link falls back to the natural order instead of reaching the query layer. The key that comes back is the column’s own, not the string from the URL, so it can be compared with eq? and handed straight back to sortable-table.

params

The decoded query, an association list such as '(("hygguile-table-sort" . "seats")); symbol names and (name value) pairs are accepted too.

columns

The column descriptors given to sortable-table, or a plain list of sort keys.

default-direction

The direction used when the URL asks for none. Defaults to 'asc.

Returns a two-element list, (key direction), where key is #f when the request asks for the natural order.

(parse-sort '(("hygguile-table-sort" . "seats")
              ("hygguile-table-dir" . "desc"))
            #:columns (list (table-column #:key 'seats #:label "Seats")))
;; => (seats desc)
Procedure: phosphor-icons KEY: #:stylesheet-href

Generate S-expression link tags to pull down standard Phosphor icon asset libraries asynchronously.

Note: If the default value string containing an internal NPM handle is modified, any raw symbol characters must continue to obey parsing constraints.

stylesheet-href

The source URL string pointing directly to the distributed stylesheet manifest location.

(phosphor-icons #:stylesheet-href "https://example.com/assets/icons.css")
Procedure: picture KEY: #:sources #:image #:picture-classes

Create an HTML <picture> element for responsive images and art direction.

The image argument must be the output of img, which acts as the fallback for browsers that do not support picture, modern image formats such as AVIF or WebP, or any of the supplied media queries. Because picture is native HTML, it keeps working with JavaScript disabled.

sources

A list of source elements to try in order.

image

The fallback img node produced by (img ...).

picture-classes

List of symbols representing layout CSS classes for the wrapper.

(picture #:sources (list (source #:type "image/webp" #:srcset "photo.webp"))
         #:image (img #:src "photo.jpg" #:alt "A photo"))
Procedure: popover KEY: #:id #:trigger #:content #:title #:position #:label #:classes

Attach a native popover to a trigger.

The popover is the HTML Popover API: the control carries popovertarget, the panel carries the popover attribute, and the browser puts the panel in the top layer, dismisses it on Escape or an outside click, and does not let it be clipped by an ancestor. That is a lot of behaviour no script of ours has to implement.

Placement is CSS anchor positioning where the browser supports it: the trigger names an anchor, the panel points at it, and the stylesheet places the panel below or above it. Where anchor positioning is not available, the panel falls back to the browser’s own centred popover placement, which is usable even if less precise – and the position class is still there for a page that wants to nudge it.

id

The identifier the trigger’s popovertarget names.

trigger

The button’s label, or a node to put inside the button.

content

The popover body.

title

An optional heading inside the popover.

position

'bottom-start (default), 'bottom, 'top-start or 'top.

label

The accessible name of the panel, when there is no title.

classes

Extra classes for the panel.

(popover #:id "share" #:trigger "Share"
         #:title "Share this page"
         #:content (list (copy-button #:text "https://example.org")))
Procedure: pre content KEY: #:id #:weight #:color #:size #:background #:rounding #:shadow #:width #:leading #:text-wrap #:padding-y #:padding-x #:pre-classes

Create an HTML preformatted multi-line source code container block (<pre>).

content

The raw multi-line string contents to preserve.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

size

List of symbols representing text sizing CSS classes.

background

List of symbols representing code presentation layout surface background cards.

rounding

List of symbols representing card container boundary radius configurations.

shadow

List of symbols representing card container visual shadow definitions.

width

List of symbols tracking macro dimension constraint rules.

leading

List of symbols processing vertical paragraph text baseline spacing layouts.

text-wrap

List of symbols configuring code boundary line wrapping constraints.

padding-y

List of symbols processing top/bottom container padding margins.

padding-x

List of symbols processing side outer margin boundaries padding options.

pre-classes

List of structural code scrolling mechanisms and whitespace behaviors.

id

An optional HTML identifier, or #f for a <pre> nothing has to name.

(pre "(define (hello)\n  (display \"Hi\"))")
Procedure: pretty-code datum

Render datum as the source a reader would write: quoted forms in shorthand, the arguments of a form aligned under its head, and a form that does not fit wrapped at a fixed column width.

datum

The S-expression to render, exactly as the page passes it to the component it shows.

(pretty-code '(button "Save" #:size '(text-sm)))
;; => (button "Save"
;;             #:size
;;             '(text-sm))
Procedure: pretty-quote str

Transform explicit S-expression (quote symbol) fragments within a string into shorthand syntax.

str

The target input string containing raw code snippets or text representations.

(pretty-quote "This is a (quote example) line.")
Procedure: primary-button content KEY: #:loading #:loading-label #:type #:id #:on-click #:extra-classes

The one main action of a page or form.

content

The button’s label or nodes.

loading

A boolean putting the button in its loading state.

loading-label

The spoken label of the loading state.

type

The HTML button type.

id

An optional identifier.

on-click

A JavaScript handler.

extra-classes

Extra layout classes.

(primary-button "Create account")
Procedure: print-version OPT: port

Print the version banner, the license and the homepage of hygguile to PORT.

The three lines are written to be read as they are: the banner first, then the license and the homepage, each on its own line and each naming what it is.

port

The port to print to, (current-output-port) by default.

(print-version)
;; hygguile 2.6.0
;; License: LGPL-3.0-or-later
;; Homepage: https://hygguile.jointhefreeworld.org
Procedure: progress KEY: #:value #:max #:label #:show-value #:progress-classes #:bar-classes

Create a native HTML <progress> bar.

value

The current value. When omitted the bar renders in an indeterminate state.

max

The maximum value. Defaults to 100.

label

An optional text label shown above the bar and used for accessibility.

show-value

A boolean rendering the percentage next to the label.

progress-classes

List of symbols representing the progress bar CSS classes.

bar-classes

List of additional utility CSS classes.

(progress #:value 42 #:label "Uploading" #:show-value #t)
Procedure: radio KEY: #:label #:name #:id #:value #:checked #:required #:disabled #:input-css #:label-css #:container-css

Create a labelled HTML radio button.

Give several radios the same name to form a mutually exclusive group. Radios are native controls, so they keep working with JavaScript disabled.

label

The descriptive text shown next to the radio.

name

The shared form field name identifying the group.

id

An optional explicit HTML identifier; defaults to a value derived from value.

value

The value submitted when this radio is selected.

checked

A boolean rendering the pre-selected state.

required

A boolean making the group mandatory.

disabled

A boolean disabling the control.

input-css

List of symbols styling the radio control.

label-css

List of symbols styling the label text.

container-css

List of symbols styling the wrapping row.

(radio #:label "Small" #:name "size" #:value "s" #:checked #t)
Procedure: range KEY: #:label #:name #:id #:value #:min #:max #:step #:disabled #:input-css

Create a labelled HTML range slider.

label

The descriptive text shown above the slider.

name

The form field name.

id

An optional explicit HTML identifier; defaults to name.

value

The optional initial slider position.

min

The lowest accepted value. Defaults to 0.

max

The highest accepted value. Defaults to 100.

step

The increment between accepted values. Defaults to 1.

disabled

A boolean disabling the control.

input-css

List of symbols styling the range control.

(range #:label "Volume" #:name "volume" #:min 0 #:max 11 #:value 7)
Procedure: ratio-label ratio

Return ratio as the CSS aspect-ratio value it denotes, or #f when the value is not a ratio this component understands.

Accepts 'video (16/9), 'square (1/1), 'photo (4/3), 'wide (21/9), 'golden, a pair such as '(16 . 9), a number, or any string CSS already accepts.

ratio

A symbol, pair, number or string.

(ratio-label 'video)
;; => "16 / 9"

(ratio-label '(4 . 3))
;; => "4 / 3"
Procedure: remember-dark-theme-js

Undocumented procedure.

Procedure: render-if-nempty render-func item

Apply render-func to item if item is neither empty nor false.

Returns an empty list if item evaluates to null or false.

render-func

A single-argument procedure to evaluate the layout with item.

item

The data element or fragment list to inspect.

(render-if-nempty (lambda (i) `(b ,i)) "Bold Text")
Procedure: render-if-nestring expr x

Evaluate and return the result of expr with x if x is a non-empty string.

If x is false or an empty string, returns an empty list.

expr

A single-argument procedure to apply to x.

x

The string value or condition to check.

(render-if-nestring (lambda (val) `((p ,val))) "Hello")
Procedure: row content KEY: #:gap #:shadow #:wrap #:padding-y #:padding-x #:rounding #:background #:items #:row-classes

Create a horizontal row container using flexbox layout.

content

The inner content elements or fragments to align horizontally.

gap

List of symbols for spacing gaps between elements.

shadow

List of symbols for shadow effect CSS classes.

wrap

List of symbols defining wrap properties.

padding-y

List of symbols for vertical padding CSS classes.

padding-x

List of symbols for horizontal padding CSS classes.

rounding

List of symbols for container border-radius CSS classes.

background

List of symbols for container background CSS classes.

items

List of symbols for cross-axis alignment CSS classes.

row-classes

List of base horizontal flex layout CSS classes.

(row '((div "Left Element") (div "Right Element")) #:gap '(gap-4))
Procedure: samp content KEY: #:weight #:color #:size #:samp-classes

Create an inline HTML <samp> fragment for program output.

samp marks text produced by a computer: a command’s reply, a compiler diagnostic, a log line.

content

The sampled output text string or inline nodes.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

size

List of symbols representing text sizing CSS classes.

samp-classes

List of typography rules enforcing monospaced output metrics.

(samp "Build succeeded.")
Procedure: search-field KEY: #:label #:name #:id #:value #:placeholder #:action #:method #:submit-label #:help-text #:error #: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.

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: secondary-button content KEY: #:loading #:loading-label #:type #:id #:on-click #:extra-classes

A supporting action beside a primary one.

content

The button’s label or nodes.

loading

A boolean putting the button in its loading state.

loading-label

The spoken label of the loading state.

type

The HTML button type.

id

An optional identifier.

on-click

A JavaScript handler.

extra-classes

Extra layout classes.

(secondary-button "Cancel")
Procedure: section-nav sections KEY: #:label #:sticky? #:scroll-spy? #:controls #:classes #:list-classes #:link-classes #:active-classes #:inactive-classes

Create a rail of links to the sections of one page.

A page whose sections are stacked one under another is hard to move around in, and a table of contents that scrolls away is no help once a reader is past it. This rail is that list of links, frosted so it holds its own over whatever it passes, and sticky from the small breakpoint up: any section stays one tap away, and each link is an ordinary anchor, so it works with JavaScript disabled and can be bookmarked or shared.

sections

A list of (id . label) pairs, in the order the rail should show them. Each id is the identifier of the section it points at.

label

The accessible name of the rail, read out by assistive technology.

sticky?

Whether the rail follows the page from the small breakpoint up.

scroll-spy?

Whether the rail marks the section a reader is currently in. The marking is done by the framework’s scroll-spy script (a progressive enhancement), and the attribute it needs is written only when the page asks for it. Defaults to true.

controls

A list of nodes drawn at the end of the rail, beside the links: the place for a control that belongs to the page as a whole rather than to any one section, such as the theme toggle, since a rail that follows the page is then also the place the setting stays reachable from. The control is a sibling of the list rather than an item of it, so a screen reader still reads the rail as the list of sections and nothing else. Empty by default, which leaves the bare rail.

classes

List of base CSS classes for the <nav>.

list-classes

List of CSS classes for the wrapping <ul>.

link-classes

List of CSS classes for every link.

active-classes

List of CSS classes applied to the link of the current section.

inactive-classes

List of CSS classes applied to the other links.

(section-nav '((buttons . "Buttons") (forms . "Forms")))
(section-nav '((buttons . "Buttons")) #:controls (ui-mode-button))
Procedure: select KEY: #:options #:on-change #:name #:id #:label #:help-text #:error #:required #:disabled #:family #:select-classes #:label-css #:select-flex-css

Construct a dropdown list interactive selection menu wrapper.

The menu fills its container on phones and, from the small breakpoint up, takes the width it needs and no more: a row such as a flag beside a language name is shown whole, while a label long enough to matter stops at the edge of its container instead of pushing a narrow page sideways. A menu of a fixed width would instead clip the row it is showing, which is what min-content does to a control the browser draws itself.

Which row the menu shows is markup, not script: it is carried by the selected attribute of exactly one option, rendered from the selected-value the options were built with, so the field a reader sees is the field that is submitted. A menu whose rows match nothing – a stale value, or a hand-built list – gets its first choosable row marked, because that is the row the browser would display regardless.

A label is bound to the control through for whenever the menu has a name or an id to bind to, and help-text and error are linked by aria-describedby the same way, so the description of a field reaches assistive technology with it. Give the menu a name (the normal case for a form) and both bindings come for free.

The rows are a flat list, or groups of them: an optgroup entry stands in the list beside the options and holds rows of its own, which is how a long menu is read as a handful of headings instead of one run of rows.

options

A list of option, glyph-option or optgroup S-expressions making up the menu.

on-change

A string JavaScript execution statement evaluated during interaction changes.

name

The element name context configuration string bound onto target HTML form tracking.

id

The unique DOM string selector matching layout identifier tables; defaults to name for binding the label and the description.

label

The text introducing the field, rendered as a <label> bound to the control.

help-text

Optional helper copy rendered below the field and linked with aria-describedby.

error

Optional validation message rendered below the field with role="alert".

required

A boolean making the browser refuse to submit an empty menu.

disabled

A boolean disabling the control.

family

List of symbols representing base structural font choices.

select-classes

List of symbols specifying visual layout styles for the component wrapper card.

label-css

List of symbols representing presentation utility CSS classes for the label.

select-flex-css

List of symbols regulating structure container box margins and gaps.

(select #:label "Plan"
        #:name 'plan
        #:options (list (option #:value "cozy" #:content "Cozy (free)"
                                #:selected-value "cozy")
                        (option #:value "calm" #:content "Calm"))
        #:on-change "console.log('Changed!')")
Procedure: sheet KEY: #:id #:title #:content #:footer #:close-label #:label #:classes

Create a bottom sheet: a drawer anchored to the bottom edge.

This is drawer with #:side 'bottom, named for the surface it makes. On a phone it is the shape a picker or an action list usually takes, and it is still a plain anchor target, so it opens without a script.

id

The identifier the opening link targets.

title

The sheet’s title and accessible name.

content

The sheet body.

footer

An optional list of actions.

close-label

The accessible name of the close control.

label

An optional accessible name overriding the title.

classes

Extra classes for the panel.

(sheet #:id "actions" #:title "Actions" #:content (list (p "...")))
Procedure: show content KEY: #:from #:display #:tag #:classes #:attributes

Show content only from a breakpoint up.

The markup is hidden plus the breakpoint’s display utility, so the hiding is done by the same stylesheet as everything else and the content is not fetched twice. Below the breakpoint the element is not rendered at all (display: none), which is the correct behaviour for a control that only makes sense on a wide screen.

content

The children, as a list of nodes.

from

The breakpoint: 'sm, 'md (default), 'lg, 'xl or '2xl.

display

The display value above the breakpoint, e.g. 'block, 'flex, 'grid or 'inline-block.

tag

The element to build. Defaults to div.

classes

Extra classes for the wrapper.

attributes

Extra attributes, as pairs of name and value.

(show (list (table ...)) #:from 'lg)
Procedure: show-notifications-from-url-js KEY: #:header-classes #:close-icon-on-click #:close-icon-classes #:title-container-classes #:card-classes

Generate a client-side script block that handles rendering notifications from URL parameters.

This macro sets up a DOM listener evaluating parameters matching notify-title and notify-body from the window URL properties. When populated, it puts a notification-card into the layout tray wrapper designated by the element identifier notification-tray, fills its title and body with textContent, and clears the tray again after the notification’s duration.

header-classes

A space-separated string containing CSS layout configuration targeting the card header text elements.

close-icon-on-click

The client-side callback script evaluated whenever a dismiss interaction triggers.

close-icon-classes

A space-separated string containing structural component icon identifiers and layout options.

title-container-classes

A space-separated string containing CSS layout parameters handling the alignment properties of the text boundaries.

card-classes

Extra classes for the notification’s frosted card, appended to the panel’s own.

(show-notifications-from-url-js #:header-classes "text-lg font-bold")
Procedure: sidebar KEY: #:brand #:sections #:footer #:label #:current #:sticky? #:classes

Create a vertical navigation sidebar, collapsing to a plain list on phones.

A sidebar is a page’s own table of contents for an application: a brand at the top, one or more groups of links, and an optional footer (usually a settings link or an account block). It is a real <nav> with a label, each link is an ordinary anchor, and the current page is marked with aria-current. From the large breakpoint up it can stick beside the content; below it, it is simply a list above the page.

brand

An optional node shown at the top.

sections

A list of (heading . items) pairs. Each item is either a (label . href) pair or a node built with sidebar-link.

footer

An optional node shown at the bottom.

label

The accessible name of the navigation region.

current

The href of the current page, used to mark the active link.

sticky?

Whether the sidebar sticks from the large breakpoint up.

classes

Extra classes for the sidebar.

(sidebar #:brand "LucidPlan"
         #:current "/projects"
         #:sections '(("Work" ("Projects" . "/projects")
                               ("Reports" . "/reports"))
                      ("Account" ("Settings" . "/settings"))))

Create one link of a sidebar, with an optional icon and badge.

The link marks the page it is on with aria-current="page", so a screen reader announces the current item the same way the stylesheet paints it.

label

The link text.

href

The destination.

icon

An optional Phosphor icon name shown before the label.

active

Whether this is the current page.

badge

An optional short count or label shown at the end of the row.

classes

Extra classes for the link.

(sidebar-link "Settings" #:href "/settings" #:icon 'gear)
Procedure: skeleton KEY: #:lines #:height #:rounded #:classes

Create a pulsing placeholder shown while content loads.

lines

The number of placeholder rows to render.

height

A height keyword: 'sm, 'md or 'lg.

rounded

A rounding keyword: 'none, 'sm, 'md or 'full.

classes

List of additional utility CSS classes.

(skeleton #:lines 3 #:height 'md)
Procedure: skeleton-avatar KEY: #:size #:classes

A placeholder for an avatar.

size

'sm, 'md or 'lg.

classes

Extra classes.

(skeleton-avatar #:size 'lg)
Procedure: skeleton-card KEY: #:lines #:avatar? #:classes

A placeholder for a card: a header row and a few lines of copy.

lines

How many body lines to draw.

avatar?

Whether the header carries an avatar placeholder.

classes

Extra classes.

(skeleton-card #:lines 2 #:avatar? #t)
Procedure: skeleton-form KEY: #:fields #:classes

A placeholder for a form: one field block per control.

fields

How many field blocks to draw.

classes

Extra classes.

(skeleton-form #:fields 4)
Procedure: skeleton-heading KEY: #:width #:classes

A placeholder for a heading line.

width

A width utility list. Defaults to (w-1/2).

classes

Extra classes.

(skeleton-heading #:width '(w-1/3))
Procedure: skeleton-table KEY: #:rows #:columns #:classes

A placeholder for a data table: a header rule and the requested rows.

rows

How many body rows to draw.

columns

How many cells per row.

classes

Extra classes.

(skeleton-table #:rows 5 #:columns 3)
Procedure: skeleton-text KEY: #:lines #:last-width #:classes

A placeholder for a paragraph, one block per line.

The last line is shorter, which is what makes the block read as text rather than as a table. The whole placeholder is hidden from assistive technology; the loading state is announced once by whatever region owns it.

lines

How many lines to draw.

last-width

The width of the final line. Defaults to (w-2/3).

classes

Extra classes.

(skeleton-text #:lines 4)

Render the first focusable control on a page: a link that jumps past the navigation straight to the content.

A skip link is the cheapest accessibility win there is and the most often forgotten. It sits off-screen until a keyboard reader tabs to it, then appears at the top of the page; following it moves focus to target, which should be the id of the page’s <main>.

target

The fragment the link points at, including the leading #. Defaults to "#main".

label

The link text. Defaults to "Skip to content".

classes

Extra classes for the link.

(skip-link #:target "#main")
Procedure: slide-in content KEY: #:from #:duration #:tag #:classes #:attributes

Slide content in from an edge, or not at all when motion is reduced.

content

The children, as a list of nodes.

from

'up (default), 'down, 'start or 'end.

duration

A motion token. Defaults to 'base.

tag

The element to build. Defaults to div.

classes

Extra classes for the wrapper.

attributes

Extra attributes, as pairs of name and value.

(slide-in (list (card ...)) #:from 'down)
Procedure: sort-control KEY: #:target #:modes #:label #:aria-label #:classes #:label-classes

Create a group of buttons that puts a tray in one of several orders.

The tray is the container whose identifier is target; its items are ordered by the data-* attribute each of them carries under the name of the mode asked for. The ordering is done in the browser by tray.js, which every build already ships, and it is the sort of a handful of rendered cards rather than of a data set: a page that has a real question to ask a server asks it with sortable-table and a URL instead.

A mode no item carries is the tray’s own order – the items return to the order the page wrote them in – so the button that restores it needs no attribute anywhere:

(sort-control #:target "cards"
              #:modes '((page . "Page order")
                        (title . "A to Z")))
target

The identifier of the container to reorder.

modes

A list of (mode . label) pairs, in the order the buttons should appear. Each mode names the data-* attribute to order by, and the first pair is the order the tray opens in.

label

The word shown before the buttons.

aria-label

The accessible name of the group of buttons.

classes

List of CSS classes for the group.

label-classes

List of CSS classes for the label.

Procedure: sort-href KEY: #:path #:params #:drop-params #:sort #:direction

Build the URL asking the server to order a table by sort in direction.

The link a header cell carries is the whole sorting state: the server reads it back on the next request, and a reload or a shared link reproduces the same order without any scripting. The order itself travels in hygguile-table-sort and hygguile-table-dir, prefixed so that a page’s own sort or dir parameter is never mistaken for the table’s state. Every value is URL-encoded, and the parameters named in drop-params are left out, because they describe a position in the order that is being replaced: keeping page=4 while the sort changes would show an arbitrary slice of the new order. Sorting therefore starts at the first page.

An order already present in params is replaced, not repeated, so the whole query such a page received can be handed straight back in without the link contradicting itself. Parameters of the page’s own are carried over untouched, however they are named: a page whose sort means something else keeps it.

With no path the href is a bare query, such as ?hygguile-table-sort=seats&hygguile-table-dir=desc, which the browser resolves against the page’s own path. That is what a table route wants, since one route renders the page and handles the sort.

path

An optional path to prefix the query with, such as "/plans".

params

An association list of other query parameters to carry over, such as '(("filter" . "active")). Symbol names and non-string values are converted.

drop-params

Names removed from params first. Defaults to "page".

sort

The key of the column to order by, as a symbol or a string. #f asks for the natural order instead, which is how a "stop sorting" link is built.

direction

'asc (the default) or 'desc.

(sort-href #:params '(("page" . "4"))
           #:sort 'seats #:direction 'desc)
;; => "?hygguile-table-sort=seats&hygguile-table-dir=desc"
Procedure: sortable-table KEY: #:columns #:rows #:sort #:direction #:path #:params #:drop-params #:default-direction #:caption #:caption-position #:caption-classes #:scroll #:scroll-label #:table-classes #:container-classes #:head-classes #:header-classes #:cell-classes #:row-classes #:empty-message #:empty #:sticky-header? #:selectable? #:select-name #:row-keys #:resizable? #:density #:zebra? #:loading? #:loading-rows #:error #:retry

Create a data table whose column order lives in the URL.

Each sortable header cell is a link to the same page carrying hygguile-table-sort and hygguile-table-dir, and the cell announces the state it is in through aria-sort. Nothing runs in the browser: clicking a header is an ordinary navigation, so the table sorts with JavaScript disabled, a reload reproduces the order, and a shared link opens on the same view. The server reads the order back with parse-sort.

Clicking the column the table is already ordered by reverses it; clicking another starts that column in its own direction. The other query parameters travel along, except those in drop-params, so a filter survives a sort while a page number does not.

columns

A list of table-column descriptors, or of bare sort keys.

rows

A list of rows, each a list of cell contents matching the columns by position. A short row simply leaves its trailing cells empty.

sort

The key of the column currently ordered, or #f for the natural order. A key no column declares is ignored.

direction

The current direction, 'asc (the default) or 'desc.

path

The path the links point at, when it differs from the page that renders them. By default they are bare queries, which keep the current path.

params

Other query parameters to carry over, as for sort-href.

drop-params

Parameters removed when the sort changes. Defaults to "page".

default-direction

The direction a column starts in when it declares none.

caption

An optional caption rendered as the table’s own caption element.

caption-position

A symbol placing that caption 'top (default) or 'bottom.

caption-classes

List of symbols representing caption typography CSS classes.

scroll

A boolean wrapping the table in an accessible horizontally scrollable region.

scroll-label

An optional accessible name for that region; it defaults to a string caption.

table-classes

List of symbols representing the table layout CSS classes.

container-classes

List of symbols representing the scroll container CSS classes.

head-classes

List of symbols representing the header row group CSS classes.

header-classes

List of symbols representing a sortable header cell. The cell’s padding moves to the link, so the link covers the whole cell.

cell-classes

List of symbols representing the data cell CSS classes.

row-classes

List of symbols representing each body row’s CSS classes.

empty-message

What a table with no rows says, spanning every column.

A totals row or any other bespoke footer is the one thing this component leaves out: reach for table with tfoot, and for sort-href plus th #:aria-sort to build the header the same way by hand.

(sortable-table #:columns (list (table-column #:key 'plan #:label "Plan")
                                (table-column #:key 'seats #:label "Seats"
                                              #:numeric #t))
                #:rows (list (list "Cozy" "3"))
                #:sort 'seats #:direction 'desc
                #:caption "Plans")
Procedure: source KEY: #:srcset #:media #:type #:sizes #:width #:height

Create an HTML <source> element for use inside picture.

srcset

The candidate image set string.

media

An optional media query string providing art direction, e.g. "(min-width: 800px)".

type

An optional MIME type string such as "image/avif".

sizes

An optional source-size list string.

width

An optional intrinsic width in pixels.

height

An optional intrinsic height in pixels.

(source #:type "image/webp" #:srcset "hero.webp")
Procedure: spacer KEY: #:axis #:size #:grow? #:classes #:attributes

Insert deliberate empty space, or a flexible gap, between siblings.

A spacer is better than a margin on the thing after it, because it is visible in the markup as a decision rather than looking like an accident of one component’s defaults. In flexible layouts, #:grow? #t is the spacer that pushes what follows to the far end of the row.

axis

'vertical (default), 'horizontal or 'both.

size

A list of sizing utilities, such as (h-8). Ignored when grow? is true.

grow?

Whether the spacer takes the free space on the main axis instead.

classes

Extra classes for the spacer.

attributes

Extra attributes, as pairs of name and value.

(spacer #:size '(h-12))
(cluster (list (brand) (spacer #:grow? #t) (ui-mode-button)))
Procedure: spinner KEY: #:size #:label #:classes

Create an accessible CSS loading spinner.

The spinner animates through CSS only and exposes a screen-reader label through role="status", so it works without JavaScript.

size

A size keyword: 'xs, 'sm, 'md, 'lg or 'xl.

label

The screen-reader status text. Defaults to "Loading...".

classes

List of additional utility CSS classes.

(spinner #:size 'lg #:label "Fetching results")
Procedure: split KEY: #:start #:end #:ratio #:gap #:breakpoint #:tag #:classes #:attributes

Lay out a main pane beside a secondary one, collapsing to one column on narrow screens.

The ratio is the contract: '(1 2) gives the start pane a third and the end pane two thirds from the breakpoint up, and each pane simply takes the whole row below it. That is a sidebar and its content, but also a summary beside a form and a figure beside its caption.

start

The first pane’s nodes.

end

The second pane’s nodes.

ratio

A pair of positive integers, start then end. Defaults to (1 2).

gap

A list of gap utilities. Defaults to (gap-6).

breakpoint

The breakpoint from which the panes sit side by side: 'md or 'lg (the default).

tag

The element to build. Defaults to div.

classes

Extra classes for the wrapper.

attributes

Extra attributes, as pairs of name and value.

(split #:start (list (sidebar ...))
       #:end (list (main ...))
       #:ratio '(1 3))
Procedure: stack content KEY: #:gap #:align #:tag #:classes #:attributes

Stack children vertically with one rhythm between them.

This is the most-used layout in an interface: a heading, a paragraph, a button, all separated by the same gap. Writing the gap once, in the component, is what keeps two stacks on the same page from being four pixels apart in one place and six in another.

content

The children, as a list of nodes.

gap

A list of gap utilities. Defaults to (gap-4).

align

Cross-axis alignment: 'stretch (default), 'start, 'center, 'end or 'baseline.

tag

The element to build. Defaults to div.

classes

Extra classes for the stack itself.

attributes

Extra attributes, as pairs of name and value.

(stack (list (h2 "Account")
             (p "Signed in as Ada.")
             (submit-button "Save"))
       #:gap '(gap-2))
Procedure: stat-card KEY: #:value #:label #:trend #:trend-variant #:classes

Create a compact key-figure block for dashboards and summary panels.

value

The prominent value to display, such as a number or short string.

label

A short descriptive label rendered below the value.

trend

An optional trend string, e.g. "+12%".

trend-variant

A semantic keyword controlling the trend color: 'success, 'danger or 'neutral.

classes

List of additional utility CSS classes.

(stat-card #:value "1,204" #:label "Active users" #:trend "+12%")
Procedure: stepper KEY: #:label #:name #:id #:value #:min #:max #:step #:unit #:help-text #:error #: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.

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: strong content KEY: #:weight #:color #:family #:leading #:size

Create an HTML strong importance (<strong>) text fragment.

content

The inner phrase string or inline elements requiring strong visual weight.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

leading

List of symbols representing line-height spacing CSS classes.

size

List of symbols representing text sizing CSS classes.

(strong "Warning: Destructive action ahead!")
Procedure: sub content KEY: #:weight #:color #:size #:vertical #:sub-classes

Create an inline HTML <sub> subscript fragment.

The natural home for chemical formulae and mathematical indices.

content

The subscript text or inline nodes.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

size

List of symbols representing text sizing CSS classes.

vertical

List of symbols setting the vertical alignment offset.

sub-classes

List of additional typography utility classes.

(p (list "H" (sub "2") "O"))
Procedure: sub-h1 content KEY: #:weight #:color #:family #:style #:size #:leading

Create a lighter, styled secondary <h1> heading wrapper.

content

The inner text string or child elements of the subheading.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

style

List of symbols representing font style layout configuration options (e.g., italic).

size

List of symbols representing text sizing CSS classes.

leading

List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.

(sub-h1 "A deeper dive into the architecture")
Procedure: sub-h2 content KEY: #:weight #:style #:color #:family #:size #:leading

Create a lighter, styled secondary <h2> heading wrapper.

content

The inner text string or child elements of the subheading.

weight

List of symbols representing font weight CSS classes.

style

List of symbols representing font style layout configuration options.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

size

List of symbols representing text sizing CSS classes.

leading

List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.

(sub-h2 "Contextual commentary block")
Procedure: sub-h3 content KEY: #:weight #:style #:color #:family #:size #:leading

Create a lighter, styled secondary <h3> heading wrapper.

content

The inner text string or child elements of the subheading.

weight

List of symbols representing font weight CSS classes.

style

List of symbols representing font style layout configuration options.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

size

List of symbols representing text sizing CSS classes.

leading

List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.

(sub-h3 "Detailed breakdown notes")
Procedure: sub-h4 content KEY: #:weight #:style #:color #:family #:size #:leading

Create a lighter, styled secondary <h4> heading wrapper.

content

The inner text string or child elements of the subheading.

weight

List of symbols representing font weight CSS classes.

style

List of symbols representing font style layout configuration options.

color

List of symbols representing text color CSS classes.

family

List of symbols representing font family CSS classes.

size

List of symbols representing text sizing CSS classes.

leading

List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.

(sub-h4 "Supplemental metadata label")
Procedure: submit-button content

Create a green-themed standard HTML form submission button.

The fill is the dark end of the green ramp with near-white type on the light theme, and the light end with dark type on the dark theme, so the label clears WCAG AA (4.5:1) whichever theme is active.

content

The inner content (text string or HTML fragments) of the button.

(submit-button "Save Preferences")
Procedure: summary content KEY: #:classes

Create an HTML <summary> disclosure caption for details.

content

The summary text or inline nodes.

classes

List of symbols representing typography CSS classes.

(summary "Advanced options")
Procedure: sup content KEY: #:weight #:color #:size #:vertical #:sup-classes

Create an inline HTML <sup> superscript fragment.

Use it for footnote markers, ordinal suffixes and exponents.

content

The superscript text or inline nodes.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

size

List of symbols representing text sizing CSS classes.

vertical

List of symbols setting the vertical alignment offset.

sup-classes

List of additional typography utility classes.

(p (list "Copyright" (sup "©")))
Procedure: switch KEY: #:label #:name #:id #:value #:checked #:disabled

Create a styled on/off switch backed by a native checkbox.

The switch is pure CSS and a real checkbox underneath, so it works without JavaScript. The visual track is styled by .hg-switch.

label

The descriptive text shown next to the switch.

name

The form field name.

id

An optional explicit HTML identifier; defaults to name.

value

The value submitted when the switch is on.

checked

A boolean rendering the switch in the on position.

disabled

A boolean disabling the control.

(switch #:label "Enable notifications" #:name "notifications" #:checked #t)
Procedure: tab KEY: #:id #:label #:content

Describe a single tab for use with tabs.

id

A short, group-unique identifier used to bind the tab, its label and its panel.

label

The visible label shown on the tab button.

content

The panel content fragments.

(tab #:id "billing" #:label "Billing" #:content (list (p "Invoices")))
Procedure: table content KEY: #:caption #:caption-position #:caption-classes #:scroll #:scroll-label #:table-attributes #:container-attributes #:table-classes #:container-classes

Create a semantic HTML <table> for tabular data.

Rows are built from thead, tbody, tfoot, tr, th and td nodes, so every piece of the table keeps its native meaning: browsers, screen readers and crawlers all understand the relationship between headers and cells without any scripting.

Wide tables are a common source of horizontal page overflow on phones. By default the table is therefore wrapped in a scrollable region that carries role="region" and an accessible name, and that is reachable with tabindex, so keyboard users can scroll it too. Pass #:scroll #f when you would rather handle the overflow yourself.

content

The row groups making up the table, typically a thead, a tbody and an optional tfoot.

caption

An optional caption rendered as the table’s own caption element.

caption-position

A symbol placing that caption 'top (default) or 'bottom.

caption-classes

List of symbols representing caption typography CSS classes.

scroll

A boolean wrapping the table in an accessible horizontally scrollable region.

scroll-label

An optional accessible name for that region; it defaults to a string caption.

table-attributes

Extra attributes for the <table> itself, as pairs.

container-attributes

Extra attributes for the scroll region, as pairs.

table-classes

List of symbols representing the table layout CSS classes.

container-classes

List of symbols representing the scroll container CSS classes.

(table (list (thead (list (tr (list (th "Plan" #:scope 'col)
                                   (th "Price" #:scope 'col #:numeric #t)))))
             (tbody (list (tr (list (td "Cozy")
                                    (td "9" #:numeric #t))))))
       #:caption "Plans")
Procedure: table-column KEY: #:label #:key #:numeric #:sortable #:default-direction #:align #:nowrap

Describe one column of a sortable-table.

label

The text shown in the header cell. When omitted, the sort key is used.

key

The sort key this column is ordered by, and the value that travels in the URL as ?hygguile-table-sort=. Symbols are conventional. A column without a key is rendered but not sortable, which is what a column of row actions wants.

numeric

A boolean right-aligning the column and letting its digits line up.

sortable

A boolean; #f renders a plain header cell even when a key is given.

default-direction

The direction the first click on this column asks for, 'asc or 'desc. Defaults to the table’s own #:default-direction.

align

A symbol, one of 'left, 'right or 'center, overriding the numeric default.

nowrap

A boolean preventing this column’s cells from wrapping.

(table-column #:key 'seats #:label "Seats" #:numeric #t)
Procedure: table-js

Undocumented procedure.

Procedure: tabs KEY: #:tab-items #:id #:selected #:tabs-class

Create a JavaScript-free tabbed interface.

Each tab is a hidden radio input paired with a visible label; selecting a label reveals the matching panel purely through CSS (:checked and :has). This means the component remains fully functional with JavaScript disabled and is keyboard accessible, since the underlying controls are native form elements.

tab-items

A list of tab descriptors.

id

An optional group identifier; a unique one is generated when omitted.

selected

The id of the tab selected initially. Defaults to the first tab.

tabs-class

List of symbols representing the wrapper CSS classes.

(tabs #:tab-items (list (tab #:id "a" #:label "A" #:content (list (p "A")))
                       (tab #:id "b" #:label "B" #:content (list (p "B")))))
Procedure: tbody rows KEY: #:classes

Create an HTML <tbody> row group holding the data rows.

rows

A list of tr rows built from td cells.

classes

List of symbols representing the body group CSS classes.

(tbody (list (tr (list (td "Ada Lovelace")))))
Procedure: td content KEY: #:align #:numeric #:nowrap #:colspan #:classes

Create an HTML <td> data cell.

content

The cell text string or inline nodes.

align

A symbol, one of 'left, 'right or 'center. By default a cell inherits the table’s alignment.

numeric

A boolean enabling tabular figures and, unless align says otherwise, right-aligning the cell, so columns of numbers line up digit by digit.

nowrap

A boolean preventing the cell from wrapping.

colspan

A number of columns this cell spans, which is what a totals row and an empty state need.

classes

List of symbols representing the data cell CSS classes.

(td "1 204" #:numeric #t)
Procedure: text-field KEY: #:label #:name #:id #:value #:placeholder #:type #:required #:autocomplete #:input-mode #:pattern #:help-text #:error #: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.

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: textarea KEY: #:label #:name #:id #:value #:placeholder #:rows #:cols #:required #:autocomplete #:maxlength #:spellcheck #:disabled #:readonly #:autofocus #:help-text #:error #:textarea-css #:label-css #:textarea-flex-css

Construct an HTML <textarea> multiline field block.

label

The explicit header string informing users of required field contexts.

name

The unique string structural identifier mapping the entity across standard DOM tables.

id

An optional explicit HTML identifier; defaults to name.

value

The initial text block paragraph payload string nested inside the block.

placeholder

An optional hint string shown while the field is empty.

rows

An optional suggested visible number of text rows.

cols

An optional suggested visible width in average character widths.

required

A field completion mandatory requirement boolean validation gate switch.

autocomplete

An optional string forwarding the HTML autocomplete hint to the browser.

maxlength

An optional maximum accepted character count.

spellcheck

An optional string ("true" or "false") controlling native spell checking.

disabled

A boolean that disables the control.

readonly

A boolean that makes the control read-only.

autofocus

A boolean that focuses the control on page load.

help-text

Optional helper copy rendered below the field and linked with aria-describedby.

error

Optional validation message rendered below the field with role="alert".

textarea-css

List of symbols targeting specific dimensions and borders configuration styles.

label-css

List of symbols handling sizing constraints applied onto the matching label tags.

textarea-flex-css

List of symbols managing outer wrapper responsive card flex constraints.

(textarea #:label "User Bio" #:name "biography" #:value "Born in Scheme...")
Procedure: tfoot rows KEY: #:classes

Create an HTML <tfoot> row group for summaries and totals.

rows

A list of tr rows, usually a single row of totals.

classes

List of symbols representing the footer group CSS classes.

(tfoot (list (tr (list (th "Total" #:scope 'row)
                       (td "42" #:numeric #t)))))
Procedure: th content KEY: #:scope #:align #:numeric #:nowrap #:colspan #:aria-sort #:resizable? #:classes

Create an HTML <th> header cell.

Every header is given an explicit scope by default, which lets assistive technology tie the header to the column or row it describes without guessing.

content

The header text string or inline nodes.

scope

A symbol, one of 'col (default), 'row, 'colgroup or 'rowgroup.

align

A symbol, one of 'left, 'right or 'center. By default a header inherits the table’s alignment.

numeric

A boolean enabling tabular figures and, unless align says otherwise, right-aligning the header.

nowrap

A boolean preventing the header from wrapping.

colspan

A number of columns this cell spans.

aria-sort

A symbol or string from ascending, descending or none. Set by sortable-table to announce the order a sortable column is in; none marks a column that can be sorted but is not.

resizable?

Whether the header cell carries a column-resize handle (an enhancement).

classes

List of symbols representing the header cell CSS classes.

(th "Price" #:align 'right #:numeric #t)
Procedure: thead rows KEY: #:classes

Create an HTML <thead> row group holding the column headers.

rows

A list of tr rows built from th cells.

classes

List of symbols representing the header group CSS classes.

(thead (list (tr (list (th "Name" #:scope 'col)))))
Procedure: time content KEY: #:datetime #:weight #:color #:size #:time-classes

Create an inline HTML <time> fragment for a machine-readable date.

The datetime attribute carries the value in ISO format (2026-10-11, 2026-10-11T09:30) so that browsers, calendars and crawlers can parse it, while the visible text stays human and locale-appropriate. When content is a string and no datetime is given, the content itself is used.

content

The human-readable date or time string.

datetime

An optional ISO-8601 machine-readable value.

weight

List of symbols representing font weight CSS classes.

color

List of symbols representing text color CSS classes.

size

List of symbols representing text sizing CSS classes.

time-classes

List of typography rules, such as tabular figures for aligned dates.

(time "11 October 2026" #:datetime "2026-10-11")
Procedure: time-field KEY: #:label #:name #:id #:value #:min #:max #:step #:required #:help-text #:error #: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.

disabled

Whether the control is disabled.

classes

Classes for the field wrapper.

(time-field #:label "Starts at" #:name "starts-at" #:step 900)
Procedure: timeline items KEY: #:label #:classes

Create an ordered history from timeline-item nodes.

The list is an <ol>, because a timeline is order rather than a set, and the order is what the reader came for.

items

A list of timeline-item nodes.

label

The accessible name of the list.

classes

Extra classes for the wrapper.

(timeline (list (timeline-item #:time "11 Oct" #:title "Deployed")))
Procedure: timeline-item KEY: #:time #:datetime #:title #:body #:icon #:variant #:current?

Create one entry of a timeline.

The marker is decorative, the time is marked up as a real <time> with its machine-readable value, and the entry’s heading says what happened. The current entry carries aria-current="step", which is how a reader learns where in the history the page is.

time

The human-readable time shown beside the marker.

datetime

The ISO value for the <time> element.

title

The entry’s heading.

body

Optional detail below the heading.

icon

An optional Phosphor icon name shown in the marker.

variant

'neutral (default), 'info, 'success, 'warning or 'danger.

current?

Whether this is the entry in progress.

(timeline-item #:time "11 Oct" #:title "Deployed")
Procedure: toast KEY: #:title #:body #:variant #:duration #:dismissible? #:dismiss-label #:id #:classes

Create one toast: a small frosted card that arrives, says one thing, and can leave again.

The toast is a live region of its own – status for a quiet variant and alert for a danger – so a screen reader announces it without the page having to move focus. The dismiss control is a progressive enhancement (js-only); a toast rendered by the server is still readable and still carries its text, and the script that queues toasts is what makes it leave on its own.

Toast markup is also the template toast-tray clones, which is why the title and body carry data-hg-toast-title and data-hg-toast-body: one shape serves both the server-rendered case and the queued one.

title

The toast’s heading.

body

Optional supporting copy.

variant

'info (default), 'success, 'warning or 'danger.

duration

How long the toast stays, in milliseconds. Defaults to 5000; the script reads it from the markup.

dismissible?

Whether the dismiss control is rendered. Defaults to true.

dismiss-label

The accessible name of the dismiss control.

id

An optional identifier.

classes

Extra classes for the card.

(toast #:title "Saved" #:body "Your changes are live." #:variant 'success)
Procedure: toast-js

Undocumented procedure.

Procedure: toast-tray KEY: #:id #:label #:max #:position #:template-id #:classes

Create the live region a page’s toasts arrive in, and its template.

The tray is one polite region, never one region per toast: a screen reader announces an addition once, and the page keeps a single place where its notifications live. The template inside it is a toast with empty slots; the queue script clones it, fills the slots with textContent, appends the clone, and removes it after its duration. Nothing a notification says is ever parsed as markup, so a title cannot smuggle an element into the page.

id

The tray’s identifier.

label

Its accessible name.

max

How many toasts the tray keeps at once, oldest first.

position

'bottom-end (default), 'bottom-start, 'top-end or 'top-start.

template-id

The identifier of the template the script clones.

classes

Extra classes for the tray.

(toast-tray)
Procedure: toggle-dark-theme-js

Undocumented procedure.

Procedure: token-declarations pairs KEY: #:theme

Turn a list of (group name) pairs into CSS declaration pairs.

The result can be handed to mk-style, which is how a component passes a token to the browser as an inline custom property:

(mk-style (token-declarations '((space 6) (radius 2xl))))
;; => "--hg-space-6: 1.5rem; --hg-radius-2xl: 1.12rem"
pairs

A list of two-element lists, (group name).

theme

The theme read by a colour token. Defaults to 'light.

Procedure: token-name group name KEY: #:theme

Return the CSS custom property a token is exposed as, or #f for an unknown group.

A colour’s name carries its theme as a suffix (--hg-color-ink-dark), because both values have to exist in the same stylesheet.

(token-name 'space 4)
;; => "--hg-space-4"
Procedure: token-value group name KEY: #:theme

Return the value of the token called name in group, as a string.

Groups are 'space, 'radius, 'elevation, 'motion, 'z, 'color and 'density. A colour has a value per theme, so theme picks one; every other group ignores it.

Unknown groups and names signal an error rather than returning a silent #f, because a token that is misspelt in a component is a bug that should surface on the first render, not a value that quietly disappears.

group

One of the symbol names above.

name

The token’s key, e.g. 4 for spacing or 'lg for a radius.

theme

'light (the default) or 'dark, read by colour tokens only.

(token-value 'space 4)
;; => "1rem"

(token-value 'color 'ink #:theme 'dark)
;; => "#ededee"
Procedure: tooltip content KEY: #:text #:id #:position #:classes

Attach an accessible, JavaScript-free tooltip to inline content.

The tooltip is shown on both hover and keyboard focus through CSS, and its text is exposed twice, each copy for the reader it is for. Sighted hover is served by the data-hg-tooltip attribute the generated ::after bubble reads; assistive technology is served by a real text node the trigger describes itself with, because generated content is not reliably part of the exposed accessibility tree. The text node is visually hidden rather than also drawn, so a hover still produces one bubble and one reading, not two.

content

The trigger content the tooltip is attached to.

text

The tooltip text.

id

A stable HTML identifier for the hidden description, or #f for a generated one. Give one when the page has to name it.

position

Where the tooltip appears: 'top (default), 'bottom, 'left or 'right.

classes

List of additional utility CSS classes.

(tooltip (code "CARGO_HOME") #:text "Where Cargo stores downloaded crates")
Procedure: tr cells KEY: #:classes

Create an HTML table row (<tr>) from a list of cells.

cells

A list of th or td cells.

classes

List of symbols representing optional row CSS classes.

(tr (list (td "Cozy") (td "9" #:numeric #t)))
Procedure: tray-js

Undocumented procedure.

Procedure: tree-item label KEY: #:href #:children #:open #:icon #:classes

Create one node of a tree-view.

A node with children is a native <details>, so the tree opens and closes with no script; a leaf is a link. That combination is the smallest thing that is both a hierarchy and a set of destinations, and it stays usable when the browser cannot run a tree widget’s keyboard model.

label

The node’s text.

href

The destination for a leaf node.

children

Child tree-item nodes, for a branch.

open

Whether a branch starts open.

icon

An optional Phosphor icon name.

classes

Extra classes for the node.

(tree-item "Docs" #:children (list (tree-item "Install" #:href "/install")))
Procedure: tree-view KEY: #:items #:label #:classes

Create a navigable hierarchy from tree-item nodes.

items

A list of tree-item nodes.

label

The accessible name of the tree’s navigation region.

classes

Extra classes for the wrapper.

(tree-view #:items (list (tree-item "Docs"
                                    #:children (list (tree-item "Install"
                                                                #:href "/install")))))
Procedure: trigger-notification-function-js KEY: #:title #:body #:duration #:body-font-family

Generate a JavaScript IIFE string that pushes notification parameters into the URL query string and reloads the window.

title

The fallback string title of the notification.

body

The core informational content string of the notification.

duration

The visibility timeline limit in milliseconds before the notification is cleared.

body-font-family

The fallback typography class string applied to the notification’s description field.

(trigger-notification-function-js #:title "Alert" #:body "System update complete.")
Procedure: truncate-text content KEY: #:lines #:title #:tag #:classes

Cut text off after a number of lines, with an ellipsis.

One line uses Olive’s truncate utility; several lines use its line-clamp-* utilities, which is a real, painted clamp rather than a character count, so it follows the rendered width of the type. The full text stays in the DOM, so assistive technology and find-in-page still reach it; a title is available for the mouse reader, but it is off by default because it would make a second copy of the text for anyone who can already see it.

content

The text or inline nodes.

lines

How many lines to keep. Defaults to 1.

title

An optional title attribute carrying the untruncated text.

tag

The element to build. Defaults to p.

classes

Extra classes for the text.

(truncate-text "A very long description indeed" #:lines 2)
Procedure: ui-mode-button KEY: #:icon #:content #:type #:background #:hover-background #:border-size #:border-color #:shadow #:size #:active #:family #:weight #:color #:on-click #:rounding #:icon-css #:extra-classes #:icon-text-container-classes

Create a utility button pre-wired to toggle UI dark/light mode themes.

icon

A symbol identifying the default icon type.

content

The description text displayed within the button layout.

type

A symbol representing the HTML button type.

background

List of symbols for background CSS classes.

hover-background

List of symbols for hover background CSS classes.

border-size

List of symbols for border width CSS classes.

border-color

List of symbols for border color CSS classes.

shadow

List of symbols for shadow CSS classes.

size

List of symbols for text sizing CSS classes.

active

List of symbols for active state transformation CSS classes.

family

List of symbols for font family CSS classes.

weight

List of symbols for font weight CSS classes.

color

List of symbols for text color CSS classes.

on-click

The global JavaScript function invocation string triggered to process the theme change.

rounding

List of symbols for border radius CSS classes.

icon-css

List of symbols targeting icon coloring and sizing classes.

extra-classes

List of utility layout CSS classes.

icon-text-container-classes

List of symbols applied to the inner layout wrapper surrounding the icon and content.

(ui-mode-button #:content "Switch Theme")
Procedure: var content KEY: #:weight #:style #:color #:size #:family #:var-classes

Create an inline HTML <var> fragment for a variable or placeholder.

A serif italic var stands out from the surrounding sans-serif text, which is exactly what you want for a name the reader is expected to substitute.

content

The variable name or placeholder text string.

weight

List of symbols representing font weight CSS classes.

style

List of symbols representing text style transformations.

color

List of symbols representing text color CSS classes.

size

List of symbols representing text sizing CSS classes.

family

List of symbols representing font family CSS classes.

var-classes

List of additional typography utility classes.

(var "username")
Procedure: visually-hidden content KEY: #:tag #:focusable? #:classes

Hide content visually while leaving it available to assistive technology.

This is the framework’s ‘sr-only’ class with a name, so a page does not have to remember which utility it is. Use it for the label of an icon-only control, a section heading that would repeat what is already visible, or an instruction that only makes sense to a screen reader.

Pass #:focusable? #t for the rarer case of content that should be hidden until it takes focus – a skip link, or a control that appears when tabbed to. That one needs to be an element that can hold focus, so the default tag is a <span> and callers pass #'a or #'button as appropriate.

content

The text or nodes to hide.

tag

The element to build. Defaults to span.

focusable?

Whether the content becomes visible when focused. Defaults to false.

classes

Extra classes for the wrapper.

(visually-hidden "Loading search results")
(visually-hidden "Skip to content" #:tag 'a #:focusable? #t)
Procedure: with-footer KEY: #:main #:footer

Wrap a main content fragment and a footer fragment within a full-screen vertical flex container.

main

S-expression representing the main content layout.

footer

S-expression representing the footer layout.

(with-footer #:main '((p "Hello World")) #:footer '((footer "Standard Footer")))
Procedure: wizard KEY: #:steps #:current #:label #:orientation #:numbered? #:classes

Create the ordered steps of a multi-step form or a checkout.

The list is a real <ol>, because the order is the point, and the step the reader is on carries aria-current="step". Steps before it are marked complete, steps after it are marked upcoming, and every step that has an href is a link so a reader can go back. Nothing here needs a script: the server knows which step it rendered, and that is the state the page shows.

steps

A list of steps, each a label string or a (label . href) pair.

current

The 1-based index of the current step, or its label.

label

The accessible name of the navigation region.

orientation

'horizontal (default) or 'vertical.

numbered?

Whether each step shows its number (or a check when complete).

classes

Extra classes for the wrapper.

(wizard #:steps '(("Cart" . "/cart") ("Address") ("Payment"))
        #:current 2)

1.30.2 Variables

Variable: doctype-html
"<!DOCTYPE html>"
Variable: hygguile-homepage
"https://hygguile.jointhefreeworld.org"
Variable: hygguile-license
"LGPL-3.0-or-later"
Variable: hygguile-version
"2.7.3"
Variable: notification-tray
((div (@@ (id notification-tray) (role status) (aria-live polite) (aria-atomic "true") (class "fixed right-8 top-8 z-32")) ""))

Next: (hygguile js), Previous: (hygguile embed js), Up: API