Next: (hygguile js), Previous: (hygguile embed js), Up: API
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.
The text label or component displayed inside the interactive anchor boundary.
List of symbols representing font weight CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing base link text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing interactive state hover coloring CSS rules.
List of base text formatting utility attributes.
Optional explicit destination target path URL string.
(a "https://gnu.org" #:extra-classes '(underline))
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.
The abbreviation text, e.g. "HTTP".
The expansion, e.g. "HyperText Transfer Protocol".
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing text decoration classes.
List of additional typography utility classes.
(abbr "HTTP" #:title "HyperText Transfer Protocol")
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.
A list of accordion-item or details nodes.
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")))))
Create a single accordion row backed by a native details element.
The always-visible row title.
The collapsible row body.
A boolean expanding the row by default.
(accordion-item #:summary "How do I install it?" #:content (list (p "Use Guix.")))
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.
An optional short, bold heading for the alert.
The alert message text or inline nodes.
A semantic keyword: 'info (default), 'success,
'warning, 'danger or 'neutral.
An optional ARIA role override. Defaults to alert for danger and
status for every other variant.
An optional Phosphor icon name overriding the variant default.
List of symbols representing container CSS classes.
(alert #:title "Heads up" #:body "Your session expires soon." #:variant 'warning)
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.
The children, as a list of nodes.
The shape: 'video (default), 'square, 'photo,
'wide, 'golden, a pair, a number, or a CSS string.
A list of rounding utilities. Defaults to (rounded-xl).
Whether the box hides anything that outgrows it. Defaults to true.
The element to build. Defaults to div.
Extra classes for the box.
Extra attributes, as pairs of name and value.
(aspect-ratio (list (img #:src "hero.jpg" #:alt ""))
#:ratio 'video)
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.
The optional image source URL.
The alternative text describing the depicted person.
Fallback initials shown when no image source is supplied.
A size keyword: 'xs, 'sm, 'md, 'lg or
'xl.
A shape keyword: 'circle (default), 'rounded or
'square.
An optional boolean adding a subtle border ring around the avatar.
List of additional utility CSS classes.
(avatar #:src "me.png" #:alt "Josep" #:size 'lg #:ring #t)
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.
A list of avatar nodes.
How many avatars to show before the overflow badge. Defaults to
4.
The size of the overflow badge’s text. Defaults to 'md.
The real number of people, when avatars is a truncated list. Defaults to the length of avatars.
An optional accessible name for the group.
Extra classes for the group.
(avatar-group (list (avatar #:src "a.png" #:alt "Ada")
(avatar #:src "b.png" #:alt "Grace")))
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.
The label text or inline nodes.
A semantic keyword: 'neutral, 'info, 'success,
'warning or 'danger.
A size keyword: 'sm, 'md or 'lg.
(badge "New" #:variant 'success)
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.
The inline content.
Extra classes.
(p (list "Signed in as " (bdi "@ada")))
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.
The inline content.
'ltr or 'rtl. Defaults to 'rtl.
Extra classes.
(bdo "abc" #:dir 'rtl)
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.
An ordered list of link or text nodes, from root to current page.
The string shown between items. Defaults to "/".
List of symbols representing the nav wrapper CSS classes.
List of symbols representing the list CSS classes.
(breadcrumbs #:items (list (a "Home" #:href "/") (a "Docs" #:href "/docs") (span "API")))
Create a standard HTML button element.
The inner content (text string or HTML fragments) of the button.
An optional HTML identifier string.
A symbol representing the HTML button type (e.g., 'button,
'submit).
A boolean indicating whether the button should be hidden.
A JavaScript handler string triggered on click events.
A boolean putting the button in its loading state: a spinner appears,
the control is disabled, and aria-busy says why.
The spoken label of the loading state. Defaults to "Loading".
List of symbols for background CSS classes.
List of symbols for hover background CSS classes.
List of symbols for border width CSS classes.
List of symbols for border color CSS classes.
List of symbols for container shadow CSS classes.
List of symbols for text color CSS classes.
List of symbols for text sizing CSS classes.
List of symbols for active state transformation CSS classes.
List of symbols for font family CSS classes.
List of symbols for font weight CSS classes.
List of symbols for border radius CSS classes.
List of utility formatting layout classes.
(button "Click Me" #:id "btn-primary" #:on-click "alert('Hello!')")
(button "Saving" #:loading #t)
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.
The caption text string or inline nodes.
A symbol placing the caption above ('top, the default) or below
('bottom) the table, purely through the caption-side
property.
List of symbols representing typography CSS classes.
(caption "Quarterly revenue")
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.
The inner content or section fragments of the card.
List of symbols representing container CSS classes.
(card (list (card-header (h3 "Title")) (card-body (p "Body"))))
Create the main body section of a card.
The body content fragments.
List of symbols representing body CSS classes.
(card-body (p "Everything you need to know."))
Create muted descriptive copy inside a card.
The description text or inline nodes.
List of symbols representing typography CSS classes.
(card-description "Updated 3 minutes ago.")
Create the footer section of a card, typically holding actions.
The footer content fragments.
List of symbols representing footer CSS classes.
(card-footer (list (submit-button "Save")))
Create the header section of a card.
The header content fragments.
List of symbols representing header CSS classes.
(card-header (h3 "Recent activity"))
Create a card heading.
The heading text or inline nodes.
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.
The unique HTML string identifier for the inner carousel list element.
List of symbols representing CSS classes for the <ul> element.
List of symbols specifying the width CSS classes for each carousel item.
List of symbols specifying the height CSS classes for each carousel item.
List of symbols representing CSS classes for each item wrapper
<li>.
List of symbols representing CSS classes for the wrapper <div> of
the content.
List of symbols representing CSS classes for the outer wrapper
<div>.
List of symbols representing base CSS classes for the previous/next buttons.
List of symbols representing additional CSS classes for the left (previous) button.
List of symbols representing additional CSS classes for the right (next) button.
Fallback copy shown inside a <noscript> element when JavaScript
is disabled.
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.
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.
The children, as a list of nodes.
A list of max-width utilities. Defaults to (max-w-prose).
A list of horizontal padding utilities. Defaults to (px-4).
'horizontal (default) or 'both.
The element to build. Defaults to div.
Extra classes for the wrapper.
Extra attributes, as pairs of name and value.
(center (list (h1 "Sign in") (form ...)) #:max-width '(max-w-128) #:axis 'both)
Create a labelled HTML checkbox input.
Native checkboxes work without JavaScript and are keyboard accessible.
The descriptive text shown next to the checkbox.
The form field name.
An optional explicit HTML identifier; defaults to name.
The value submitted when the box is checked.
A boolean rendering the pre-checked state.
A boolean making the checkbox mandatory.
A boolean disabling the control.
Optional helper copy linked through aria-describedby.
Optional validation message rendered with role="alert".
List of symbols styling the checkbox control.
List of symbols styling the label text.
List of symbols styling the wrapping row.
(checkbox #:label "I accept the terms" #:name "terms" #:required #t)
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.
The chip’s label.
'neutral (default), 'info, 'success,
'warning or 'danger.
'sm, 'md or 'lg.
An optional destination, turning the chip into a link.
An optional Phosphor icon name.
An optional URL that removes the chip.
The accessible name of the remove control.
Extra classes for the chip.
(chip "Scheme" #:variant 'info) (chip "Draft" #:remove-href "/posts/1?remove=draft")
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.
The current value, or #f for an indeterminate ring.
The maximum value. Defaults to 100.
'xs, 'sm, 'md, 'lg or 'xl.
An accessible name for the ring.
Whether the percentage is written in the middle. Defaults to false.
The ring’s stroke width in the SVG’s own units. Defaults to 4.
Extra classes for the wrapper.
(circular-progress #:value 68 #:label "Importing" #:show-value #t)
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.
The children, as a list of nodes.
A list of gap utilities. Defaults to (gap-2).
Cross-axis alignment. Defaults to 'center.
Main-axis distribution: 'start (default), 'center,
'end or 'between.
The element to build. Defaults to div.
Extra classes for the cluster itself.
Extra attributes, as pairs of name and value.
(cluster (list (badge "new") (badge "beta" #:variant 'warning)))
Create an inline HTML <code> fragment.
The code instruction text string.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing inline code surface container background colors.
List of symbols representing container boundary radius options.
List of symbols representing structural layout presentation depth drops.
List of symbols controlling top/bottom baseline cell padding spaces.
List of symbols controlling left/right horizontal boundary alignment padding.
List of font typography rules enforcing monospaced code metrics.
(code "(car list-expression)")
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.
The S-expression to show and to render, e.g. '(button "Save").
An already-rendered fragment to show as the specimen, or #f to
render form.
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.
The accessible name of the control that unfolds the source.
The text of the button that copies the source.
The title of the notification a copy raises.
List of CSS classes for the card.
List of CSS classes for the row holding the specimen and the fold control.
List of CSS classes for the panel the source lives in.
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.
List of CSS classes for the fold control.
(code-sample '(button "Save"
#:on-click "save()"))
Undocumented procedure.
Create a vertical column container using flexbox layout.
The inner content elements or fragments to stack vertically.
List of symbols for spacing gaps between elements.
List of symbols for shadow effect CSS classes.
List of symbols defining wrap properties.
List of symbols for vertical padding CSS classes.
List of symbols for horizontal padding CSS classes.
List of symbols for container border-radius CSS classes.
List of symbols for container background CSS classes.
List of symbols for cross-axis alignment CSS classes.
List of base vertical flex layout CSS classes.
(col '((div "Top Item") (div "Bottom Item")) #:background '(bg-transparent))
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.
The always-visible caption.
The collapsible body, as a list of nodes.
Whether it starts open.
Extra classes for the <details>.
(collapse #:summary "Advanced" #:content (list (input #:label "Pattern" #:name "p")))
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.
The palette colour to show, named the way a class names it:
apple-green-500.
The text on the tile, or #f for the colour’s own name.
The accessible name of the tile, or #f for a sentence naming the
colour and what a click does.
List of CSS classes for the tile itself.
List of CSS classes for the frosted caption inside it.
(color-swatch #:color 'apple-green-500)
Render a free-text field with a list of suggestions.
The suggestion list is a native <datalist>: the browser draws it
in its own way, filters it as the reader types, and leaves the field
free to accept a value that is not on the list. That is the correct
behaviour for a combobox, and none of it needs a script. A page that
needs a fixed set of choices wants select instead; a page that
needs several values wants multi-select.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
A list of strings or (value . label) pairs.
The initial value.
A hint shown while the field is empty.
Whether the field is required.
Optional helper copy.
Optional error message.
Whether several values may be entered (the datalist is unchanged; the
field is a normal text input and this only sets multiple).
Whether the control is disabled.
Classes for the field wrapper.
(combobox #:label "Colour" #:name "colour"
#:options '("amber" "asparagus" "jeans-blue"))
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.
The command’s visible text.
Where the command goes.
An optional keyboard shortcut or category shown at the end of the row.
Optional extra text for the filter to match.
(command-item "Open settings" #:href "/settings" #:hint "G S")
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.
The identifier the opening link targets.
The accessible name of the dialog and its list.
The input’s placeholder. Defaults to "Type a command…".
A list of command-item nodes.
What the palette says when the filter matches nothing.
The accessible name of the backdrop’s close control.
Extra classes for the panel.
(command-palette #:id "commands"
#:commands (list (command-item "Home" #:href "/")))
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.
The value to copy.
The accessible label, shown as text only when show-text? is true.
The accessible label after a successful copy. Defaults to
"Copied".
Whether the label is visible beside the icon. Defaults to false.
Extra classes for the button.
(copy-button #:text "(use-modules (hygguile hygguile))")
Undocumented procedure.
Generate S-expression tags to optimize and load external web typography families asynchronously.
A list of S-expression initialization link tags specifying network domains to warm up.
A list of configuration strings selecting families and weights to include from the external repository API.
A string directive identifying font rendering behaviors during file downloads.
A destructive action: the colour is a warning, and the label says what it does.
The button’s label or nodes.
A boolean putting the button in its loading state.
The spoken label of the loading state.
The HTML button type.
An optional identifier.
A JavaScript handler.
Extra layout classes.
(danger-button "Delete project")
Create a red-themed HTML form submission button meant for destructive actions.
The inner content (text string or HTML fragments) of the button.
(danger-submit-button "Delete Repository")
Render a date field backed by the browser’s own date control.
The value travels in ISO form (2026-10-11), which is the only
form a <input type="date"> accepts and the only form a server
should have to parse. The browser draws a calendar in the reader’s own
locale, so the page never has to ship a date-picker script or guess at a
format.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
The initial value, in ISO form.
The earliest accepted date, in ISO form.
The latest accepted date, in ISO form.
Whether the field is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
Classes for the field wrapper.
(date-field #:label "Starts" #:name "start" #:value "2026-10-11")
Render a from/to date pair as one field.
The pair is a <fieldset> with a <legend>, because that is
what a group of related controls is, and each half keeps its own label
and its own id. The names are derived from name, so a server
reads start=...&end=... without the page inventing a second
convention.
Both halves are native date inputs, which means the range is usable with scripting off – a page that wants the second date to follow the first can add that behaviour on top, but it is not required for the field to work.
The group’s legend, e.g. "Stay".
The shared name; the halves become <name>-start and
<name>-end.
The initial start date, in ISO form.
The initial end date, in ISO form.
The first half’s label. Defaults to "From".
The second half’s label. Defaults to "To".
The earliest accepted date for both halves.
The latest accepted date for both halves.
Whether both halves are required.
Optional helper copy for the group.
Optional error message for the group.
Whether both halves are disabled.
Classes for the fieldset.
(date-range-field #:label "Stay" #:name "stay"
#:start "2026-10-11" #:end "2026-10-14")
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.
The detailed explanation text string or fragment.
List of symbols for layout and indentation CSS classes.
List of symbols for font weight CSS classes.
List of symbols for font family CSS classes.
List of symbols for text color CSS classes.
List of symbols for text sizing CSS classes.
(dd "An implementation of the Scheme programming language.")
Undocumented procedure.
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.
The density the server rendered the page in. Defaults to
'comfortable.
The accessible label of the control.
Extra classes for the button.
(density-toggle #:current 'comfortable)
Return the scale of a density mode as a string, defaulting to
comfortable for a mode the table does not name.
'comfortable or 'compact.
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.
A class name for the <style> element itself.
(design-tokens-css)
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.
The collapsible body content.
The summary caption rendered in the always-visible header. Accepts a
string or a summary node.
A boolean expanding the disclosure by default.
List of symbols representing the wrapper CSS classes.
List of symbols representing the collapsible body CSS classes.
(details (list (p "Hidden details")) #:summary "Show more")
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.
A CSS selector matching the elements to open or close.
The label of the button that opens them all.
The label of the button that closes them all.
List of CSS classes for the pair.
(disclosure-controls #:selector "details.hg-expandable-card")
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.
The inner term (<dt>) and description (<dd>) elements.
List of symbols for layout and style CSS classes.
(dl '((dt "Framework") (dd "Hygguile")))
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.
The identifier the opening link targets.
The dialog’s title, also its accessible name.
The drawer body.
An optional list of actions.
'right (default), 'left or 'bottom.
The accessible name of the close control.
An optional accessible name overriding the title.
Extra classes for the panel.
(drawer #:id "filters" #:title "Filters"
#:content (list (checkbox #:label "In stock" #:name "stock")))
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.
The item’s text.
The destination, for a navigation item.
A JavaScript handler, for an item that acts in place.
Whether the item is destructive.
Whether the item is disabled.
Extra classes for the item.
(dropdown-item "Duplicate" #:on-click "duplicate()")
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.
The trigger’s visible text.
A list of dropdown-item nodes.
'bottom-start (default), 'bottom-end, 'top-start or
'top-end.
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.
Extra classes for the menu wrapper.
(dropdown-menu #:label "Actions"
#:items (list (dropdown-item "Rename" #:href "/rename")
(dropdown-item "Delete" #:danger? #t)))
Render a file field as a drop zone.
This is file-field with #:dropzone? #t and multi-file
selection on by default. It is still a native file input under a label,
so the picker, the accept filter and the upload work with scripting off;
the drop-zone treatment and the list of chosen names are the
enhancement.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
A native accept list.
Whether several files may be chosen. Defaults to true.
Whether a file is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
The text inside the drop zone.
Classes for the field wrapper.
(dropzone #:label "Attachments" #:name "files" #:accept "application/pdf")
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.
The term text string or fragment.
List of symbols for default structural spacing CSS classes.
List of symbols for font weight CSS classes.
List of symbols for font family CSS classes.
List of symbols for font style CSS classes (empty by default).
List of symbols for text color CSS classes.
List of symbols for text sizing CSS classes.
(dt "Guile Scheme" #:weight '(font-bold))
Create an HTML emphasis (<em>) layout block.
The inner text content or target inline nodes.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing line-height spacing CSS classes.
List of symbols representing text element style transformations.
List of symbols representing text sizing CSS classes.
(em "Please notice this crucial contextual exception.")
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>.
The Phosphor icon name. Defaults to 'tray.
The heading that names the state.
Optional explanatory copy.
An optional node – usually a link or a button – offering the next step.
Extra classes for the wrapper.
(empty-state #:title "No projects yet"
#:body "Create one to get started."
#:action (primary-button "New project"))
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.
The heading. Defaults to a generic failure sentence.
The explanation. Defaults to a generic one.
An optional retry node, usually a link back to the same URL.
The text of the default retry link, used when no retry node is given.
Optional technical detail, folded away in a disclosure.
Extra classes for the wrapper.
(error-state #:retry (a "Try again" #:href "/reports"))
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.
The nodes revealed while the card is open.
An optional HTML identifier, placed on the summary, for a link to land on.
The card’s title, rendered as a real heading inside the summary.
An optional Phosphor icon name shown before the title, e.g.
'cards.
An optional short label shown as a badge beside the title, such as the category a card belongs to.
Whether the card starts open.
Whether the card spans the whole row of a grid it is placed in.
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")).
List of additional container CSS classes.
List of CSS classes for the header, which is the disclosure control.
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")))
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.
The children, as a list of nodes.
A motion token: 'fast, 'base (default), 'slow or
'slower.
An optional delay token or CSS time.
The element to build. Defaults to div.
Extra classes for the wrapper.
Extra attributes, as pairs of name and value.
(fade-in (list (alert ...)))
Return the attribute pairs a control should carry for wiring.
Splice them into the control’s attribute list:
`((input (@ ,@(field-control-attributes wiring)
(type "email"))))
The result carries the shared id, the name, the
description and error links, required and aria-invalid
when they apply, and nothing else – a control’s own type, value and
classes are its own.
Return the aria-describedby value for the control, or #f.
Return the identifier of the field’s error message.
Return the identifier of the field’s helper copy.
Return the identifier the control and its label share.
Return the identifiers and attributes one control shares with its field shell.
The wiring is an association list rather than an opaque object, so it
can be inspected, logged and passed between procedures. Read it through
the accessors below, or hand it to field-control-attributes to
get the attribute pairs a control should carry.
When name and id are both absent, a unique id is generated, so a field can be rendered without a form name (a filters bar, say) and still have a label bound to it.
The form field name.
An explicit identifier; defaults to name.
Optional helper copy, which gets an id and joins the description.
Optional error message, which gets an id and joins the description.
Whether the control is required.
(field-wiring #:name "email" #:help-text "We never share it.")
Create an HTML <fieldset> grouping related form controls.
The list of form controls or fragments contained by the group.
An optional caption rendered as the fieldset legend.
List of symbols representing container CSS classes.
(fieldset (list (input #:label "City" #:name "city")) #:legend "Address")
Create an HTML <figcaption> caption element.
The caption text string or inline nodes.
List of symbols representing typography CSS classes.
(figcaption "Figure 1: the hygguile logo")
Create an HTML <figure> element with an optional caption.
The figure body, typically an img or picture node.
An optional caption rendered as a figcaption below the body.
List of symbols representing caption typography CSS classes.
A symbol controlling alignment, one of 'start, 'center or
'end.
List of symbols representing the wrapper layout CSS classes.
(figure (img #:src "logo.png" #:alt "The logo") #:caption "Figure 1")
Render a file picker, optionally as a drop zone.
The control is a native <input type="file">, so the browser’s own
picker, its filter for accept, its multi-file handling and its
multipart/form-data upload all work without a script. The
visible box is a <label> bound to the input, which means clicking
it opens the picker and a keyboard reader reaches the input itself.
With dropzone? the box is bigger, says it accepts dropped files, and carries the attributes a small script uses to list the chosen names. Dropping is an enhancement: with scripting off the label still opens the picker, and the filename is still shown by the browser.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
A native accept list, e.g. "image/*,.pdf".
Whether several files may be chosen.
An optional capture hint for a camera on a phone.
Whether a file is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
Render the larger drop-zone treatment. Defaults to false.
The text inside the drop zone.
Classes for the field wrapper.
(file-field #:label "Avatar" #:name "avatar" #:accept "image/*") (dropzone #:label "Attachments" #:name "files" #:multiple #t)
Construct an outer fly-out navigation menu shell.
The accessibility text label string assigned to the <nav>
component.
List of symbols representing CSS classes for the structural <nav>
wrapper.
List of symbols representing CSS classes for the top-level container
<ul>.
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")))
Undocumented procedure.
Construct a single leaf item within a fly-out menu.
The text description, link, or component fragment inside the item container.
List of symbols representing CSS classes for the wrapping <li>
item.
(fly-out-item "Documentation" #:li-class '(px-2 py-2))
Undocumented procedure.
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.
List of symbols representing CSS classes for the wrapper <li>
component.
List of symbols representing CSS classes for the trigger <button>
element.
List of symbols representing the dropdown’s own shape and layout. A colour utility among them replaces the panel’s tint.
S-expression content or fragments rendered inside the dropdown trigger button.
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.
The inner content or layout fragments of the footer.
List of symbols for background CSS classes.
List of symbols for shadow effect CSS classes.
List of utility formatting and layout CSS classes.
(footer "© 2026 Hygguile Framework")
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.
The controls and fieldsets that make up the form.
The URL the browser navigates to on submission.
A symbol or string, 'post by default; use 'get for
searches.
An optional encoding symbol such as 'multipart/form-data,
required for file uploads.
An optional browsing context string, e.g. "_blank".
An optional HTML identifier.
An optional form name string.
An optional string ("on" or "off") for the whole form.
A boolean switching off native validation, for forms validated by hand.
An optional accessible name when no visible heading describes the form.
An optional label for a submit button appended after the fields.
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")
Create an accessible inline form validation message.
The message is announced by assistive technology through
role="alert".
The error message string or inline nodes.
List of symbols representing typography CSS classes.
(form-error "Please provide a valid email address.")
Render one labelled, described, validated field shell around a control.
The shell owns the four things every field needs and every hand-written
field eventually gets wrong: the label is bound to the control’s id, the
helper copy and the error are linked through aria-describedby,
the error is announced with role="alert", and the required state
is both visible (a marker) and spoken (a visually hidden word plus
aria-required on the control).
control is a procedure of one argument, the wiring, and returns the control’s SXML – a node or a list of nodes. A control that renders more than one element (a select and its tags, an input and a dropzone) returns them all and the shell places them in order.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
Optional helper copy, rendered below the control.
Optional error message, rendered above the help text and announced.
Whether the field is required. Renders a visible marker and a spoken one.
Render the label visually hidden but still available as the control’s name.
A procedure of one argument (the wiring) returning the control’s nodes.
Classes for the field’s wrapper.
Classes for the label.
Classes for the helper copy.
Classes for the error message.
(form-field #:label "Email"
#:name "email"
#:required #t
#:help-text "We never share it."
#:control
(lambda (w)
`((input (@ ,@(field-control-attributes w)
(type "email")
(class ,(mk-class '(p-3 rounded-xl))))))))
Undocumented procedure.
Retrieve the short Git commit SHA or execute a custom version control command.
The shell command string to execute, defaulting to the command that fetches the 7-character short commit hash.
(get-short-commit-sha)
A quiet action that only shows its surface on hover or focus.
The button’s label or nodes.
A boolean putting the button in its loading state.
The spoken label of the loading state.
The HTML button type.
An optional identifier.
A JavaScript handler.
Extra layout classes.
(ghost-button "Skip for now")
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.
The panel’s contents.
A list of glass-wash descriptors, or the empty list (the default)
for a plain frosted panel.
A boolean; #t lets the washes drift.
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.
The element the surface is built from. Defaults to div; a bar
wants header and a menu wants ul.
Extra attributes for the surface, as pairs of name and value.
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.
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.
List of symbols representing the frosted tint. Defaults to
(bg-stone-100/75 dark:bg-onyx-900/75).
List of symbols representing the corner rounding. Defaults to
(rounded-3xl).
List of symbols representing the border, or the empty list for none. Defaults to a two-pixel hairline in the stone ramp.
List of symbols representing the shadow, or the empty list for none.
Defaults to (shadow-lg).
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.
List of symbols representing the padding classes. Defaults to
(p-6 sm:p-8).
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.
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))
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.
List of symbols: the disc’s size, position and colour.
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.
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.
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))
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.
The word as the prose writes it.
Where the term is documented, or #f for a term that is only text.
What the term stands for, or #f for a term with nothing to spell
out.
Where the note appears: 'top (default), 'bottom,
'left or 'right.
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")
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.
The value submitted when this option is chosen.
The character or emoji introducing the row, e.g. "🇳🇱".
The text of the row. Defaults to value.
List of symbols representing font family CSS classes for the option.
The value the dropdown is currently showing.
A boolean marking the option as not choosable.
(glyph-option #:value "nl" #:glyph "🇳🇱" #:content "Nederlands")
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.
The children, as a list of nodes.
The number of columns from the medium breakpoint up. Defaults to
2.
A list of gap utilities. Defaults to (gap-4).
An optional CSS length, such as "16rem", switching the grid to
intrinsic sizing instead of breakpoints.
Whether the grid may backfill holes left by items spanning several tracks.
The element to build. Defaults to div.
Extra classes for the grid itself.
Extra attributes, as pairs of name and value.
(grid (list (card ...) (card ...) (card ...)) #:columns 3) (grid (list (card ...) (card ...)) #:min-column "18rem")
Create an HTML <h1> heading.
The inner text string or child elements of the heading.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing text sizing CSS classes.
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))
Create an HTML <h2> heading.
The inner text string or child elements of the heading.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.
(h2 "Section Subtitle" #:weight '(font-semibold))
Create an HTML <h3> heading.
The inner text string or child elements of the heading.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.
(h3 "Subsection Heading")
Create an HTML <h4> heading.
The inner text string or child elements of the heading.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.
(h4 "Minor Subsection Title")
Create a small helper paragraph providing extra field guidance.
The helper copy string or inline nodes.
List of symbols representing typography CSS classes.
(help-text "We will never share your email.")
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.
The children, as a list of nodes.
The breakpoint: 'sm, 'md (default), 'lg, 'xl
or '2xl.
The element to build. Defaults to div.
Extra classes for the wrapper.
Extra attributes, as pairs of name and value.
(hide (list (toolbar ...)) #:from 'md)
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.
The label or node the card is attached to.
An optional heading inside the card.
The card body.
'bottom-start (default), 'bottom, 'top-start or
'top.
The accessible name of the card.
Extra classes for the card.
(hover-card #:trigger "@ada" #:title "Ada Lovelace"
#:content (list (p "Wrote the first algorithm.")))
Create an HTML horizontal rule (<hr>) element.
List of symbols representing margin CSS classes.
List of symbols representing border width CSS classes.
List of symbols representing border color CSS classes.
(hr #:margin '(my-4) #:color '(border-redwood-500))
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)
Assemble a list of recommended UI resource fragments, including standard web typography assets and component script blocks.
An S-expression block managing custom font loading configurations,
defaulting to the result of (custom-fonts).
(hygguile-recommended-resources)
Return the one-line version banner for hygguile, as ‘maak version’ prints it.
Construct an inline HTML icon element using Phosphor formatting.
A symbol designating the core identifier name of the Phosphor icon asset
(e.g., 'star, 'gear).
A configuration symbol defining line weight thickness variants. Accepts
'bold, 'light, or defaults to 'regular.
List of symbols representing additional layout formatting rules or sizing CSS classes.
(icon #:type 'heart #:weight 'bold #:icon-css '(text-redwood-500 text-2xl))
Create a button containing both an icon and text.
An optional HTML identifier string.
A symbol identifying the type of icon to render.
The text string or components positioned next to the icon.
A boolean indicating whether the button should be hidden.
A JavaScript handler string triggered on click events.
A symbol representing the HTML button type.
List of symbols for background CSS classes.
List of symbols for hover background CSS classes.
List of symbols for border width CSS classes.
List of symbols for border color CSS classes.
List of symbols for shadow CSS classes.
List of symbols for text sizing CSS classes.
List of symbols for font family CSS classes.
List of symbols for font weight CSS classes.
List of symbols for text color CSS classes.
List of symbols for border radius CSS classes.
List of symbols for active state transformation CSS classes.
List of symbols targeting icon coloring and sizing classes.
List of utility layout CSS 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()")
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.
The Phosphor icon name.
The accessible name.
An optional identifier.
The HTML button type.
A JavaScript handler.
A boolean putting the button in its loading state.
The spoken label of the loading state.
Background classes.
Hover background classes.
Text and icon colour classes.
Border colour classes.
Icon sizing and colour classes.
Extra layout classes.
(icon-only-button #:icon 'gear #:label "Settings")
Create a responsive, evenly spaced gallery grid for a list of image nodes.
A list of image or figure nodes to lay out.
The number of columns to request on wide viewports.
List of symbols representing gap CSS classes.
(image-gallery (list (img #:src "a.png" #:alt "A")
(img #:src "b.png" #:alt "B")))
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.
The image source URL string.
The alternative text. Use an empty string for purely decorative images.
An optional advisory title string.
An optional responsive candidate set string, e.g. "a.png 1x,
a@2x.png 2x".
An optional source-size list describing layout widths to the browser.
An optional intrinsic width in pixels, helping avoid layout shift.
An optional intrinsic height in pixels, helping avoid layout shift.
A symbol controlling native lazy loading, either 'lazy or
'eager.
A symbol requesting 'async or 'sync image decoding.
An optional string such as "high" for above-the-fold hero images.
An optional referrer policy string.
An optional CORS setting string such as "anonymous".
An optional aspect ratio class value, e.g. 'square or
"16/9".
A symbol mapping to an object-* fit class: 'cover,
'contain, 'fill or 'none.
List of symbols representing border radius CSS classes.
List of symbols representing shadow CSS classes.
List of symbols representing base image CSS classes.
List of additional utility CSS classes.
(img #:src "avatar.png" #:alt "Portrait" #:width 320 #:height 320)
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").
The descriptive text string displaying the identity of the field.
The name mapping configuration string bound onto form serialization attributes.
An optional explicit HTML identifier; defaults to name.
The standard default textual configuration payload string.
The ambient string text descriptive hint applied when the field boundary is unpopulated.
A configuration symbol managing browser input rendering choices (e.g.,
'text, 'password).
A boolean property indicating if form verification forces evaluation validation.
An optional string forwarding the HTML autocomplete hint to the
browser.
An optional string forwarding the HTML autocapitalize behaviour
(e.g. "none", "words").
An optional string ("true" or "false") controlling native
spell checking.
An optional string suggesting a virtual keyboard layout (e.g.
"email", "numeric").
A boolean that disables the control.
A boolean that makes the control read-only.
A boolean that focuses the control on page load.
An optional regular expression string enforcing the value format.
An optional lower bound for numeric or date inputs.
An optional upper bound for numeric or date inputs.
An optional numeric increment for numeric inputs.
An optional datalist identifier offering autocomplete suggestions.
Optional helper copy rendered below the field and linked with
aria-describedby.
Optional validation message rendered below the field with
role="alert".
List of symbols representing layout utility CSS classes for the input field.
List of symbols representing presentation utility CSS classes for the label field.
List of symbols regulating structure container box margins and gaps.
(input #:label "Email Address" #:name "email" #:type 'email #:autocomplete "email" #:required #t)
Undocumented procedure.
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.
The key name or inline nodes.
List of symbols representing font weight CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing the key surface background colors.
List of symbols representing the key border colors.
List of symbols representing container boundary radius options.
List of symbols representing the key depth shadow.
List of symbols controlling top/bottom padding.
List of symbols controlling left/right padding.
List of structural typography rules for keyboard keys.
(kbd "Ctrl")
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.
The children, as a list of nodes.
A layer name. Defaults to 'modal.
An optional HTML identifier, for a portal target a script fills.
The element to build. Defaults to div.
Extra classes for the element.
Extra attributes, as pairs of name and value.
(layer (list (drawer ...)) #:level 'modal)
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.
The identifier scripts use to find the root.
Any overlays rendered by the server, as a list of nodes.
Extra classes for the root.
(layer-root)
Create an HTML <legend> caption for a fieldset.
The legend caption string or inline nodes.
List of symbols representing typography CSS classes.
(legend "Shipping address")
Create a button pre-configured with a light font weight.
The inner content (text string or HTML fragments) of the button.
(light-button "Read More...")
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.
The text or inline nodes.
How many lines to keep. Defaults to 2.
An optional title attribute carrying the untruncated text.
The element to build. Defaults to p.
Extra classes for the text.
(line-clamp "A long summary" #:lines 3)
Create a container styled with a CSS linear gradient background.
The inner layout components or text elements inside the container.
A string containing additional CSS utility classes for the wrapper.
A symbol or string designating the gradient vector direction (e.g.,
'right, 'bottom).
A list of color string steps making up the gradient stops.
(linear-gradient "Gradient Text" #:direction 'right #:steps '("#ff0000" "#0000ff"))
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.
The children, as a list of nodes.
A BCP 47 language tag, such as "ar" or "pt-BR".
'ltr, 'rtl or 'auto.
The element to build. Defaults to div.
Extra classes for the element.
Extra attributes, as pairs of name and value.
(locale (list (p "مرŘبا")) #:lang "ar" #:dir 'rtl)
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.
The highlighted text string or inline nodes.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing the highlight background colors.
List of symbols representing container boundary radius options.
List of symbols controlling left/right padding.
List of additional typography utility classes.
(mark "today")
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.
A list of symbols.
(mk-class '(p-2 bg-stone-900/75))
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.
The unique element id targeted by the opening link.
The dialog title, also used as its accessible name.
The dialog body content.
An optional list of action nodes shown at the bottom.
The accessible label for the close control. Defaults to "Close".
(modal #:id "confirm" #:title "Are you sure?" #:content (list (p "This cannot be undone.")))
Create a structural paragraph element wrapped in a monospace text layout.
The inner code text string or inline elements.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing text sizing CSS classes.
List of configuration framework tokens defining a fixed-width layout style.
(mono "$ guix pull")
Create a button pre-configured with a monospace font family.
The inner content (text string or HTML fragments) of the button.
(mono-button "git commit")
Render a multi-value choice control with the chosen values shown as tags.
The control is a native <select multiple>, which is the only
multi-select that works without a script and is announced correctly by
assistive technology. The tags above it are a *second view* of the same
selection, not a replacement: they show what is chosen without opening
the menu, and, when remove-href is given, each tag is a link that
removes that value – a plain navigation, so removal works with
scripting off too.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
A list of strings or (value . label) pairs.
The values that start selected.
How many rows the menu shows. Defaults to the number of options, capped at 8.
Whether at least one value is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
A procedure of one argument (a selected value) returning the URL that
removes it, or #f for tags that are not removable.
The accessible label template for a removal link. Defaults to
"Remove".
Classes for the field wrapper.
(multi-select #:label "Topics" #:name "topics"
#:options '((a . "Accessibility") (s . "SSR"))
#:selected '("ssr"))
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.
The link label.
The destination URL.
A boolean marking this link as the current page.
List of base link CSS classes.
List of symbols applied when the link is active.
List of symbols applied when the link is inactive.
(nav-link "Pricing" #:href "/pricing")
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.
A list of (icon label href) triples.
The accessible name of the navigation region.
The href of the current page.
Extra classes for the rail.
(nav-rail #:items '((house "Home" "/") (gear "Settings" "/settings")))
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.
The navigation content, typically a list of nav-link elements.
An optional brand/logo node shown at the start of the bar.
A boolean pinning the bar to the top of the viewport. Defaults to true.
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")
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.
The fallback content shown when scripting is unavailable.
List of symbols representing utility CSS classes applied to the element.
(noscript fallback-fragment)
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.
The title’s own classes, on its <h4>.
The script run when the dismiss icon is clicked.
The classes of the dismiss icon, which needs its Phosphor icon names.
The classes of the row holding the title and the dismiss icon.
Extra classes for the card itself, appended to the panel’s own.
(notification-card #:header-classes "text-lg font-bold")
Render a numeric field with an optional unit and optional stepper buttons.
The control is a native <input type="number">: the browser’s own
spinner, number keyboard on touch, and native validation all come for
free. With stepper? the field also gets a pair of buttons that
step the value by one step; those are a progressive enhancement
(they carry js-only), so with scripting off the native input is
still the whole control rather than a broken one.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
The initial value.
The lowest accepted value.
The highest accepted value.
The increment between accepted values.
An optional unit shown beside the field, e.g. "kg".
A hint shown while the field is empty.
Whether the field is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
Add the decrement/increment buttons. Defaults to false.
Classes for the field wrapper.
Classes for the input itself.
(number-field #:label "Seats" #:name "seats" #:min 1 #:max 12 #:stepper? #t)
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.
The heading shown above the group’s rows.
List of option or glyph-option S-expressions making up the group.
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")))
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.
The value submitted when this option is chosen.
The text shown for the option. Defaults to value.
List of symbols representing font family CSS classes for the option.
The value the dropdown is currently showing, so that the matching option renders as selected.
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")
Undocumented procedure.
Create an HTML paragraph (<p>) component.
The text block or inner nodes rendered inside the paragraph boundaries.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing line-height spacing CSS classes.
List of symbols representing text sizing CSS classes.
(p "This framework focuses on modular, cozy web architectures.")
Create page navigation for a paginated result set.
The current page number, starting at 1.
The total number of pages.
The URL prefix each page number is appended to. Defaults to "#".
The label for the previous-page link.
The label for the next-page link.
List of symbols representing the nav wrapper CSS classes.
List of symbols representing a page link’s base CSS classes.
List of symbols highlighting the current page link.
(pagination #:current 2 #:total 5 #:base-url "/blog?page=")
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.
The decoded query, an association list such as
'(("hygguile-table-sort" . "seats")); symbol names and
(name value) pairs are accepted too.
The column descriptors given to sortable-table, or a plain list
of sort keys.
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)
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.
The source URL string pointing directly to the distributed stylesheet manifest location.
(phosphor-icons #:stylesheet-href "https://example.com/assets/icons.css")
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.
A list of source elements to try in order.
The fallback img node produced by (img ...).
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"))
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.
The identifier the trigger’s popovertarget names.
The button’s label, or a node to put inside the button.
The popover body.
An optional heading inside the popover.
'bottom-start (default), 'bottom, 'top-start or
'top.
The accessible name of the panel, when there is no title.
Extra classes for the panel.
(popover #:id "share" #:trigger "Share"
#:title "Share this page"
#:content (list (copy-button #:text "https://example.org")))
Create an HTML preformatted multi-line source code container block
(<pre>).
The raw multi-line string contents to preserve.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing code presentation layout surface background cards.
List of symbols representing card container boundary radius configurations.
List of symbols representing card container visual shadow definitions.
List of symbols tracking macro dimension constraint rules.
List of symbols processing vertical paragraph text baseline spacing layouts.
List of symbols configuring code boundary line wrapping constraints.
List of symbols processing top/bottom container padding margins.
List of symbols processing side outer margin boundaries padding options.
List of structural code scrolling mechanisms and whitespace behaviors.
An optional HTML identifier, or #f for a <pre> nothing has
to name.
(pre "(define (hello)\n (display \"Hi\"))")
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.
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))
Transform explicit S-expression (quote symbol) fragments within a
string into shorthand syntax.
The target input string containing raw code snippets or text representations.
(pretty-quote "This is a (quote example) line.")
The one main action of a page or form.
The button’s label or nodes.
A boolean putting the button in its loading state.
The spoken label of the loading state.
The HTML button type.
An optional identifier.
A JavaScript handler.
Extra layout classes.
(primary-button "Create account")
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.
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
Create a native HTML <progress> bar.
The current value. When omitted the bar renders in an indeterminate state.
The maximum value. Defaults to 100.
An optional text label shown above the bar and used for accessibility.
A boolean rendering the percentage next to the label.
List of symbols representing the progress bar CSS classes.
List of additional utility CSS classes.
(progress #:value 42 #:label "Uploading" #:show-value #t)
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.
The descriptive text shown next to the radio.
The shared form field name identifying the group.
An optional explicit HTML identifier; defaults to a value derived from value.
The value submitted when this radio is selected.
A boolean rendering the pre-selected state.
A boolean making the group mandatory.
A boolean disabling the control.
List of symbols styling the radio control.
List of symbols styling the label text.
List of symbols styling the wrapping row.
(radio #:label "Small" #:name "size" #:value "s" #:checked #t)
Create a labelled HTML range slider.
The descriptive text shown above the slider.
The form field name.
An optional explicit HTML identifier; defaults to name.
The optional initial slider position.
The lowest accepted value. Defaults to 0.
The highest accepted value. Defaults to 100.
The increment between accepted values. Defaults to 1.
A boolean disabling the control.
List of symbols styling the range control.
(range #:label "Volume" #:name "volume" #:min 0 #:max 11 #:value 7)
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.
A symbol, pair, number or string.
(ratio-label 'video) ;; => "16 / 9" (ratio-label '(4 . 3)) ;; => "4 / 3"
Undocumented procedure.
Apply render-func to item if item is neither empty nor false.
Returns an empty list if item evaluates to null or false.
A single-argument procedure to evaluate the layout with item.
The data element or fragment list to inspect.
(render-if-nempty (lambda (i) `(b ,i)) "Bold Text")
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.
A single-argument procedure to apply to x.
The string value or condition to check.
(render-if-nestring (lambda (val) `((p ,val))) "Hello")
Create a horizontal row container using flexbox layout.
The inner content elements or fragments to align horizontally.
List of symbols for spacing gaps between elements.
List of symbols for shadow effect CSS classes.
List of symbols defining wrap properties.
List of symbols for vertical padding CSS classes.
List of symbols for horizontal padding CSS classes.
List of symbols for container border-radius CSS classes.
List of symbols for container background CSS classes.
List of symbols for cross-axis alignment CSS classes.
List of base horizontal flex layout CSS classes.
(row '((div "Left Element") (div "Right Element")) #:gap '(gap-4))
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.
The sampled output text string or inline nodes.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing text sizing CSS classes.
List of typography rules enforcing monospaced output metrics.
(samp "Build succeeded.")
Render a search control as a real search landmark.
The form carries role="search", so assistive technology can jump
straight to it, and the field is a native <input type="search">:
pressing Enter submits, the browser offers its own clear control, and
nothing depends on a script. The submit button is a real submit button,
so the form still works when it is the only way to send the query.
The field’s name. Defaults to "Search".
The query parameter name.
An explicit identifier; defaults to name.
The current query, so a results page shows what was searched for.
A hint shown while the field is empty.
The URL the search posts to.
'get (the default) or 'post.
The submit button’s label, or #f for a field that submits on
Enter only.
Optional helper copy.
Optional error message.
Whether a query is required.
Hide the label visually while keeping it as the field’s name. Defaults to true, which is what a compact search bar wants.
Classes for the search form.
(search-field #:name "q" #:action "/search" #:value "cozy")
A supporting action beside a primary one.
The button’s label or nodes.
A boolean putting the button in its loading state.
The spoken label of the loading state.
The HTML button type.
An optional identifier.
A JavaScript handler.
Extra layout classes.
(secondary-button "Cancel")
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.
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.
The accessible name of the rail, read out by assistive technology.
Whether the rail follows the page from the small breakpoint up.
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.
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.
List of base CSS classes for the <nav>.
List of CSS classes for the wrapping <ul>.
List of CSS classes for every link.
List of CSS classes applied to the link of the current section.
List of CSS classes applied to the other links.
(section-nav '((buttons . "Buttons") (forms . "Forms"))) (section-nav '((buttons . "Buttons")) #:controls (ui-mode-button))
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.
A list of option, glyph-option or optgroup S-expressions making up the menu.
A string JavaScript execution statement evaluated during interaction changes.
The element name context configuration string bound onto target HTML form tracking.
The unique DOM string selector matching layout identifier tables; defaults to name for binding the label and the description.
The text introducing the field, rendered as a <label> bound to
the control.
Optional helper copy rendered below the field and linked with
aria-describedby.
Optional validation message rendered below the field with
role="alert".
A boolean making the browser refuse to submit an empty menu.
A boolean disabling the control.
List of symbols representing base structural font choices.
List of symbols specifying visual layout styles for the component wrapper card.
List of symbols representing presentation utility CSS classes for the label.
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!')")
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.
The identifier the opening link targets.
The sheet’s title and accessible name.
The sheet body.
An optional list of actions.
The accessible name of the close control.
An optional accessible name overriding the title.
Extra classes for the panel.
(sheet #:id "actions" #:title "Actions" #:content (list (p "...")))
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.
The children, as a list of nodes.
The breakpoint: 'sm, 'md (default), 'lg, 'xl
or '2xl.
The display value above the breakpoint, e.g. 'block,
'flex, 'grid or 'inline-block.
The element to build. Defaults to div.
Extra classes for the wrapper.
Extra attributes, as pairs of name and value.
(show (list (table ...)) #:from 'lg)
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.
A space-separated string containing CSS layout configuration targeting the card header text elements.
The client-side callback script evaluated whenever a dismiss interaction triggers.
A space-separated string containing structural component icon identifiers and layout options.
A space-separated string containing CSS layout parameters handling the alignment properties of the text boundaries.
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")
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.
An optional node shown at the top.
A list of (heading . items) pairs. Each item is either a
(label . href) pair or a node built with sidebar-link.
An optional node shown at the bottom.
The accessible name of the navigation region.
The href of the current page, used to mark the active link.
Whether the sidebar sticks from the large breakpoint up.
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.
The link text.
The destination.
An optional Phosphor icon name shown before the label.
Whether this is the current page.
An optional short count or label shown at the end of the row.
Extra classes for the link.
(sidebar-link "Settings" #:href "/settings" #:icon 'gear)
Create a pulsing placeholder shown while content loads.
The number of placeholder rows to render.
A height keyword: 'sm, 'md or 'lg.
A rounding keyword: 'none, 'sm, 'md or
'full.
List of additional utility CSS classes.
(skeleton #:lines 3 #:height 'md)
A placeholder for an avatar.
'sm, 'md or 'lg.
Extra classes.
(skeleton-avatar #:size 'lg)
A placeholder for a card: a header row and a few lines of copy.
How many body lines to draw.
Whether the header carries an avatar placeholder.
Extra classes.
(skeleton-card #:lines 2 #:avatar? #t)
A placeholder for a form: one field block per control.
How many field blocks to draw.
Extra classes.
(skeleton-form #:fields 4)
A placeholder for a heading line.
A width utility list. Defaults to (w-1/2).
Extra classes.
(skeleton-heading #:width '(w-1/3))
A placeholder for a data table: a header rule and the requested rows.
How many body rows to draw.
How many cells per row.
Extra classes.
(skeleton-table #:rows 5 #:columns 3)
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.
How many lines to draw.
The width of the final line. Defaults to (w-2/3).
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>.
The fragment the link points at, including the leading #.
Defaults to "#main".
The link text. Defaults to "Skip to content".
Extra classes for the link.
(skip-link #:target "#main")
Slide content in from an edge, or not at all when motion is reduced.
The children, as a list of nodes.
'up (default), 'down, 'start or 'end.
A motion token. Defaults to 'base.
The element to build. Defaults to div.
Extra classes for the wrapper.
Extra attributes, as pairs of name and value.
(slide-in (list (card ...)) #:from 'down)
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")))
The identifier of the container to reorder.
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.
The word shown before the buttons.
The accessible name of the group of buttons.
List of CSS classes for the group.
List of CSS classes for the label.
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.
An optional path to prefix the query with, such as "/plans".
An association list of other query parameters to carry over, such as
'(("filter" . "active")). Symbol names and non-string values
are converted.
Names removed from params first. Defaults to "page".
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.
'asc (the default) or 'desc.
(sort-href #:params '(("page" . "4"))
#:sort 'seats #:direction 'desc)
;; => "?hygguile-table-sort=seats&hygguile-table-dir=desc"
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.
A list of table-column descriptors, or of bare sort keys.
A list of rows, each a list of cell contents matching the columns by position. A short row simply leaves its trailing cells empty.
The key of the column currently ordered, or #f for the natural
order. A key no column declares is ignored.
The current direction, 'asc (the default) or 'desc.
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.
Other query parameters to carry over, as for sort-href.
Parameters removed when the sort changes. Defaults to "page".
The direction a column starts in when it declares none.
An optional caption rendered as the table’s own caption element.
A symbol placing that caption 'top (default) or 'bottom.
List of symbols representing caption typography CSS classes.
A boolean wrapping the table in an accessible horizontally scrollable region.
An optional accessible name for that region; it defaults to a string caption.
List of symbols representing the table layout CSS classes.
List of symbols representing the scroll container CSS classes.
List of symbols representing the header row group CSS classes.
List of symbols representing a sortable header cell. The cell’s padding moves to the link, so the link covers the whole cell.
List of symbols representing the data cell CSS classes.
List of symbols representing each body row’s CSS classes.
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")
Create an HTML <source> element for use inside picture.
The candidate image set string.
An optional media query string providing art direction, e.g.
"(min-width: 800px)".
An optional MIME type string such as "image/avif".
An optional source-size list string.
An optional intrinsic width in pixels.
An optional intrinsic height in pixels.
(source #:type "image/webp" #:srcset "hero.webp")
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.
'vertical (default), 'horizontal or 'both.
A list of sizing utilities, such as (h-8). Ignored when
grow? is true.
Whether the spacer takes the free space on the main axis instead.
Extra classes for the spacer.
Extra attributes, as pairs of name and value.
(spacer #:size '(h-12)) (cluster (list (brand) (spacer #:grow? #t) (ui-mode-button)))
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.
A size keyword: 'xs, 'sm, 'md, 'lg or
'xl.
The screen-reader status text. Defaults to "Loading...".
List of additional utility CSS classes.
(spinner #:size 'lg #:label "Fetching results")
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.
The first pane’s nodes.
The second pane’s nodes.
A pair of positive integers, start then end. Defaults to (1 2).
A list of gap utilities. Defaults to (gap-6).
The breakpoint from which the panes sit side by side: 'md or
'lg (the default).
The element to build. Defaults to div.
Extra classes for the wrapper.
Extra attributes, as pairs of name and value.
(split #:start (list (sidebar ...))
#:end (list (main ...))
#:ratio '(1 3))
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.
The children, as a list of nodes.
A list of gap utilities. Defaults to (gap-4).
Cross-axis alignment: 'stretch (default), 'start,
'center, 'end or 'baseline.
The element to build. Defaults to div.
Extra classes for the stack itself.
Extra attributes, as pairs of name and value.
(stack (list (h2 "Account")
(p "Signed in as Ada.")
(submit-button "Save"))
#:gap '(gap-2))
Create a compact key-figure block for dashboards and summary panels.
The prominent value to display, such as a number or short string.
A short descriptive label rendered below the value.
An optional trend string, e.g. "+12%".
A semantic keyword controlling the trend color: 'success,
'danger or 'neutral.
List of additional utility CSS classes.
(stat-card #:value "1,204" #:label "Active users" #:trend "+12%")
Render a numeric field with the decrement/increment buttons already on.
This is number-field with #:stepper? #t, named for the
thing a page asks for. The buttons are an enhancement over the native
input, so the control keeps working with scripting off.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
The initial value.
The lowest accepted value.
The highest accepted value.
The increment. Defaults to 1.
An optional unit shown beside the field.
Optional helper copy.
Optional error message.
Whether the field is required.
Whether the control is disabled.
Classes for the field wrapper.
(stepper #:label "Quantity" #:name "qty" #:value 1 #:min 1 #:max 9)
Create an HTML strong importance (<strong>) text fragment.
The inner phrase string or inline elements requiring strong visual weight.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing line-height spacing CSS classes.
List of symbols representing text sizing CSS classes.
(strong "Warning: Destructive action ahead!")
Create an inline HTML <sub> subscript fragment.
The natural home for chemical formulae and mathematical indices.
The subscript text or inline nodes.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols setting the vertical alignment offset.
List of additional typography utility classes.
(p (list "H" (sub "2") "O"))
Create a lighter, styled secondary <h1> heading wrapper.
The inner text string or child elements of the subheading.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing font style layout configuration options (e.g., italic).
List of symbols representing text sizing CSS classes.
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")
Create a lighter, styled secondary <h2> heading wrapper.
The inner text string or child elements of the subheading.
List of symbols representing font weight CSS classes.
List of symbols representing font style layout configuration options.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.
(sub-h2 "Contextual commentary block")
Create a lighter, styled secondary <h3> heading wrapper.
The inner text string or child elements of the subheading.
List of symbols representing font weight CSS classes.
List of symbols representing font style layout configuration options.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.
(sub-h3 "Detailed breakdown notes")
Create a lighter, styled secondary <h4> heading wrapper.
The inner text string or child elements of the subheading.
List of symbols representing font weight CSS classes.
List of symbols representing font style layout configuration options.
List of symbols representing text color CSS classes.
List of symbols representing font family CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing the line-height, defaulting to the ratio the heading scale pairs with size.
(sub-h4 "Supplemental metadata label")
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.
The inner content (text string or HTML fragments) of the button.
(submit-button "Save Preferences")
Create an HTML <summary> disclosure caption for details.
The summary text or inline nodes.
List of symbols representing typography CSS classes.
(summary "Advanced options")
Create an inline HTML <sup> superscript fragment.
Use it for footnote markers, ordinal suffixes and exponents.
The superscript text or inline nodes.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols setting the vertical alignment offset.
List of additional typography utility classes.
(p (list "Copyright" (sup "©")))
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.
The descriptive text shown next to the switch.
The form field name.
An optional explicit HTML identifier; defaults to name.
The value submitted when the switch is on.
A boolean rendering the switch in the on position.
A boolean disabling the control.
(switch #:label "Enable notifications" #:name "notifications" #:checked #t)
Describe a single tab for use with tabs.
A short, group-unique identifier used to bind the tab, its label and its panel.
The visible label shown on the tab button.
The panel content fragments.
(tab #:id "billing" #:label "Billing" #:content (list (p "Invoices")))
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.
The row groups making up the table, typically a thead, a
tbody and an optional tfoot.
An optional caption rendered as the table’s own caption element.
A symbol placing that caption 'top (default) or 'bottom.
List of symbols representing caption typography CSS classes.
A boolean wrapping the table in an accessible horizontally scrollable region.
An optional accessible name for that region; it defaults to a string caption.
Extra attributes for the <table> itself, as pairs.
Extra attributes for the scroll region, as pairs.
List of symbols representing the table layout CSS 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")
Describe one column of a sortable-table.
The text shown in the header cell. When omitted, the sort key is used.
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.
A boolean right-aligning the column and letting its digits line up.
A boolean; #f renders a plain header cell even when a key is
given.
The direction the first click on this column asks for, 'asc or
'desc. Defaults to the table’s own #:default-direction.
A symbol, one of 'left, 'right or 'center,
overriding the numeric default.
A boolean preventing this column’s cells from wrapping.
(table-column #:key 'seats #:label "Seats" #:numeric #t)
Undocumented procedure.
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.
A list of tab descriptors.
An optional group identifier; a unique one is generated when omitted.
The id of the tab selected initially. Defaults to the first tab.
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")))))
Create an HTML <tbody> row group holding the data rows.
A list of tr rows built from td cells.
List of symbols representing the body group CSS classes.
(tbody (list (tr (list (td "Ada Lovelace")))))
Create an HTML <td> data cell.
The cell text string or inline nodes.
A symbol, one of 'left, 'right or 'center. By
default a cell inherits the table’s alignment.
A boolean enabling tabular figures and, unless align says otherwise, right-aligning the cell, so columns of numbers line up digit by digit.
A boolean preventing the cell from wrapping.
A number of columns this cell spans, which is what a totals row and an empty state need.
List of symbols representing the data cell CSS classes.
(td "1 204" #:numeric #t)
Render a single-line text field through the field shell.
‘input’ is still the right component when a page does not need the shell’s wiring (an inline search box, a bare numeric filter); this one is for a form, where the label, the hint, the error and the required state have to agree with every other field beside it.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
The initial value.
A hint shown while the field is empty.
The native input type. Defaults to 'text.
Whether the field is required.
An optional autocomplete hint.
An optional virtual-keyboard hint.
An optional validation pattern.
Optional helper copy.
Optional error message.
Whether the control is disabled.
Whether the control is read-only.
Classes for the field wrapper.
Classes for the input itself.
(text-field #:label "Email" #:name "email" #:type 'email #:required #t)
Construct an HTML <textarea> multiline field block.
The explicit header string informing users of required field contexts.
The unique string structural identifier mapping the entity across standard DOM tables.
An optional explicit HTML identifier; defaults to name.
The initial text block paragraph payload string nested inside the block.
An optional hint string shown while the field is empty.
An optional suggested visible number of text rows.
An optional suggested visible width in average character widths.
A field completion mandatory requirement boolean validation gate switch.
An optional string forwarding the HTML autocomplete hint to the
browser.
An optional maximum accepted character count.
An optional string ("true" or "false") controlling native
spell checking.
A boolean that disables the control.
A boolean that makes the control read-only.
A boolean that focuses the control on page load.
Optional helper copy rendered below the field and linked with
aria-describedby.
Optional validation message rendered below the field with
role="alert".
List of symbols targeting specific dimensions and borders configuration styles.
List of symbols handling sizing constraints applied onto the matching label tags.
List of symbols managing outer wrapper responsive card flex constraints.
(textarea #:label "User Bio" #:name "biography" #:value "Born in Scheme...")
Create an HTML <tfoot> row group for summaries and totals.
A list of tr rows, usually a single row of totals.
List of symbols representing the footer group CSS classes.
(tfoot (list (tr (list (th "Total" #:scope 'row)
(td "42" #:numeric #t)))))
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.
The header text string or inline nodes.
A symbol, one of 'col (default), 'row, 'colgroup or
'rowgroup.
A symbol, one of 'left, 'right or 'center. By
default a header inherits the table’s alignment.
A boolean enabling tabular figures and, unless align says otherwise, right-aligning the header.
A boolean preventing the header from wrapping.
A number of columns this cell spans.
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.
Whether the header cell carries a column-resize handle (an enhancement).
List of symbols representing the header cell CSS classes.
(th "Price" #:align 'right #:numeric #t)
Create an HTML <thead> row group holding the column headers.
A list of tr rows built from th cells.
List of symbols representing the header group CSS classes.
(thead (list (tr (list (th "Name" #:scope 'col)))))
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.
The human-readable date or time string.
An optional ISO-8601 machine-readable value.
List of symbols representing font weight CSS classes.
List of symbols representing text color CSS classes.
List of symbols representing text sizing CSS classes.
List of typography rules, such as tabular figures for aligned dates.
(time "11 October 2026" #:datetime "2026-10-11")
Render a time field backed by the browser’s own time control.
The value is the ISO time form (09:30, or 09:30:00 with
seconds), and the browser draws it in the reader’s locale. step
is in seconds, which is how a time input says whether it wants minutes
or seconds.
The field’s visible name.
The form field name.
An explicit identifier; defaults to name.
The initial value, in ISO time form.
The earliest accepted time.
The latest accepted time.
The increment between accepted times, in seconds.
Whether the field is required.
Optional helper copy.
Optional error message.
Whether the control is disabled.
Classes for the field wrapper.
(time-field #:label "Starts at" #:name "starts-at" #:step 900)
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.
A list of timeline-item nodes.
The accessible name of the list.
Extra classes for the wrapper.
(timeline (list (timeline-item #:time "11 Oct" #:title "Deployed")))
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.
The human-readable time shown beside the marker.
The ISO value for the <time> element.
The entry’s heading.
Optional detail below the heading.
An optional Phosphor icon name shown in the marker.
'neutral (default), 'info, 'success,
'warning or 'danger.
Whether this is the entry in progress.
(timeline-item #:time "11 Oct" #:title "Deployed")
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.
The toast’s heading.
Optional supporting copy.
'info (default), 'success, 'warning or
'danger.
How long the toast stays, in milliseconds. Defaults to 5000; the
script reads it from the markup.
Whether the dismiss control is rendered. Defaults to true.
The accessible name of the dismiss control.
An optional identifier.
Extra classes for the card.
(toast #:title "Saved" #:body "Your changes are live." #:variant 'success)
Undocumented procedure.
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.
The tray’s identifier.
Its accessible name.
How many toasts the tray keeps at once, oldest first.
'bottom-end (default), 'bottom-start, 'top-end or
'top-start.
The identifier of the template the script clones.
Extra classes for the tray.
(toast-tray)
Undocumented procedure.
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"
A list of two-element lists, (group name).
The theme read by a colour token. Defaults to 'light.
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"
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.
One of the symbol names above.
The token’s key, e.g. 4 for spacing or 'lg for a radius.
'light (the default) or 'dark, read by colour tokens only.
(token-value 'space 4) ;; => "1rem" (token-value 'color 'ink #:theme 'dark) ;; => "#ededee"
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.
The trigger content the tooltip is attached to.
The tooltip text.
A stable HTML identifier for the hidden description, or #f for a
generated one. Give one when the page has to name it.
Where the tooltip appears: 'top (default), 'bottom,
'left or 'right.
List of additional utility CSS classes.
(tooltip (code "CARGO_HOME") #:text "Where Cargo stores downloaded crates")
Create an HTML table row (<tr>) from a list of cells.
A list of th or td cells.
List of symbols representing optional row CSS classes.
(tr (list (td "Cozy") (td "9" #:numeric #t)))
Undocumented procedure.
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.
The node’s text.
The destination for a leaf node.
Child tree-item nodes, for a branch.
Whether a branch starts open.
An optional Phosphor icon name.
Extra classes for the node.
(tree-item "Docs" #:children (list (tree-item "Install" #:href "/install")))
Create a navigable hierarchy from tree-item nodes.
A list of tree-item nodes.
The accessible name of the tree’s navigation region.
Extra classes for the wrapper.
(tree-view #:items (list (tree-item "Docs"
#:children (list (tree-item "Install"
#:href "/install")))))
Generate a JavaScript IIFE string that pushes notification parameters into the URL query string and reloads the window.
The fallback string title of the notification.
The core informational content string of the notification.
The visibility timeline limit in milliseconds before the notification is cleared.
The fallback typography class string applied to the notification’s description field.
(trigger-notification-function-js #:title "Alert" #:body "System update complete.")
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.
The text or inline nodes.
How many lines to keep. Defaults to 1.
An optional title attribute carrying the untruncated text.
The element to build. Defaults to p.
Extra classes for the text.
(truncate-text "A very long description indeed" #:lines 2)
Create a utility button pre-wired to toggle UI dark/light mode themes.
A symbol identifying the default icon type.
The description text displayed within the button layout.
A symbol representing the HTML button type.
List of symbols for background CSS classes.
List of symbols for hover background CSS classes.
List of symbols for border width CSS classes.
List of symbols for border color CSS classes.
List of symbols for shadow CSS classes.
List of symbols for text sizing CSS classes.
List of symbols for active state transformation CSS classes.
List of symbols for font family CSS classes.
List of symbols for font weight CSS classes.
List of symbols for text color CSS classes.
The global JavaScript function invocation string triggered to process the theme change.
List of symbols for border radius CSS classes.
List of symbols targeting icon coloring and sizing classes.
List of utility layout CSS classes.
List of symbols applied to the inner layout wrapper surrounding the icon and content.
(ui-mode-button #:content "Switch Theme")
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.
The variable name or placeholder text string.
List of symbols representing font weight CSS classes.
List of symbols representing text style transformations.
List of symbols representing text color CSS classes.
List of symbols representing text sizing CSS classes.
List of symbols representing font family CSS classes.
List of additional typography utility classes.
(var "username")
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.
The text or nodes to hide.
The element to build. Defaults to span.
Whether the content becomes visible when focused. Defaults to false.
Extra classes for the wrapper.
(visually-hidden "Loading search results") (visually-hidden "Skip to content" #:tag 'a #:focusable? #t)
Wrap a main content fragment and a footer fragment within a full-screen vertical flex container.
S-expression representing the main content layout.
S-expression representing the footer layout.
(with-footer #:main '((p "Hello World")) #:footer '((footer "Standard Footer")))
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.
A list of steps, each a label string or a (label . href) pair.
The 1-based index of the current step, or its label.
The accessible name of the navigation region.
'horizontal (default) or 'vertical.
Whether each step shows its number (or a check when complete).
Extra classes for the wrapper.
(wizard #:steps '(("Cart" . "/cart") ("Address") ("Payment"))
#:current 2)
"<!DOCTYPE html>"
"https://hygguile.jointhefreeworld.org"
"LGPL-3.0-or-later"
"2.7.3"
((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