xoong
Menu · All elements and attributes
DocsReferenceAll elements and attributes

Reference

Every element, directive, event, function and piece of template syntax of xoong, in one place: what it does, what it takes, an example, and the guide that explains it. 175 entries, written from the code itself.

Press Ctrl K (or /) to search everything on this site, or open the Reference menu in the header. The same data is published as reference.json for tools.

What the types mean
text
Any text.
number
A number.
flag
Present means on, absent means off.
list
Comma-separated values.
choice
One of a fixed set; any other value falls back to the default.
path
A path into the data: items, name.en.
operation
An operationId or METHOD /path.
selector
A CSS selector.
JSON
A JSON text.

Elements

Every xo-* element: what it does, its attributes, its events and an example. 47 entries.

<xo-view>

@xoong/data import '@xoong/data/view';

Declare a screen: get names the GET operation to read. Children are bound to the result. Add live to apply server-sent updates.

Attributes

AttributeTypeDefaultWhat it does
getoperationoperation—A path ("/users") or operationId.
liveflagflagoffApply live updates from the event stream.
url-paramslistlist—Comma-separated param names kept in the page URL (shareable, survive reload and Back): status,q,sort,offset.
p-<name>texttext—The value of the path parameter "name" of the <xo-view get> operation (p-id="42"; p-id="?id" reads ?id= from the page URL; p-id=":id" reads a route parameter).

Events

  • xo-done An action succeeded.
  • xo-error A load or an action failed.
  • xo-invalid A form was not sent because it is invalid.
  • xo-notice A message for the toast.
  • xo-go An action asked to go to another page.

Example

<xo-view get="listPeople" url-params="name,role">
  <p xo-if="$loading">Loading…</p>
  <p xo-if="$error">{$error.detail}</p>
  <ul>
    <li xo-each="items">{name} · {email}</li>
  </ul>
</xo-view>

Guide Data and tables

<xo-table>

@xoong/ui import '@xoong/ui';

A table for the list in the enclosing <xo-view get>. Children become the actions cell of each row.

Attributes

AttributeTypeDefaultWhat it does
ofpath, default itemspathitemsList field of the response (default items).
columnslistlist—Comma-separated fields, optionally field:Label.
sortableflagflagoffHeader buttons sort on the server through the sort query parameter.
selectableflagflagoffA checkbox per row and a select-all box; slot="bulk" children act on the ticked rows.
filterslistlist—Comma-separated fields that get a search box above the table; typing sets the query parameter of the same name.
page-sizenumbernumber—Rows per page: sets limit, Previous/Next change offset. The response needs a total.
cardsflagflagoffDraw each row as a card in a narrow pane (a theme does it; the table gets the class xo-cards). Cells carry data-label.
titletext, default the firsttextthe firstThe column that is the card title (default: the first).
statustexttext—The column that is the status badge of the card (data-status).
emptytexttext—Text shown when the list has no rows.
page-sizeslistlist—Comma-separated rows-per-page choices: a select beside the pager (needs page-size).
column-pickerflagflagoffA "Columns" dropdown at the right end of the search row: tick a column to show it, untick to hide it (the last visible one stays). Fires xo-columns-change with { visible, hidden }.
hidden-columnslistlist—With column-picker: comma-separated fields that start hidden.
columns-keytexttext—With column-picker: a name under which the choice is remembered in this browser (localStorage).

Events

Example

<xo-view get="listPeople">
  <xo-table columns="name,email:E-mail,role" sortable filters="name:Search by name" selectable page-size="10" empty="Nobody here">
    <span slot="role" class="badge">{role}</span>
    <button xo-click="deletePerson" xo-confirm="Delete {name}?" xo-notice="Deleted">Delete</button>
    <button slot="bulk" xo-click="deletePerson" xo-selected>Delete ticked</button>
  </xo-table>
</xo-view>

Guide Data and tables

<xo-form>

@xoong/ui import '@xoong/ui';

A form generated from the request schema of an operation.

Attributes

AttributeTypeDefaultWhat it does
opoperationoperation—The operation to submit to.
submittexttext—Text of the submit button.

Events

Example

<xo-form op="createPerson" submit="Add person" xo-notice="Person added"></xo-form>

Guide Forms

<xo-dialog>

@xoong/ui import '@xoong/ui';

A modal on the native <dialog>. Open it with xo-open, close it with xo-close or by submitting a form inside. With heading it is a titled modal: a header with the title and a Close button, the content as the body, and the elements marked slot="footer" as the footer.

Attributes

AttributeTypeDefaultWhat it does
headingtexttext—The title: makes it a titled modal (header, body, footer).
sizechoice, default 40remchoice40remsm, wide or full (the default is 40rem).One of sm, wide, full.
dismissflagflagoffClose it on a click outside the dialog.

Example

<button xo-open="#add">Add person</button>
<xo-dialog id="add" heading="Add person" size="wide" dismiss>
  <xo-form op="createPerson" submit="Add"></xo-form>
  <button slot="footer" xo-close>Cancel</button>
</xo-dialog>

Guide Dialogs and modals

<xo-toast>

@xoong/ui import '@xoong/ui';

Shows the message of the latest xo-error event on the page (not when the event was prevented with preventDefault(), or its handled is true because a view shows $error itself); hides on the next xo-done or after timeout seconds. Set detail.action ({ label, run }, for example Retry) and detail.timeout on the error and the toast shows that button, the same way an xo-notice does.

Attributes

AttributeTypeDefaultWhat it does
timeoutnumber, default 6number6Seconds before it hides (default 6, 0 = until the next success).

Example

<xo-toast timeout="8"></xo-toast>

Guide Dialogs and modals

<xo-router>

@xoong/ui import '@xoong/ui';

A History-API router declared in markup: one <template xo-route> per route, optional <template xo-layout> shells. Links are intercepted, aria-current marks the active one. Guards and leave-vetoes are JavaScript (router.guard, router.beforeNavigate).

Attributes

AttributeTypeDefaultWhat it does
basetexttext—Path prefix the app lives under, for example /admin.
base-linksflagflagoffA root-relative link such as /users inside the router means /admin/users, so the same screens mount under any base. rel="external" leaves the app.

Events

Example

<xo-router base="/admin" base-links>
  <template xo-layout="shell">
    <nav><a href="/users">Users</a></nav>
    <main xo-outlet></main>
  </template>
  <template xo-route="/users/:id" layout="shell" title="User">
    <xo-view get="getPerson" p-id=":id"><h1>{name}</h1></xo-view>
  </template>
  <template xo-route="*">Not found</template>
</xo-router>

Guide Router and page lifecycle

<xo-pager>

@xoong/ui import '@xoong/ui';

Previous and Next (and the "1–12 of 48" text) for any list in the enclosing <xo-view>: a tile grid, a thread list. The same pager <xo-table page-size> draws.

Attributes

AttributeTypeDefaultWhat it does
page-sizenumbernumber—Rows per page: sets limit; the buttons change offset. The response needs a total.
page-sizeslistlist—Comma-separated rows-per-page choices.

Example

<xo-view get="listPeople">
  <ul><li xo-each="items">{name}</li></ul>
  <xo-pager page-size="12" page-sizes="12,24,48"></xo-pager>
</xo-view>

Guide Recipes

<xo-tabs>

@xoong/controls import '@xoong/controls';

Accessible tabs from the sections that have xo-tab. Panels stay in the DOM, so form controls on other tabs still count; xo-reveal selects the tab of an invalid field.

Attributes

AttributeTypeDefaultWhat it does
valuetext, default the firsttextthe firstKey of the selected tab (default: the first).
labeltexttext—Accessible name of the tab list.

Events

Example

<xo-tabs label="Person">
  <section xo-tab="General" id="general"><label>Name <input name="name"></label></section>
  <section xo-tab="Topics" id="topics">…</section>
</xo-tabs>

Guide Tabs

<xo-menu>

@xoong/controls import '@xoong/controls';

An action menu: a trigger button and items (buttons or links) with arrow keys, Home/End, type-ahead and Escape. Drawn in the top layer where the browser supports the Popover API. Items with a value make it a choice menu: xo-menu-select fires with the value.

Attributes

AttributeTypeDefaultWhat it does
labeltexttext—Accessible name of the menu.
valuetexttext—The chosen value of a choice menu (the items that have a value attribute are radio items).

Events

Example

<xo-menu label="Actions">
  <button xo-menu-trigger>Actions</button>
  <button>Duplicate</button>
  <a href="/people/1">Open</a>
</xo-menu>

Guide Menu and tooltip

<xo-combobox>

@xoong/controls import '@xoong/controls';

A form control to pick from a list you can filter by typing. <option> children are the choices; multiple shows chips; the value is a string, or an array with multiple.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
multipleflagflagoffPick several values (chips).
creatableflagflagoffAllow a value that is not in the list.
remoteflagflagoffDo not filter locally: listen to xo-search and set el.options.
options-opoperationoperation—A GET operation whose list is the options (needs @xoong/data/view). With option-value and option-label (paths in each item, default id and name) and p-<name> parameters; a mutation of the list refreshes the options.
option-valuepath, default idpathidWith options-op: the path in each item that is the value (default id).
option-labelpath, default namepathnameWith options-op: the path in each item that is the label (default name).
placeholdertexttext—Hint text of the empty field.
labeltexttext—Accessible name when there is no <label>.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Events

  • xo-search The person typed in a combobox that does not filter by itself.

Example

<xo-combobox name="topics" label="Topics" multiple>
  <option value="web">Web</option>
  <option value="data">Data</option>
  <option value="design">Design</option>
</xo-combobox>

Guide Combobox and tags

<xo-tags>

@xoong/controls import '@xoong/controls';

A free-text tag input: type and press Enter or a comma to add a chip; Backspace removes the last. Value: an array of strings.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name when there is no <label>.
placeholdertexttext—Hint text of the empty field.
requiredtexttext—The field must be filled before the form can be sent.
disabledtexttext—Not available: not editable, not sent with the form.
readonlytexttext—Shown and sent, but not changeable.

Example

<xo-tags name="keywords" label="Keywords" placeholder="Add a keyword"></xo-tags>

Guide Combobox and tags

<xo-richtext>

@xoong/richtext import '@xoong/richtext';

A rich text editor form control: toolbar, headings, bold, italic, lists, quote, links. The value is sanitised HTML (p, h2, h3, strong, em, ul, ol, li, blockquote, br, a, img). xo-pick-media (cancelable) asks the app for an image.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
toolstexttext—Space-separated tools: undo redo style bold italic underline ul ol quote link image (h2 as a toggle).
toolbarchoicechoice—full or compact forces a toolbar size; by default the editor's own width decides (full from 580px).One of full, compact.
mediaflagflagoffShow the image tool; it fires xo-pick-media.
placeholdertexttext—Hint text of the empty editor.
labeltexttext—Accessible name of the editor.
maxlengthnumbernumber—Maximum number of text characters.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Events

Example

<xo-richtext name="bio" label="Bio" toolbar="compact" maxlength="2000" media></xo-richtext>

Guide Rich text

<xo-carousel>

@xoong/controls import '@xoong/controls';

A row of slides you move through: cards, a banner, a gallery. The children are the slides; they sit in a scroll container with scroll snap, so swiping is native. WAI-ARIA carousel pattern, slides out of view are inert.

Attributes

AttributeTypeDefaultWhat it does
itemsnumber, default 1number1Slides visible at once (default 1; 1.15 lets the next peek). Also --xo-carousel-items in CSS.
dotsflagflagoffA button per position; a slide with data-thumb="url" shows that picture instead.
counterflagflagoffShow "1 / 5".
no-arrowsflagflagoffHide the previous and next buttons.
loopflagflagoffNext at the end returns to the first slide.
autoplaynumbernumber—Move on by itself every n milliseconds (pauses on hover and focus; off with reduced motion; adds a pause button).
labeltexttext—Accessible name of the carousel.

Events

Example

<xo-carousel label="Services" items="3" dots counter loop>
  <article>First</article>
  <article>Second</article>
  <article>Third</article>
  <article>Fourth</article>
</xo-carousel>

Guide Carousel

<xo-switch>

@xoong/controls import '@xoong/controls';

One setting on or off (WAI-ARIA switch). A form control with a boolean value; the text inside is the visible label (label.xo-switch-row); change fires when the person toggles it.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
checkedflagflagoffStart on.
labeltexttext—Accessible name.
sizechoicechoice—sm for the compact switch.One of sm.
busyflagflagoffA save is running: aria-busy, clicks ignored.
disabledflagflagoffNot available.
readonlyflagflagoffShown but not changeable.
requiredflagflagoffThe field must be filled before the form can be sent.

Example

<xo-switch name="notify" checked>Email me about new comments</xo-switch>

Guide More controls

<xo-date>

@xoong/controls import '@xoong/controls';

A date field with a calendar popover. The value is ISO YYYY-MM-DD (with range: { start, end }); the field shows the locale format and accepts typing.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name.
mintexttext—Earliest date, YYYY-MM-DD.
maxtexttext—Latest date, YYYY-MM-DD.
rangeflagflagoffPick a start and an end (Clear and Apply in the calendar).
week-startnumber, default the localenumberthe localeFirst day of the week, 1 = Monday ... 7 = Sunday (default: the locale).
placeholdertexttext—Hint text of the empty field.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Example

<xo-date name="publishedAt" label="Publish on" value="2026-11-25"></xo-date>

Guide More controls

<xo-time>

@xoong/controls import '@xoong/controls';

A time of day as two selects, hour and minute. The value is HH:mm or empty.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name of the group.
stepnumber, default 5number5Minutes between choices (default 5).
mintexttext—Earliest time, HH:mm.
maxtexttext—Latest time, HH:mm.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Example

<xo-time name="at" label="Time" step="15" value="09:30"></xo-time>

Guide More controls

<xo-slider>

@xoong/controls import '@xoong/controls';

A number on a track (a native range input). input while dragging, change on release.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name.
minnumber, default 0number0Lowest value (default 0).
maxnumber, default 100number100Highest value (default 100).
stepnumber, default 1number1Step (default 1).
labelslistlist—Comma-separated names of the stops, read by screen readers (Low,Medium,High).
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Example

<xo-slider name="quality" label="Image quality" min="0" max="100" step="5" value="80"></xo-slider>

Guide More controls

<xo-stepper>

@xoong/controls import '@xoong/controls';

A number with minus and plus buttons; arrows change by step, Page by ten steps, the value is clamped.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name.
minnumbernumber—Lowest value.
maxnumbernumber—Highest value.
stepnumber, default 1number1Step (default 1).
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Example

<xo-stepper name="perPage" label="Items per page" min="1" max="100" step="5" value="25"></xo-stepper>

Guide More controls

<xo-password>

@xoong/controls import '@xoong/controls';

A password field with a show/hide button.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name.
autocompletetexttext—new-password or current-password, for password managers.
placeholdertexttext—Hint text of the empty field.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Example

<xo-password name="password" label="Password" autocomplete="new-password"></xo-password>

Guide More controls

<xo-copy>

@xoong/controls import '@xoong/controls';

A read-only value with a Copy button; fires copied.

Attributes

AttributeTypeDefaultWhat it does
valuetexttext—The text to show and copy.
labeltexttext—Accessible name of the field.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Events

  • copied The text was copied to the clipboard.
  • copy-failed The browser refused the clipboard.

Example

<xo-copy label="API key" value="xo_live_4f9a7f3a"></xo-copy>

Guide More controls

<xo-image>

@xoong/controls import '@xoong/controls';

An image that loads lazily, keeps its space (no layout jump), shows a placeholder while loading and a fallback on error. data-state: empty, loading, loaded, error.

Attributes

AttributeTypeDefaultWhat it does
srctexttext—The image URL (in an xo-view: src="{cover.url}").
alttexttext—Required: the text for the image, or alt="" when it is decorative.
widthnumbernumber—Intrinsic width, reserves the space with height.
heightnumbernumber—Intrinsic height.
ratiotexttext—Aspect ratio to reserve instead of width/height: 16/9, 1.5.
fittext, default covertextcovercover (default) or contain.
positiontexttext—object-position, e.g. top.
srcsettexttext—Responsive sources, passed to the img.
sizestexttext—Display sizes for srcset.
priorityflagflagoffThe image above the fold: loads at once with high priority (not lazy).
loadingchoice, default lazychoicelazylazy (default) or eager.One of lazy, eager.
fallbacktexttext—A src to use once when the image fails.

Example

<xo-image src="img/landscape.svg" alt="A landscape" ratio="16/9" fallback="img/placeholder.svg"></xo-image>

Guide More controls

<xo-theme-toggle>

@xoong/controls import '@xoong/controls';

A button that switches between the light and the dark theme: sets data-theme on <html>, saves the choice, follows the system until chosen. Fires xo-theme-change.

Attributes

AttributeTypeDefaultWhat it does
storagetext, default xoong-themetextxoong-themelocalStorage key (default xoong-theme).

Events

Example

<xo-theme-toggle></xo-theme-toggle>

Guide More controls

<xo-accordion>

@xoong/controls import '@xoong/controls';

A stack of native <details> as an accordion; single allows one open at a time; arrows move between summaries. Fires xo-accordion-change.

Attributes

AttributeTypeDefaultWhat it does
singleflagflagoffOnly one section open at a time.

Events

Example

<xo-accordion single>
  <details><summary>Does it need a build?</summary><div class="xo-accordion__body">No.</div></details>
  <details><summary>Can I use my own styles?</summary><div class="xo-accordion__body">Yes.</div></details>
</xo-accordion>

Guide More controls

<xo-popover>

@xoong/controls import '@xoong/controls';

A small panel opened by a button: Escape or an outside click closes it and the focus returns to the trigger. xo-popover-open / xo-popover-close.

Attributes

AttributeTypeDefaultWhat it does
anchorselector, default the element before itselectorthe element before itSelector of the trigger (default: the element before it).
labeltexttext—Accessible name of the panel.
openflagflagoffOpen at the start.

Events

Example

<div class="xo-notify">
  <button aria-label="Notifications">Bell</button>
  <xo-popover label="Notifications"><p>Nothing new.</p></xo-popover>
</div>

Guide More controls

<xo-consent>

@xoong/controls import '@xoong/controls';

A cookie consent bar that remembers the choice. The text inside is the message. Fires xo-consent with the choices.

Attributes

AttributeTypeDefaultWhat it does
categorieslistlist—Comma-separated categories: analytics,marketing.
storagetext, default xoong-consenttextxoong-consentlocalStorage key (default xoong-consent).
labeltexttext—Accessible name of the bar.

Events

  • xo-consent The visitor made a choice about cookies.

Example

<xo-consent categories="analytics,marketing">
  <b>We use two cookies.</b> One keeps you signed in, one measures anonymous visits.
</xo-consent>
<button xo-consent-open>Cookie settings</button>

Guide More controls

<xo-file>

@xoong/controls import '@xoong/controls';

A file field with a drop zone. The value is a File (an array with multiple). Refused files fire xo-file-reject.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name.
accepttexttext—Allowed types: image/*, .pdf.
max-sizetexttext—Largest file: bytes or 2MB.
multipleflagflagoffSeveral files.
hinttexttext—Text under the prompt.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Events

Example

<xo-file name="logo" label="Logo" accept="image/*" max-size="2MB" hint="PNG or SVG, up to 2 MB"></xo-file>

Guide More controls

<xo-command>

@xoong/controls import '@xoong/controls';

A command palette: Ctrl/Cmd+K, type to filter, arrows, Enter. The children (links, buttons with data-group) are the commands. Fires xo-command-run.

Attributes

AttributeTypeDefaultWhat it does
shortcuttext, default ktextkThe key used with Ctrl/Cmd (default k).
labeltexttext—Accessible name of the dialog.

Events

Example

<xo-command shortcut="k" label="Search commands">
  <a href="/articles" data-group="Go to">Articles</a>
  <button data-group="Actions" data-value="new-article">New article</button>
</xo-command>

Guide More controls

<xo-chart>

@xoong/controls import '@xoong/controls';

A bar or line chart drawn from data, with a table of the same numbers.

Attributes

AttributeTypeDefaultWhat it does
typechoice, default barchoicebarbar (default) or line.One of bar, line.
labeltexttext—Accessible name of the chart.
dataJSONJSON—JSON: [{"label":"Mon","value":12}] or [12, 18]; or set el.rows.
unittexttext—Appended to values: k, %.
headingtexttext—Label column of the table.
value-headingtexttext—Value column of the table.

Example

<xo-chart type="bar" label="Visits per day" unit="k" data='[{"label":"Mon","value":12},{"label":"Tue","value":18},{"label":"Wed","value":9}]'></xo-chart>

Guide More controls

<xo-range-slider>

@xoong/controls import '@xoong/controls';

A minimum and a maximum on one track: two native range inputs, thumbs cannot cross. The value is [min, max]; the attribute is value="20,70".

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name of the group.
minnumber, default 0number0Lowest value (default 0).
maxnumber, default 100number100Highest value (default 100).
stepnumber, default 1number1Step (default 1).
min-labeltexttext—Accessible name of the minimum thumb.
max-labeltexttext—Accessible name of the maximum thumb.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Example

<xo-range-slider name="price" label="Price" min="0" max="100" value="20,70" min-label="Minimum price" max-label="Maximum price"></xo-range-slider>

Guide More controls

<xo-segmented>

@xoong/controls import '@xoong/controls';

A group of buttons where one is chosen. The children <button value> are the segments; aria-pressed on each; arrows move the focus.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name of the group.
multipleflagflagoffEach segment toggles on its own; the value is an array.
clearableflagflagoffA click on the chosen segment unchooses it.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Example

<xo-segmented name="period" label="Billing period" value="month">
  <button value="month">Monthly</button>
  <button value="year">Yearly</button>
</xo-segmented>

Guide More controls

<xo-split-button>

@xoong/controls import '@xoong/controls';

A main action with a menu of related actions. The first child is the main button; the others are the menu items.

Attributes

AttributeTypeDefaultWhat it does
moretext, default More optionstextMore optionsAccessible name of the arrow button (default "More options").

Example

<xo-split-button more="More publish options">
  <button class="primary">Publish</button>
  <button>Schedule</button>
  <button>Save as draft</button>
</xo-split-button>

Guide More controls

<xo-textarea>

@xoong/controls import '@xoong/controls';

A textarea with a character counter and a thin progress bar toward maxlength; over the limit it is invalid and the text is kept.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name.
maxlengthnumbernumber—Character limit.
rowsnumber, default 3number3Visible rows (default 3).
placeholdertexttext—Hint text of the empty field.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Example

<xo-textarea name="excerpt" label="Excerpt" maxlength="160" rows="3"></xo-textarea>

Guide More controls

<xo-stat>

@xoong/controls import '@xoong/controls';

A KPI tile: label, value, a change against before and a sparkline from a series.

Attributes

AttributeTypeDefaultWhat it does
labeltexttext—What is measured.
valuetexttext—The number as text (formatted by you).
deltanumbernumber—Change in percent; positive shows an up arrow, negative a down arrow.
delta-labeltexttext—Text after the change, e.g. "vs last week".
serieslistlist—Comma-separated numbers for the sparkline.
trendtexttext—down-is-good when a fall is an improvement.

Example

<xo-stat label="Visits" value="48,210" delta="12.4" delta-label="vs last week" series="20,22,18,26,24,32,29"></xo-stat>

Guide More controls

<xo-otp>

@xoong/controls import '@xoong/controls';

One-time code: one box per digit; the value is the digit string; fires complete when all are filled. Paste splits across the boxes.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name of the group.
lengthnumber, default 6number6Number of digits (default 6).
valuetexttext—The start value.
requiredflagflagoffMust be filled.
disabledflagflagoffNot available.
readonlyflagflagoffShown but not changeable.

Events

  • complete Every box of a one-time code is filled.

Example

<xo-otp name="code" length="6" label="Verification code"></xo-otp>

Guide More controls

<xo-avatar>

@xoong/controls import '@xoong/controls';

A round badge with initials from the name, or a picture; aria-label is the name and the status.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Full name (initials and accessible name).
srctexttext—Picture URL; the initials stay when it fails.
sizechoicechoice—sm or lg.One of sm, lg.
statuschoicechoice—ok, warn or muted.One of ok, warn, muted.

Example

<xo-avatar name="Linh Phạm" status="ok"></xo-avatar>

Guide More controls

<xo-avatars>

@xoong/controls import '@xoong/controls';

A stack of avatars with a +N badge for the rest; a list.

Attributes

AttributeTypeDefaultWhat it does
maxnumbernumber—How many to show.
labeltexttext—Accessible name of the list.

Example

<xo-avatars max="2" label="Reviewers">
  <xo-avatar name="An Lê"></xo-avatar>
  <xo-avatar name="Binh Vo"></xo-avatar>
  <xo-avatar name="Chi Do"></xo-avatar>
</xo-avatars>

Guide More controls

<xo-progress>

@xoong/controls import '@xoong/controls';

A progress bar; no value means indeterminate.

Attributes

AttributeTypeDefaultWhat it does
valuenumbernumber—Current amount.
maxnumber, default 100number100Full amount (default 100).
labeltexttext—Accessible name.
thinflagflagoffThe thin variant.

Example

<xo-progress value="62" label="Upload"></xo-progress>

Guide More controls

<xo-spinner>

@xoong/controls import '@xoong/controls';

A loading spinner, role=status; label="" makes it decorative.

Attributes

AttributeTypeDefaultWhat it does
labeltexttext—What is loading.
sizechoicechoice—md or lg.One of md, lg.

Example

<xo-spinner label="Loading" size="lg"></xo-spinner>

Guide More controls

<xo-steps>

@xoong/controls import '@xoong/controls';

Progress through a flow of steps: the children are the step titles; aria-current=step marks the current one; done steps show a check.

Attributes

AttributeTypeDefaultWhat it does
currentnumbernumber—Index of the current step (from 0).
labeltexttext—Accessible name of the list.

Example

<xo-steps current="1" label="Checkout">
  <span>Account</span><span>Plan</span><span>Payment</span>
</xo-steps>

Guide More controls

<xo-inline-edit>

@xoong/controls import '@xoong/controls';

Text you rename in place: select it or press the Rename button; Enter saves, Escape cancels. Fires save and cancel.

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
valuetexttext—The text.
labeltexttext—Accessible name of the Rename button.
disabledflagflagoffNot available.
readonlyflagflagoffNot editable.
requiredflagflagoffThe field must be filled before the form can be sent.

Events

  • save The new text of an inline edit was saved.
  • cancel An inline edit was cancelled.

Example

<xo-inline-edit name="title" value="Greece Golden Visa" label="Title"></xo-inline-edit>

Guide More controls

<xo-drawer>

@xoong/controls import '@xoong/controls';

A side sheet on a native modal <dialog>: open it with xo-open="#id" like xo-dialog, close it with xo-close, Escape or (with dismiss) a click outside. The header and the footer (slot="footer") stay put, the body scrolls; full screen on a phone.

Attributes

AttributeTypeDefaultWhat it does
headingtexttext—The title in the header (or give label).
labeltexttext—Accessible name when there is no heading.
sidechoice, default endchoiceendend (default) or start: logical, follows the writing direction.One of end, start.
sizechoice, default mdchoicemdsm, md (default) or lg.One of sm, md, lg.
dismissflagflagoffClose it on a click outside.

Example

<button xo-open="#filters">Filters</button>
<xo-drawer id="filters" heading="Filters" side="end" dismiss>
  <p>Choose what to show.</p>
  <button slot="footer" type="button" xo-close>Close</button>
</xo-drawer>

Guide More controls

<xo-breadcrumb>

@xoong/controls import '@xoong/controls';

The trail of pages that led here: <a> children or JSON items, the last is aria-current=page; the middle collapses into an "…" menu when max is set or the space is short.

Attributes

AttributeTypeDefaultWhat it does
labeltext, default BreadcrumbtextBreadcrumbAccessible name of the navigation (default "Breadcrumb").
itemsJSONJSON—JSON: [{"label":"Home","href":"/"},{"label":"Now"}]; or write <a> children.
maxnumbernumber—Most items to show (the first and the last ones); the rest go into the "…" menu.

Example

<xo-breadcrumb max="3" items='[{"label":"Home","href":"/"},{"label":"Docs","href":"/docs"},{"label":"Controls","href":"/docs/controls"},{"label":"Tree"}]'></xo-breadcrumb>

Guide More controls

<xo-alert>

@xoong/controls import '@xoong/controls';

A message that stays in the page: an icon by kind, an optional heading and action (slot="action"), a Close button with dismissible. role=alert for warning and danger, status otherwise. Fires xo-dismiss.

Attributes

AttributeTypeDefaultWhat it does
kindchoice, default infochoiceinfoinfo (default), success, warning or danger.One of info, success, warning, danger.
headingtexttext—A bold first line.
dismissibleflagflagoffShow a Close button that hides the alert.
livechoicechoice—polite, assertive or off: overrides the role chosen by kind.One of polite, assertive, off.

Events

  • xo-dismiss The Close button of an alert was pressed.

Example

<xo-alert kind="warning" heading="Your trial ends in 3 days" dismissible>Add a payment method to keep your pages.</xo-alert>

Guide More controls

<xo-skeleton>

@xoong/controls import '@xoong/controls';

A grey placeholder in the shape of what is loading: lines of text, a circle or a rectangle. Hidden from screen readers, with one visually hidden "Loading" status; the shimmer stops under reduced motion.

Attributes

AttributeTypeDefaultWhat it does
linesnumbernumber—Number of text lines (the last is shorter).
shapechoice, default textchoicetexttext (default), circle or rect.One of text, circle, rect.
widthtexttext—A CSS length; a bare number is pixels.
heighttexttext—A CSS length; a bare number is pixels.
labeltext, default LoadingtextLoadingThe status text (default "Loading"); label="" leaves it out.

Example

<xo-skeleton lines="3"></xo-skeleton>

Guide More controls

<xo-tree>

@xoong/controls import '@xoong/controls';

A tree view (the WAI-ARIA tree pattern): nested <ul> or JSON data; arrows, Home, End, * and type-ahead; selectable or multiple (checkboxes, a parent is true, false or mixed). Fires xo-tree-select, xo-tree-toggle and, for lazy items, xo-tree-load.

Attributes

AttributeTypeDefaultWhat it does
dataJSONJSON—JSON: [{"id":"a","label":"A","children":[...]}]; or write nested <ul><li>.
labeltexttext—Accessible name of the tree.
selectableflagflagoffOne item can be chosen (aria-selected).
multipleflagflagoffEvery item has a checkbox; a parent follows its children.
expandedlistlist—Ids of the items open at the start, separated by commas.
selectedlistlist—Ids chosen at the start, separated by commas.

Events

Example

<xo-tree label="Files" selectable>
  <ul><li>src<ul><li>index.js</li><li>util.js</li></ul></li><li>README.md</li></ul>
</xo-tree>

Guide More controls

<xo-color>

@xoong/controls import '@xoong/controls';

A color field: a swatch button opens a panel with the native color picker, a validated hex field and preset swatches. The value is #rrggbb (#rrggbbaa with alpha).

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltexttext—Accessible name.
alphaflagflagoffAdd an opacity slider; the value then has two more digits.
swatcheslistlist—Preset colors: #0b7285,#c92a2a.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Example

<xo-color name="brand" label="Brand color" value="#0b7285" swatches="#0b7285,#c92a2a,#2b8a3e"></xo-color>

Guide More controls

<xo-rating>

@xoong/controls import '@xoong/controls';

A star rating: one native radio per star (two with half), so arrows, Tab and screen readers work; the pointer previews; the value is a number (0 is nothing).

Attributes

AttributeTypeDefaultWhat it does
nametexttext—Form field path.
labeltext, default RatingtextRatingAccessible name of the group (default "Rating").
maxnumber, default 5number5Number of stars (default 5).
halfflagflagoffAllow half stars.
clearableflagflagoffA click on the chosen star, or Delete, sets it back to nothing.
requiredflagflagoffThe field must be filled before the form can be sent.
disabledflagflagoffNot available: not editable, not sent with the form.
readonlyflagflagoffShown and sent, but not changeable.

Example

<xo-rating name="score" label="Your rating" value="4" half clearable></xo-rating>

Guide More controls

Directives

The xo-* attributes you put on your own markup: data, actions, form rows, routing, dialogs. 35 entries.

xo-each

Read data @xoong/data

Repeat this element for each item of the list at the given path (keyed by entity id).

Applies to
Any element inside <xo-view>. On a <template> it repeats the template's children.
Value
The path of a list: items, tags, blocks.
  • Inside it {path} reads the current item, $index is its position from 0 and $n from 1.
  • Rows are keyed by entity id, so a changed list updates the rows that changed.

Example

<xo-view get="listPeople">
  <ul><li xo-each="items">{$n}. {name}</li></ul>
</xo-view>

Guide Data and tables

xo-if

Read data @xoong/data

Render this element only when the path is truthy. Prefix with ! to negate. An empty array is false.

Applies to
Any element inside <xo-view>.
Value
A path ($loading, items), !path to negate, or a comparison with a text: type=image, status!=draft.

Example

<xo-view get="listPeople">
  <p xo-if="$loading">Loading…</p>
  <p xo-if="!items">Nobody here yet.</p>
</xo-view>

Guide Loading, empty and error

xo-param

Read data @xoong/data

On an <input> or <select>: set the view parameter of that name to its value. On other elements, on click, set a view parameter and refetch: name=value, name+=n, name-=n, or name~=value (toggle value / -value, for sorting).

Applies to
An <input> or <select> (its value), or any element that is clicked (name=value), inside <xo-view>.
Value
On a field: the parameter name. On click: name=value, name+=n, name-=n (never below 0) or name~=value (toggle value and -value, for sorting).
  • A button whose xo-param matches the current value gets aria-current.

Example

<xo-view get="listPeople" url-params="name,role">
  <input type="search" xo-param="name" aria-label="Search by name">
  <button xo-param="role=admin">Admins</button>
  <button xo-param="role=editor">Editors</button>
  <ul><li xo-each="items">{name}</li></ul>
</xo-view>

Guide Data and tables

xo-refresh

Read data @xoong/data

On a button inside an <xo-view>: on click, fetch the view's data again (a "Try again" button after an error, or a refresh button).

Applies to
A button inside <xo-view>.
Value
None.

Example

<xo-view get="listPeople">
  <p xo-if="$error">Could not load. <button xo-refresh>Try again</button></p>
</xo-view>

Guide Loading, empty and error

xo-click

Actions @xoong/data

Call an operation when clicked: "METHOD /path/{param}" or an operationId. Params come from the current item, then the view's p-* params.

Applies to
A <button> or link inside <xo-view>. Another element works, but it is not reachable by keyboard.
Value
An operationId (deletePerson) or METHOD /path/{param}. {param} comes from the current item, then the view's p-* parameters.
  • An operation that reads (GET) belongs on <xo-view get>, not on xo-click.
  • After success the queries that overlap the path are refetched.

Example

<xo-view get="listPeople">
  <ul>
    <li xo-each="items">{name}
      <button xo-click="deletePerson" xo-confirm="Delete {name}?" xo-notice="Deleted">Delete</button>
    </li>
  </ul>
</xo-view>

Guide Data and tables

xo-submit

Actions @xoong/data

On a <form>: send the form fields as the request body of the operation. The form is reset on success unless xo-keep is present. Field errors from the server appear beside the inputs.

Applies to
A <form> inside <xo-view>.
Value
An operationId or METHOD /path/{param}.
  • The field names are paths (name, title.en, items[0].x). The form is reset after success unless xo-keep is present.

Example

<xo-view>
  <form xo-submit="createPerson" xo-notice="Person added">
    <label>Name <input name="name" required></label>
    <label>E-mail <input name="email" type="email"></label>
    <button>Add</button>
  </form>
</xo-view>

Guide Forms

xo-body

Actions @xoong/data

With xo-click: take the request body from the item at this path.

Applies to
An element with xo-click.
Value
The path of the object to send: . (the current item), draft, $selected.

Example

<xo-view get="listPeople">
  <ul><li xo-each="items">{name}
    <button xo-click="updatePerson" xo-body=".">Save</button>
  </li></ul>
</xo-view>

xo-keep

Actions @xoong/data

With xo-submit: keep the field values after a successful send.

Applies to
A <form> that has xo-submit.
Value
None.

Example

<xo-view>
  <form xo-submit="createPerson" xo-keep>
    <input name="name" aria-label="Name">
    <button>Add</button>
  </form>
</xo-view>

xo-optimistic

Actions @xoong/data

On xo-click: set these fields (field=value,field2={path}) on the current item immediately, and undo them if the call fails.

Applies to
An element with xo-click, inside xo-each.
Value
A list field=value,field2={path}.

Example

<xo-view get="listPeople">
  <ul><li xo-each="items">{name} ({role})
    <button xo-click="updatePerson" xo-with="role=viewer" xo-optimistic="role=viewer">Make viewer</button>
  </li></ul>
</xo-view>

Guide Data and tables

xo-select

Actions @xoong/data

On a checkbox in a row (inside xo-each): tick that row. all:items in the header ticks every row of the list. <xo-table selectable> does this for you.

Applies to
A checkbox in a row (inside xo-each), or in the header.
Value
None on a row; all:items (all: and the list path) on the header checkbox ticks every row.
  • <xo-table selectable> adds these checkboxes for you.

Example

<xo-view get="listPeople">
  <label><input type="checkbox" xo-select="all:items"> All</label>
  <ul><li xo-each="items"><label><input type="checkbox" xo-select> {name}</label></li></ul>
  <button xo-click="deletePerson" xo-selected xo-if="$selectedCount">Delete {$selectedCount}</button>
</xo-view>

Guide Data and tables

xo-selected

Actions @xoong/data

On an xo-click button: run the operation once for every ticked row, one after another; the first failure stops the rest. With a value (xo-selected="ids"): ONE request whose body has the ticked ids in that field.

Applies to
An element with xo-click.
Value
None: run the operation once per ticked row. With a field name (ids): one request whose body has the ticked ids in that field.

Example

<xo-view get="listPeople">
  <ul><li xo-each="items"><label><input type="checkbox" xo-select> {name}</label></li></ul>
  <button xo-click="deletePerson" xo-selected>Delete ticked</button>
</xo-view>

Guide Data and tables

xo-with

Actions @xoong/data

On xo-click/xo-submit: fixed fields added to the request body, field=value,other={path} (typed from the request schema).

Applies to
An element with xo-click or xo-submit, or xo-add in a form.
Value
A list field=value,other={path}. Values are typed from the request schema.

Example

<xo-view get="listPeople">
  <ul><li xo-each="items">{name}
    <button xo-click="updatePerson" xo-with="role=admin">Make admin</button>
  </li></ul>
</xo-view>

Guide Data and tables

xo-confirm

Actions @xoong/data

On xo-click/xo-submit: ask before running, with a native dialog. {path} names the item: xo-confirm="Delete {title}?".

Applies to
An element with xo-click or xo-submit.
Value
The question; {path} names the item: Delete {name}?.

Example

<xo-view get="listPeople">
  <ul><li xo-each="items">{name}
    <button xo-click="deletePerson" xo-confirm="Delete {name}?" xo-confirm-title="Delete person" xo-confirm-ok="Delete" xo-confirm-kind="danger">Delete</button>
  </li></ul>
</xo-view>

Guide Dialogs and modals

xo-notice

Actions @xoong/data

On xo-click/xo-submit: after success, show this message in <xo-toast> ({path} and {$selectedCount} work).

Applies to
An element with xo-click or xo-submit.
Value
The message; {path} and {$selectedCount} work.
  • The message is shown by <xo-toast>. It travels as the xo-notice event.

Example

<button xo-click="deletePerson" xo-notice="Deleted {name}">Delete</button>

Guide Dialogs and modals

xo-then

Actions @xoong/data

On xo-click/xo-submit: after success, go to this path; {id} comes from the result (a created record), then from the item acted on: xo-then="/articles/{id}", or xo-then="/articles" after a delete. <xo-router> navigates.

Applies to
An element with xo-click or xo-submit.
Value
A path of this site. {id} comes from the result (a created record), then from the item acted on.
  • <xo-router> navigates. Without a router the page is loaded with location.assign. Only a relative path or a same-origin URL is followed.

Example

<xo-view>
  <form xo-submit="createPerson" xo-then="/people/{id}">
    <input name="name" aria-label="Name">
    <button>Create</button>
  </form>
</xo-view>

xo-confirm-title

Actions @xoong/ui

With xo-confirm: a heading for the question (xo-confirm-title="Delete person").

Applies to
An element that has xo-confirm.
Value
The heading text.

Example

<button xo-click="deletePerson" xo-confirm="This cannot be undone." xo-confirm-title="Delete person">Delete</button>

Guide Dialogs and modals

xo-confirm-ok

Actions @xoong/ui

With xo-confirm: the text of the confirming button (xo-confirm-ok="Delete") instead of "Confirm".

Applies to
An element that has xo-confirm.
Value
The text of the confirming button.

Example

<button xo-click="deletePerson" xo-confirm="Delete {name}?" xo-confirm-ok="Delete">Delete</button>

Guide Dialogs and modals

xo-confirm-cancel

Actions @xoong/ui

With xo-confirm: the text of the cancelling button instead of "Cancel".

Applies to
An element that has xo-confirm.
Value
The text of the cancelling button.

Example

<button xo-click="deletePerson" xo-confirm="Delete {name}?" xo-confirm-cancel="Keep it">Delete</button>

xo-confirm-kind

Actions @xoong/ui

With xo-confirm: danger, warning, info or success: an icon and the matching colour; the confirming button is red unless a kind says otherwise.

Applies to
An element that has xo-confirm.
Value
One of danger, warning, info, success.
Values
danger, warning, info, success

Example

<button xo-click="updatePerson" xo-confirm="Publish now?" xo-confirm-kind="info">Publish</button>

Guide Dialogs and modals

xo-add

Form rows @xoong/forms

In a form: append an item to the array at this path (blocks); xo-with="type=image" gives a union item its tag.

Applies to
A button inside a <form xo-submit>.
Value
The path of the array: keywords, blocks. Add xo-with="type=image" to give a union item its tag.

Example

<xo-view get="getPerson" p-id="1">
  <form xo-submit="updatePerson">
    <div xo-each="keywords"><input name="keywords[{$index}]" aria-label="Keyword"></div>
    <button type="button" xo-add="keywords">Add keyword</button>
  </form>
</xo-view>

Guide Forms

xo-remove

Form rows @xoong/forms

In a form row: remove the array item at this path (blocks[{$index}]).

Applies to
A button inside a row of an array in a form.
Value
The path of the item: keywords[{$index}].

Example

<xo-view get="getPerson" p-id="1">
  <form xo-submit="updatePerson">
    <div xo-each="keywords"><input name="keywords[{$index}]" aria-label="Keyword">
      <button type="button" xo-remove="keywords[{$index}]" aria-label="Remove">✕</button></div>
  </form>
</xo-view>

Guide Forms

xo-move

Form rows @xoong/forms

In a form row: move the array item at this path, blocks[{$index}]:up or :down.

Applies to
A button inside a row of an array in a form.
Value
The path of the item and a direction: keywords[{$index}]:up or :down.

Example

<xo-view get="getPerson" p-id="1">
  <form xo-submit="updatePerson">
    <div xo-each="keywords"><input name="keywords[{$index}]" aria-label="Keyword">
      <button type="button" xo-move="keywords[{$index}]:up" aria-label="Move up">↑</button>
      <button type="button" xo-move="keywords[{$index}]:down" aria-label="Move down">↓</button></div>
  </form>
</xo-view>

Guide Forms

xo-drag

Form rows @xoong/forms

On a handle inside an array row of a form: drag the row onto another row of the same list to move it (blocks[{$index}]). The xo-move buttons stay for keyboards.

Applies to
A handle inside a row of an array in a form.
Value
The path of the item: keywords[{$index}].

Example

<xo-view get="getPerson" p-id="1">
  <form xo-submit="updatePerson">
    <div xo-each="keywords"><span class="handle" xo-drag="keywords[{$index}]" title="Drag to reorder">⠿</span>
      <input name="keywords[{$index}]" aria-label="Keyword"></div>
  </form>
</xo-view>

Guide Forms

xo-guard

Form rows @xoong/ui

On a <form xo-submit>: with unsaved changes the router asks before the page is left. xo-guard="off" disables it; any other text is the question to ask.

Applies to
A <form xo-submit>.
Value
off disables the question; any other text is the question to ask.

Example

<xo-view get="getPerson" p-id="1">
  <form xo-submit="updatePerson" xo-guard="Leave without saving?">
    <input name="name" aria-label="Name"> <button>Save</button>
  </form>
</xo-view>

xo-route

Routing @xoong/ui

On a <template> inside <xo-router>: the path this route answers, for example /users/:id; * is the not-found route. The content is inert until the route matches. An empty template with src fetches that URL the first time the route (or a layout) is shown.

Applies to
A <template> inside <xo-router>.
Value
The path the route answers: /users/:id; * is the not-found route.

Example

<xo-router>
  <template xo-route="/users/:id" title="User"><xo-view get="getPerson" p-id=":id"><h1>{name}</h1></xo-view></template>
  <template xo-route="*">Not found</template>
</xo-router>

Guide Router and page lifecycle

xo-layout

Routing @xoong/ui

On a <template> inside <xo-router>: a layout (shell) that routes use with layout="name". It must contain one element with xo-outlet.

Applies to
A <template> inside <xo-router>.
Value
The name of the layout. Routes use it with layout="name".

Example

<xo-router>
  <template xo-layout="shell"><nav><a href="/users">Users</a></nav><main xo-outlet></main></template>
  <template xo-route="/users" layout="shell">Users</template>
</xo-router>

Guide Router and page lifecycle

xo-outlet

Routing @xoong/ui

In a layout: the element where the matched route content is shown. The layout stays while only this content changes.

Applies to
One element in a layout.
Value
None.

Example

<xo-router>
  <template xo-layout="shell"><header>My app</header><main xo-outlet></main></template>
  <template xo-route="/" layout="shell">Home</template>
</xo-router>

Guide Router and page lifecycle

xo-current

Routing @xoong/ui

On a link, a nav, or the router: leave aria-current as the author set it. The router then does not mark that link current.

Applies to
A link, a nav, or <xo-router>.
Value
Present. No value.

Example

<nav xo-current>
  <a href="/studio/pages" aria-current="page">Pages</a>
</nav>

Guide Router and page lifecycle

xo-open

Dialogs @xoong/ui

Open the <xo-dialog> matched by this selector, for example #add.

Applies to
A button anywhere in the page.
Value
A selector for an <xo-dialog>: #add.

Example

<button xo-open="#add">Add person</button>
<xo-dialog id="add" heading="Add person"><xo-form op="createPerson"></xo-form></xo-dialog>

Guide Dialogs and modals

xo-close

Dialogs @xoong/ui

Close the <xo-dialog> this element is in.

Applies to
A button inside an <xo-dialog>.
Value
None.

Example

<xo-dialog id="info" heading="Info">
  <p>Saved.</p>
  <button slot="footer" xo-close>Close</button>
</xo-dialog>

Guide Dialogs and modals

xo-tab

Controls @xoong/controls

On a section inside <xo-tabs>: the tab label. The section id is the tab key (its position when it has none).

Applies to
A <section> inside <xo-tabs>.
Value
The tab label. The section's id is the key of the tab (its position without one).

Example

<xo-tabs label="Page"><section xo-tab="General">…</section><section xo-tab="SEO" id="seo">…</section></xo-tabs>

Guide Tabs

xo-menu-trigger

Controls @xoong/controls

On the button of <xo-menu> that opens it (the first button when none is marked).

Applies to
A button inside <xo-menu>.
Value
None.

Example

<xo-menu label="Actions"><button xo-menu-trigger>Actions</button><button>Duplicate</button></xo-menu>

Guide Menu and tooltip

xo-tooltip

Controls @xoong/controls

On any element: a short description shown on hover and focus (and hidden on Escape). It is a description, not the accessible name.

Applies to
Any element.
Value
The text of the tooltip.
  • It describes the element; it is not its accessible name, so a button without text still needs aria-label.

Example

<button xo-tooltip="Copy the link" aria-label="Copy link">Copy</button>

Guide Menu and tooltip

xo-consent-open

Controls @xoong/controls

On any element: a click shows the <xo-consent> bar again (cookie settings).

Applies to
Any element (a button).
Value
None.

Example

<button xo-consent-open>Cookie settings</button>

Guide More controls

xo-t

Language @xoong/i18n

Translate this element with @xoong/i18n: xo-t="key" for its text, xo-t="attr:key; attr2:key2" for attributes. The text written in the HTML is the default. {placeholders} come from the enclosing xo-view.

Applies to
Any element.
Value
A message key for the text (home.title), or attr:key; attr2:key2 for attributes.
  • The text written in the HTML is the default. Inside an <xo-view>, {placeholders} come from the view.

Example

<h1 xo-t="home.title">Welcome</h1>
<input xo-t="placeholder:search.placeholder" placeholder="Search" aria-label="Search">

Guide Recipes

Events

The events xoong elements fire, what is in detail, and who listens. 36 entries.

xo-done

Bubbles

An action succeeded. Fires on the element that ran the action, after the server answered, and bubbles. <xo-dialog> closes when a form inside it fires it; <xo-toast> hides its error.

Fired by
<xo-view> (xo-click, xo-submit, <xo-form>)
detail
The result of the operation.

Example

document.addEventListener('xo-done', (event) => console.log(event.detail));

xo-error

Bubbles Cancelable

A load or an action failed. Fires on the element that ran the action (or the view that could not load) and bubbles. Cancelable: call preventDefault() when your own code handles the failure, and <xo-toast> stays quiet. event.handled is true when the view shows $error itself, or the error is a 401 that onUnauthorized deals with: the toast skips those too. detail.action ({ label, run }, for example Retry) and detail.timeout are drawn when the toast is not skipped.

Fired by
<xo-view>, <xo-router>
detail
The XoongError (status, code, detail, fields). Set action ({ label, run }) and timeout on it and <xo-toast> shows that button (it reads them one microtask later).

Example

document.addEventListener('xo-error', (event) => console.log(event.detail));

xo-invalid

Bubbles

A form was not sent because it is invalid. Nothing was sent; the form already shows why beside each field.

Fired by
<form xo-submit>
detail
{ errors }: the messages by field path.

Example

document.addEventListener('xo-invalid', (event) => console.log(event.detail));

xo-notice

Bubbles

A message for the toast. Fires after an action with xo-notice succeeded, and when you call toast.show(). <xo-toast> listens on the document.

Fired by
xo-notice, toast.show()
detail
{ message, kind }; toast.show() adds action and timeout.

Example

document.addEventListener('xo-notice', (event) => console.log(event.detail));

xo-go

Bubbles Cancelable

An action asked to go to another page. Cancelable. <xo-router> handles it and cancels the default; without a router the page is loaded with location.assign.

Fired by
xo-then
detail
{ path }: where to go.

Example

document.addEventListener('xo-go', (event) => console.log(event.detail));

xo-navigate

Bubbles

The router moved to another location. Fires after a navigation, also when only the query or the hash changed.

Fired by
<xo-router>
detail
{ to, from }: two route locations (path, params, query, url, route).

Example

document.addEventListener('xo-navigate', (event) => console.log(event.detail));

xo-columns-change

Bubbles

The person showed or hid a column of the table. Fires from the Columns picker. With columns-key the choice is also remembered in this browser.

Fired by
<xo-table column-picker>
detail
{ visible, hidden }: the field names that are shown and those that are hidden.

Example

document.addEventListener('xo-columns-change', (event) => console.log(event.detail));

xo-dirty

Bubbles

A form gained or lost unsaved changes. The router listens to it to ask before the page is left.

Fired by
<form xo-submit>
detail
{ dirty }: true when there are unsaved changes.

Example

document.addEventListener('xo-dirty', (event) => console.log(event.detail));

xo-reveal

Bubbles Cancelable

The form is about to focus an invalid field. Cancelable. <xo-tabs> listens and selects the tab of the field first, so the field is visible when it gets the focus.

Fired by
<form xo-submit>
detail
{ path }: the invalid field.

Example

document.addEventListener('xo-reveal', (event) => console.log(event.detail));

xo-array-change

Bubbles

A row of an array in a form was added, removed or moved. Fires for the add, remove and move buttons, a drag, and code that changes the array.

Fired by
<form xo-submit>
detail
{ path, op, from, to }: op is add, remove or move.

Example

document.addEventListener('xo-array-change', (event) => console.log(event.detail));

xo-form-ready

Bubbles

The form model exists. From here form.model is available. A child control that needs the model listens for this event.

Fired by
<form xo-submit>
detail
{ model }: the form model.

Example

document.addEventListener('xo-form-ready', (event) => console.log(event.detail));

xo-pick-media

Bubbles Cancelable

The image tool of the editor was used. Cancelable. Open your media library in a listener, call preventDefault(), and call detail.insert now or later. Without preventDefault() nothing is inserted.

Fired by
<xo-richtext>
detail
{ insert }: call insert({ src, alt }) when the person has chosen.

Example

document.addEventListener('xo-pick-media', (event) => console.log(event.detail));

xo-richtext-expand

Bubbles

The editor opened in its large dialog.

Fired by
<xo-richtext>
detail
None.

Example

document.addEventListener('xo-richtext-expand', (event) => console.log(event.detail));

xo-richtext-done

Bubbles

The large editor dialog was closed. Fires for Done and for Escape.

Fired by
<xo-richtext>
detail
None.

Example

document.addEventListener('xo-richtext-done', (event) => console.log(event.detail));

xo-search

Bubbles

The person typed in a combobox that does not filter by itself. Set el.options in the listener.

Fired by
<xo-combobox remote>
detail
{ query }: what the person typed.

Example

document.addEventListener('xo-search', (event) => console.log(event.detail));

xo-tab-change

Bubbles

Another tab was selected.

Fired by
<xo-tabs>
detail
{ value }: the key of the selected tab.

Example

document.addEventListener('xo-tab-change', (event) => console.log(event.detail));

xo-menu-select

Bubbles

An item of a choice menu was chosen. Only for items that have a value attribute.

Fired by
<xo-menu>
detail
{ value }: the value of the chosen item.

Example

document.addEventListener('xo-menu-select', (event) => console.log(event.detail));

xo-menu-open

Bubbles

A menu opened.

Fired by
<xo-menu>
detail
None.

Example

document.addEventListener('xo-menu-open', (event) => console.log(event.detail));

xo-menu-close

Bubbles

A menu closed.

Fired by
<xo-menu>
detail
None.

Example

document.addEventListener('xo-menu-close', (event) => console.log(event.detail));

xo-carousel-change

Bubbles

The carousel moved to another slide.

Fired by
<xo-carousel>
detail
{ index, count }: the first visible slide and the number of slides.

Example

document.addEventListener('xo-carousel-change', (event) => console.log(event.detail));

xo-file-reject

Bubbles

A file was refused.

Fired by
<xo-file>
detail
{ file, reason }: reason is type or size.

Example

document.addEventListener('xo-file-reject', (event) => console.log(event.detail));

xo-command-run

Bubbles

A command of the palette was run.

Fired by
<xo-command>
detail
{ value, label }: data-value (or the href), and the text of the command.

Example

document.addEventListener('xo-command-run', (event) => console.log(event.detail));

xo-consent

Bubbles

The visitor made a choice about cookies.

Fired by
<xo-consent>
detail
The choices: one boolean per category ({ analytics: true, marketing: false }).

Example

document.addEventListener('xo-consent', (event) => console.log(event.detail));

xo-theme-change

Bubbles

The theme was switched.

Fired by
<xo-theme-toggle>
detail
{ theme }: light or dark.

Example

document.addEventListener('xo-theme-change', (event) => console.log(event.detail));

xo-popover-open

Bubbles

A popover opened.

Fired by
<xo-popover>
detail
None.

Example

document.addEventListener('xo-popover-open', (event) => console.log(event.detail));

xo-popover-close

Bubbles

A popover closed.

Fired by
<xo-popover>
detail
None.

Example

document.addEventListener('xo-popover-close', (event) => console.log(event.detail));

xo-accordion-change

Bubbles

A section of an accordion opened or closed.

Fired by
<xo-accordion>
detail
{ index, open }: which section and whether it is open now.

Example

document.addEventListener('xo-accordion-change', (event) => console.log(event.detail));

copied

Bubbles

The text was copied to the clipboard.

Fired by
<xo-copy>
detail
{ text }: what was copied.

Example

document.addEventListener('copied', (event) => console.log(event.detail));

copy-failed

Bubbles

The browser refused the clipboard.

Fired by
<xo-copy>
detail
{ text }: what could not be copied.

Example

document.addEventListener('copy-failed', (event) => console.log(event.detail));

complete

Bubbles

Every box of a one-time code is filled.

Fired by
<xo-otp>
detail
{ value }: the digits.

Example

document.addEventListener('complete', (event) => console.log(event.detail));

save

Bubbles

The new text of an inline edit was saved. Enter saves.

Fired by
<xo-inline-edit>
detail
{ value }: the new text.

Example

document.addEventListener('save', (event) => console.log(event.detail));

xo-dismiss

Bubbles

The Close button of an alert was pressed.

Fired by
<xo-alert>
detail
None. preventDefault() keeps the alert.

Example

document.addEventListener('xo-dismiss', (event) => console.log(event.detail));

xo-tree-select

Bubbles

The choice in a tree changed.

Fired by
<xo-tree>
detail
{ id, selected }: the item acted on and the chosen ids.

Example

document.addEventListener('xo-tree-select', (event) => console.log(event.detail));

xo-tree-toggle

Bubbles

A tree item opened or closed.

Fired by
<xo-tree>
detail
{ id, expanded }: the item and whether it is open now.

Example

document.addEventListener('xo-tree-toggle', (event) => console.log(event.detail));

xo-tree-load

Bubbles

A lazy tree item was opened for the first time.

Fired by
<xo-tree>
detail
{ id }: the lazy item; answer with tree.setChildren(id, children).

Example

document.addEventListener('xo-tree-load', (event) => console.log(event.detail));

cancel

Bubbles

An inline edit was cancelled. Escape cancels.

Fired by
<xo-inline-edit>
detail
None.

Example

document.addEventListener('cancel', (event) => console.log(event.detail));

JavaScript API

The functions and objects you import when HTML is not enough. 46 entries.

configure(options)

@xoong/data import { configure } from '@xoong/data';

Every <xo-view>, xo-click and xo-submit uses this client. Call it before the views start; views that started earlier wait for it.

Returns
Client

Parameters

NameTypeWhat it is
apiobjectobjectRequired. The contents of .xoong/operations.json (written by xoong sync).
baseUrlstringstringPrefix of every request URL, for example /api.
headersfunctionfunctionReturns the headers of a request, or a promise of them: authentication.
credentialsstringstringThe fetch credentials mode (same-origin, include, omit).
staleTimenumbernumberMilliseconds a GET result stays fresh. Default 30000.
gcTimenumbernumberMilliseconds an unobserved query is kept. Default 300000.
evictEntitiesbooleanbooleanDrop the entities that no cached query refers to when a query is collected. Default true.
retrynumbernumberRetries of a GET after a network error or a 502, 503 or 504. Default 2.
retryDelaynumbernumberBase delay in milliseconds, doubled per retry. Default 300.
invalidateobjectobjectWhich queries a mutation refreshes, by operation: { shipOrder: ['getDashboard'] }.
onErrorfunctionfunctionCalled with every XoongError.
onUnauthorizedfunctionfunctionCalled with a 401 error (onError is called too).
streamUrlstringstringThe server-sent events endpoint that <xo-view live> listens to.
fetchfunctionfunctionYour own fetch, for tests or a custom transport.

Example

import { configure } from '@xoong/data';
import '@xoong/data/view';
import operations from '/.xoong/operations.json' with { type: 'json' };

configure({ api: operations, baseUrl: '/api', streamUrl: '/api/events' });
TypeScript declaration
function configure<Ops extends Record<keyof Ops, OperationType> = any>(options: ClientOptions): Client<Ops>;

Guide Install

getClient()

@xoong/data import { getClient } from '@xoong/data';

Use it when HTML is not enough: getClient().load(...), .mutate(...), .reset().

Returns
Client

Example

import { getClient } from '@xoong/data';

const people = await getClient().load('listPeople', { limit: 5 });
TypeScript declaration
function getClient<Ops extends Record<keyof Ops, OperationType> = any>(): Client<Ops>;

Guide Install

createClient(options)

@xoong/data import { createClient } from '@xoong/data';

Takes the same options as configure(). Useful in tests and for a second API.

Returns
Client

Parameters

NameTypeWhat it is
apiobjectobjectRequired. The contents of .xoong/operations.json (written by xoong sync).
baseUrlstringstringPrefix of every request URL, for example /api.
headersfunctionfunctionReturns the headers of a request, or a promise of them: authentication.
credentialsstringstringThe fetch credentials mode (same-origin, include, omit).
staleTimenumbernumberMilliseconds a GET result stays fresh. Default 30000.
gcTimenumbernumberMilliseconds an unobserved query is kept. Default 300000.
evictEntitiesbooleanbooleanDrop the entities that no cached query refers to when a query is collected. Default true.
retrynumbernumberRetries of a GET after a network error or a 502, 503 or 504. Default 2.
retryDelaynumbernumberBase delay in milliseconds, doubled per retry. Default 300.
invalidateobjectobjectWhich queries a mutation refreshes, by operation: { shipOrder: ['getDashboard'] }.
onErrorfunctionfunctionCalled with every XoongError.
onUnauthorizedfunctionfunctionCalled with a 401 error (onError is called too).
streamUrlstringstringThe server-sent events endpoint that <xo-view live> listens to.
fetchfunctionfunctionYour own fetch, for tests or a custom transport.

Example

import { createClient } from '@xoong/data';

const client = createClient({ api: operations, baseUrl: '/api' });
TypeScript declaration
function createClient<Ops extends Record<keyof Ops, OperationType> = any>(options: ClientOptions): Client<Ops>;

client.load(operation, params?)

@xoong/data const client = getClient();

Uses the cache while the result is fresh (staleTime). Throws a XoongError when the request fails.

Returns
Promise<response>

Parameters

NameTypeWhat it is
operationstringstringAn operationId or GET /path.
paramsobjectobjectPath and query parameters.

Example

const page = await getClient().load('listPeople', { limit: 10, sort: 'name' });
TypeScript declaration
load<K extends Id<Ops>>(op: K, ...params: ParamsArg<Ops[K]>): Promise<Ops[K]['response']>;

Guide Data and tables

client.query(operation, params?)

@xoong/data const client = getClient();

data, status (idle, loading, success, error) and error are signals: read them inside an effect or a template. retain() and release() keep the query alive while you observe it.

Returns
{ data, status, error, retain(), release(), invalidate(), fetch() }

Parameters

NameTypeWhat it is
operationstringstringAn operationId or GET /path.
paramsobjectobjectPath and query parameters.

Example

const query = getClient().query('listPeople', { limit: 5 });
query.retain();
effect(() => console.log(query.status.value, query.data.value));
TypeScript declaration
query<K extends Id<Ops>>(op: K, ...params: ParamsArg<Ops[K]>): TypedQuery<Ops[K]>;

client.mutate(operation, params, body?)

@xoong/data const client = getClient();

Resolves with the response. The queries whose path overlaps the mutation are refetched afterwards (see invalidate in configure).

Returns
Promise<response>

Parameters

NameTypeWhat it is
operationstringstringAn operationId or METHOD /path.
paramsobjectobjectPath parameters.
bodyobjectobjectThe request body.

Example

await getClient().mutate('createPerson', {}, { name: 'Ada Lovelace', email: 'ada@example.org' });
TypeScript declaration
mutate<K extends Id<Ops>>(op: K, params: Ops[K]['params'], ...body: [Ops[K]['body']] extends [never] ? [] : [body: Ops[K]['body']]): Promise<Ops[K]['response']>;

Guide Data and tables

client.seed(operation, params, data)

@xoong/data const client = getClient();

As if the GET had run: no request is made until the result is stale. For data embedded in the page.

Returns
the data

Parameters

NameTypeWhat it is
operationstringstringAn operationId.
paramsobjectobjectThe parameters the GET would have had.
dataobjectobjectThe response.

Example

getClient().seed('listPeople', { limit: 5 }, { items: [], total: 0 });
TypeScript declaration
seed<K extends Id<Ops>>(op: K, params: Ops[K] extends { params: infer P } ? P : never, data: Ops[K]['response']): Ops[K]['response'];

client.stream(operation, params, body?, { signal }?)

@xoong/data const client = getClient();

Each event is { event, data, json, id }; json is data parsed when it is JSON. Pass an AbortSignal to stop.

Returns
AsyncGenerator<SseEvent>

Parameters

NameTypeWhat it is
operationstringstringAn operationId.
paramsobjectobjectPath parameters.
bodyobjectobjectThe request body.

Example

for await (const event of getClient().stream('sendMessage', {}, { text: 'Hello' })) {
  console.log(event.json ?? event.data);
}
TypeScript declaration
stream<K extends Id<Ops>>(op: K, params: Ops[K]['params'], body?: Ops[K]['body'], options?: { signal?: AbortSignal }): AsyncGenerator<SseEvent>;

client.invalidateAll()

@xoong/data const client = getClient();

Views on screen fetch again.

Returns
void

Example

getClient().invalidateAll();
TypeScript declaration
invalidateAll(): void;

client.reset()

@xoong/data const client = getClient();

For a logout or another user: requests in flight are cancelled, cached queries and entities are dropped, and what a mounted view shows is fetched again. Mutations already sent are not cancelled.

Returns
void

Example

configure({ api: operations, onUnauthorized: () => { getClient().reset(); router.navigate('/login'); } });
TypeScript declaration
reset(): void;

derived(({ all, get }) => value, client?)

@xoong/data import { derived } from '@xoong/data';

all('Order') lists every loaded entity of a type, get(name, id) returns one. It recomputes when they change and never makes a request: fetching is declared on a view.

Returns
Signal

Example

import { derived } from '@xoong/data';

const openOrders = derived(({ all }) => all('Order').filter((o) => o.status === 'open').length);
TypeScript declaration
function derived<T>(fn: (ctx: { all(name: string): any[]; get(name: string, id: string | number): any }) => T, client?: Client<any>): Signal<T>;

connectStream({ client, url, EventSource?, onStatus?, onError? })

@xoong/data import { connectStream } from '@xoong/data';

The events are patch, upsert, delete and resync. configure({ streamUrl }) does this for you when a view has live.

Returns
{ close() }

Example

import { connectStream, getClient } from '@xoong/data';

const stream = connectStream({ client: getClient(), url: '/api/events' });
TypeScript declaration
function connectStream(options: { client: Client<any>; url: string; EventSource?: typeof EventSource; onStatus?: (s: 'open' | 'error' | 'closed') => void; onError?: (e: Error) => void }): { close(): void };

class XoongError extends Error

@xoong/data import { XoongError } from '@xoong/data';

Built from problem+json or { error: { code, message } }. Fields: status, title, detail, code, retryAfterS, operation, and fields, the messages by field ({ email: 'is not valid' }).

Example

document.addEventListener('xo-error', (event) => console.warn(event.detail.status, event.detail.detail));
TypeScript declaration
class XoongError extends Error {
  status: number;
  title?: string;
  detail?: string;
  code?: string;
  retryAfterS?: number;
  problem?: any;
  operation?: string;
  /** Per-field messages when the server reports them (`{ email: 'is not valid' }`). */
  fields: Record<string, string>;
}

setFormat(name, fn)

@xoong/data import { setFormat } from '@xoong/data/view';

Then {price | currency} works in any view. The function receives the value and the active language. A format that throws shows the plain value.

Returns
void

Parameters

NameTypeWhat it is
namestringstringThe name used after |.
fnfunctionfunction(value, locale) => string.

Example

import { setFormat } from '@xoong/data/view';

setFormat('currency', (value, locale) => new Intl.NumberFormat(locale, { style: 'currency', currency: 'EUR' }).format(value));
TypeScript declaration
export function setFormat(name: string, fn: (value: any, locale?: string) => string): void

Guide Recipes

confirm(message, options?)

@xoong/ui import { confirm } from '@xoong/ui';

The built-in dialog, a native modal. Without a kind the confirming button is red, as for something that cannot be undone. Escape answers false.

Returns
Promise<boolean>

Parameters

NameTypeWhat it is
titlestringstringA heading above the message.
kindstringstringinfo, success, warning or danger: an icon and the matching colour.
detailstringstringA second, quieter line.
okstringstringThe text of the confirming button.
cancelstringstringThe text of the cancelling button.

Example

import { confirm } from '@xoong/ui';

if (await confirm('Delete 3 items?', { kind: 'danger', ok: 'Delete' })) remove();
TypeScript declaration
function confirm(message: string, options?: DialogLook & { ok?: string; cancel?: string }): Promise<boolean>;

Guide Dialogs and modals

alert(message, options?)

@xoong/ui import { alert } from '@xoong/ui';

One button; resolves when the dialog is closed. kind defaults to info.

Returns
Promise<void>

Parameters

NameTypeWhat it is
titlestringstringA heading above the message.
kindstringstringinfo, success, warning or danger: an icon and the matching colour.
detailstringstringA second, quieter line.
okstringstringThe text of the button.

Example

await alert('Saved', { kind: 'success' });
TypeScript declaration
function alert(message: string, options?: DialogLook & { ok?: string }): Promise<void>;

Guide Dialogs and modals

prompt(message, options?)

@xoong/ui import { prompt } from '@xoong/ui';

Resolves with the text, or null when cancelled. The field is native, so required, pattern and type are checked by the browser.

Returns
Promise<string | null>

Parameters

NameTypeWhat it is
titlestringstringA heading above the message.
kindstringstringinfo, success, warning or danger: an icon and the matching colour.
detailstringstringA second, quieter line.
valuestringstringThe starting text.
labelstringstringThe accessible name of the field.
placeholderstringstringHint text of the empty field.
typestringstringThe input type.
requiredbooleanbooleanThe field must be filled.
patternstringstringA pattern the text must match.
okstringstringThe text of the confirming button.
cancelstringstringThe text of the cancelling button.

Example

const name = await prompt('Name this view', { value: 'My view', required: true });
TypeScript declaration
function prompt(message: string, options?: DialogLook & { value?: string; label?: string; placeholder?: string; type?: string; required?: boolean; pattern?: string; ok?: string; cancel?: string }): Promise<string | null>;

Guide Dialogs and modals

ask(message, { buttons, ...look })

@xoong/ui import { ask } from '@xoong/ui';

Resolves with the value of the button pressed; Escape answers with the value of the one marked cancel, or null when none is.

Returns
Promise<unknown>

Parameters

NameTypeWhat it is
buttonsarrayarrayRequired. Each button is { label, value?, primary?, danger?, cancel? }.
titlestringstringA heading above the message.
kindstringstringinfo, success, warning or danger: an icon and the matching colour.
detailstringstringA second, quieter line.

Example

const answer = await ask('Save changes?', {
  buttons: [{ label: 'Discard', value: 'no' }, { label: 'Save', value: 'yes', primary: true }],
});
TypeScript declaration
function ask(message: string, options: DialogLook & { buttons: DialogChoice[] }): Promise<unknown>;

Guide Dialogs and modals

confirmLabels.ok, confirmLabels.cancel

@xoong/ui import { confirmLabels } from '@xoong/ui';

Replace them for another wording or another language: confirmLabels.ok = 'Delete'.

Example

import { confirmLabels } from '@xoong/ui';

confirmLabels.ok = 'Yes, delete';
TypeScript declaration
const confirmLabels: { ok: string; cancel: string };

Guide Dialogs and modals

setConfirm(fn)

@xoong/ui import { setConfirm } from '@xoong/ui';

For xo-confirm and the question asked when a form with unsaved changes is left. fn(message, context) returns true (or a promise of it) to go on.

Returns
void

Example

import { setConfirm } from '@xoong/ui';

setConfirm((message) => myDialog.ask(message));
TypeScript declaration
function setConfirm(fn: (message: string, context?: ConfirmContext) => boolean | Promise<boolean>): void;

Guide Dialogs and modals

toast.show({ message, kind?, action?, timeout? })

@xoong/ui import { toast } from '@xoong/ui';

A message that asks nothing and may offer one action, for example Undo. It is the same notice xo-notice shows.

Returns
void

Parameters

NameTypeWhat it is
messagestringstringThe text.
kindstringstringsuccess or info (the default).
actionobjectobject{ label, run }: a button, for example Undo.
timeoutnumbernumberSeconds before it hides: 6 unless <xo-toast timeout> says otherwise; 0 keeps it until it is dismissed.

Example

import { toast } from '@xoong/ui';

toast.show({ message: 'Published 3', action: { label: 'Undo', run: undo } });
TypeScript declaration
show(notice: { message: string; kind?: string; action?: { label: string; run: () => unknown }; timeout?: number }): void;

Guide Dialogs and modals

router.navigate(path, { replace? }?)

@xoong/ui import { router } from '@xoong/ui';

The path is relative to the router's base. Resolves with true when the navigation happened (a guard or a veto may stop it).

Returns
Promise<boolean>

Parameters

NameTypeWhat it is
pathstringstringFor example /users/3.
replacebooleanbooleanReplace the history entry instead of adding one.

Example

import { router } from '@xoong/ui';

await router.navigate('/users/3');
TypeScript declaration
navigate(path: string, options?: { replace?: boolean }): Promise<boolean>;

Guide Router and page lifecycle

router.guard((to, from) => result)

@xoong/ui import { router } from '@xoong/ui';

Return true (or nothing) to continue, false to cancel, or a path to redirect there. Returns a function that removes the guard.

Returns
function

Example

const stop = router.guard((to) => (loggedIn() || to.path === '/login' ? true : '/login'));
TypeScript declaration
guard(fn: (to: RouteLocation, from: RouteLocation | null) => GuardResult | Promise<GuardResult>): () => void;

Guide Router and page lifecycle

router.beforeNavigate((to, from, type) => boolean)

@xoong/ui import { router } from '@xoong/ui';

Runs for links, back and forward, and navigate(); type is link, api or popstate. Return false to stay. Returns a function that removes it.

Returns
function

Example

router.beforeNavigate(() => !editor.dirty);
TypeScript declaration
beforeNavigate(fn: (to: RouteLocation, from: RouteLocation | null, type: 'link' | 'api' | 'popstate') => boolean | void | Promise<boolean | void>): () => void;

Guide Router and page lifecycle

router.current

@xoong/ui import { router } from '@xoong/ui';

{ path, params, query, url, route }, or null before the first navigation.

Example

console.log(router.current?.params.id);
TypeScript declaration
readonly current: RouteLocation | null;

Guide Router and page lifecycle

configureI18n({ locale?, fallback?, messages?, load? })

@xoong/i18n import { configureI18n } from '@xoong/i18n';

Resolves when the catalogs of the language are loaded.

Returns
Promise<void>

Parameters

NameTypeWhat it is
localestringstringThe active language.
fallbackstringstringThe language to use for a missing message.
messagesobjectobjectCatalogs by language: { vi: { key: 'message' } }.
loadfunctionfunction(code) => catalog: loads a catalog on demand.

Example

import { configureI18n } from '@xoong/i18n';

configureI18n({ locale: 'en', fallback: 'en', load: (code) => import(`/i18n/${code}.json`) });
TypeScript declaration
function configureI18n(options?: { locale?: string; fallback?: string; messages?: Record<string, Record<string, string>>; load?: (code: string) => Promise<any> | any }): void;

Guide Recipes

setLocale(code)

@xoong/i18n import { setLocale } from '@xoong/i18n';

Loads its catalog first, so the page switches once, then updates <html lang dir>. The last call wins.

Returns
Promise<void>

Example

await setLocale('vi');
TypeScript declaration
function setLocale(code: string): Promise<void>;

Guide Recipes

t(key, args?, fallbackText?)

@xoong/i18n import { t } from '@xoong/i18n';

The message in the active language; fallbackText is shown when the key is unknown, otherwise the key.

Returns
string

Parameters

NameTypeWhat it is
keystringstringThe message key.
argsobjectobjectValues for {placeholders} and plural forms.
fallbackTextstringstringShown when no language has the key.

Example

t('articles.count', { count: 3 });
TypeScript declaration
function t(key: string, args?: Record<string, any>, fallbackText?: string): string;

Guide Recipes

locale.value

@xoong/i18n import { locale } from '@xoong/i18n';

Read it inside an effect or a template; set it with setLocale.

Example

effect(() => console.log('language:', locale.value));
TypeScript declaration
const locale: Signal<string>;

addMessages(code, messages)

@xoong/i18n import { addMessages } from '@xoong/i18n';

Merged over what is there.

Returns
void

Example

addMessages('vi', { 'home.title': 'Chào mừng' });
TypeScript declaration
function addMessages(code: string, messages: Record<string, string>): void;

onLocaleChange(fn)

@xoong/i18n import { onLocaleChange } from '@xoong/i18n';

Not for the current language. Returns a function that stops it.

Returns
function

Example

const stop = onLocaleChange((code) => console.log('now', code));
TypeScript declaration
function onLocaleChange(fn: (code: string) => void): () => void;

detectLocale(supported, { default?, query?, storageKey? }?)

@xoong/i18n import { detectLocale } from '@xoong/i18n';

The one in ?lang=, a saved choice, then the browser's preferences, then default (the first supported one).

Returns
string

Example

const code = detectLocale(['en', 'vi']);
TypeScript declaration
function detectLocale(supported: string[], options?: { default?: string; query?: string; storageKey?: string }): string;

signal(value)

@xoong/core import { signal } from '@xoong/core';

.value reads (tracked inside an effect, a computed or a template function) and writes; .peek() reads without tracking.

Returns
Signal

Example

import { signal } from '@xoong/core';

const count = signal(0);
count.value++;
TypeScript declaration
function signal<T>(value: T): Signal<T>;

computed(fn)

@xoong/core import { computed } from '@xoong/core';

It recomputes lazily, when something it read has changed.

Returns
ReadonlySignal

Example

const double = computed(() => count.value * 2);
TypeScript declaration
function computed<T>(fn: () => T): ReadonlySignal<T>;

effect(fn)

@xoong/core import { effect } from '@xoong/core';

fn may return a cleanup function. For side effects only: never fetch data in an effect, declare the operation on a view. Returns a function that stops it.

Returns
function

Example

const stop = effect(() => { document.title = `${count.value} items`; });
TypeScript declaration
function effect(fn: () => void | (() => void)): () => void;

batch(fn)

@xoong/core import { batch } from '@xoong/core';

The effects run once, after the outermost batch ends.

Returns
what fn returns

Example

batch(() => { first.value = 'Ada'; last.value = 'Lovelace'; });
TypeScript declaration
function batch<T>(fn: () => T): T;

untracked(fn)

@xoong/core import { untracked } from '@xoong/core';

Inside fn a read is not tracked by the running effect or computed.

Returns
what fn returns

Example

effect(() => { const n = untracked(() => count.value); });
TypeScript declaration
function untracked<T>(fn: () => T): T;

html`...`

@xoong/core import { html } from '@xoong/core';

A function in a child or attribute position is reactive. name=${v} sets an attribute, ?name=${v} a boolean attribute, .name=${v} a property, @event=${fn} a listener and style:--x=${v} a CSS custom property.

Returns
TemplateResult

Example

html`<button class=${() => cls.value} @click=${save}>${() => label.value}</button>`
TypeScript declaration
function html(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult;

render(result, container)

@xoong/core import { render } from '@xoong/core';

Replaces the children of container. Returns the dispose function.

Returns
function

Example

const dispose = render(html`<p>${() => count.value}</p>`, document.body);
TypeScript declaration
function render(result: TemplateResult, container: Element | DocumentFragment): () => void;

class XoElement extends HTMLElement

@xoong/core import { XoElement } from '@xoong/core';

static props declares attributes that are also properties and signals. Implement render() when the element owns its content, or connected() to enhance markup that is already there.

Example

class UserCard extends XoElement {
  static props = { name: String, age: Number };
  render() { return html`<h3>${() => this.name}</h3>`; }
}
define('user-card', UserCard);
TypeScript declaration
class XoElement extends HTMLElement {
  /** Attributes <-> signals. The attribute is the kebab-case of the name (`maxSize` is `max-size`). */
  static props: Record<string, XoPropType>;
  static get observedAttributes(): string[];

  constructor();

  /** The signal behind a prop (reading `this.name` already tracks it). */
  _sig(name: string): Signal<any>;

  attributeChangedCallback(attr: string, oldValue: string | null, value: string | null): void;
  connectedCallback(): void;
  disconnectedCallback(): void;

  /**
   * Implement it only if the element OWNS its content: the result is rendered into the element when it connects.
   * To enhance server-rendered HTML in place, implement `connected()` instead.
   */
  render?(): TemplateResult | null | undefined | void;
  /** Runs after `render()` when the element is connected. */
  connected?(): void;
  /** Runs after the element's effects and rendering were disposed, when it is disconnected. */
  disconnected?(): void;

  /** Resolves with the element when its children are there: at once once the document is parsed, else after the parser reached the end. Await it in `connected()`. */
  ready(): Promise<this>;

  /** A side effect bound to this element's lifetime: disposed on disconnect. */
  effect(fn: () => void | (() => void)): void;

  /** Dispatch a bubbling, composed CustomEvent. */
  emit<T = unknown>(name: string, detail?: T): void;
}

Guide Your own control

define(tag, class)

@xoong/core import { define } from '@xoong/core';

A tag that is already defined is left alone. The tag must contain a hyphen.

Returns
void

Example

define('user-card', UserCard);
TypeScript declaration
function define(tag: string, cls: typeof XoElement): void;

Guide Your own control

class XoControl extends XoElement

@xoong/core import { XoControl } from '@xoong/core';

Gives the element name, required, disabled and readonly, a value, validation and a place in the form model. Every field control of @xoong/controls extends it.

Example

class RatingInput extends XoControl { static props = { ...XoControl.props, max: Number }; }
TypeScript declaration
class XoControl extends XoElement {
  static formAssociated: boolean;
  static props: { name: StringConstructor; required: BooleanConstructor; disabled: BooleanConstructor; readonly: BooleanConstructor };

  constructor();

  /** The form field name. */
  name: string;
  required: boolean;
  disabled: boolean;
  readonly: boolean;

  // ---- value
  /** The value. A string by default; a subclass may hold anything and override `serialize`. Reading it is tracked. */
  get value(): any;
  set value(v: any);
  /** Set the value programmatically (like `input.value = x`: no events). Before the control has started, it also becomes the initial value. */
  setValue(v: any): void;
  /** The person changed the value: sets it and fires `input`. */
  userInput(v: any): void;
  /** The person finished a change: fires `change`. */
  userChange(): void;
  /** What the form submits for a value: a string, a File, FormData, or null for nothing. Override for structured values. */
  serialize(v: any): string | File | FormData | null;
  /** Whether a value counts as "nothing entered" for `required`. Override for structured values. */
  isEmpty(v: any): boolean;
  /** Validation hook: return an error message for an invalid value, a falsy value when it is valid. Runs on every change. */
  validate(value: any): string | null | undefined | false;

  // ---- state (reading these inside a template or an effect is tracked)
  /** The value the control started with (what `reset` goes back to and `dirty` compares with). */
  get initialValue(): any;
  get dirty(): boolean;
  get pristine(): boolean;
  get touched(): boolean;
  /** True when there is no validation error. */
  get valid(): boolean;
  get invalid(): boolean;
  /** Mark the control as visited (blur does this). */
  markTouched(): void;

  // ---- validation
  /** The message to show, or '' when valid. */
  get validationMessage(): string;
  /** Disabled by its own attribute or by a disabled fieldset. */
  get isDisabled(): boolean;
  get willValidate(): boolean;
  get validity(): XoValidity;
  /** Set your own error (cross-field rules, server errors). An empty string clears it. */
  setCustomValidity(message: string | null | undefined): void;
  /** True when valid (or not validated); otherwise fires a cancelable `invalid` event and returns false. */
  checkValidity(): boolean;
  /** Like `checkValidity`, and on failure marks the control touched and focuses `focusTarget`. */
  reportValidity(): boolean;
  /** The element inside that takes focus (and anchors the validation bubble). */
  get focusTarget(): HTMLElement | null;

  // ---- reset
  /** Back to the initial value, untouched. The form calls this on reset. */
  reset(): void;
  /** Make the current value the new baseline, as after a successful save: `dirty` becomes false. */
  markPristine(): void;

  // ---- form-associated callbacks
  formResetCallback(): void;
  formDisabledCallback(disabled: boolean): void;
  formStateRestoreCallback(state: any): void;
  /** The form this control belongs to (through ElementInternals, else the closest `<form>`). */
  get form(): HTMLFormElement | null;
}

Guide Your own control

createForm({ schema?, value?, rules?, serialize? })

@xoong/forms import { createForm } from '@xoong/forms';

Holds the value, the errors and the dirty state by path. <form xo-submit> creates one for you; use this in tests and for a form that is not in a view.

Returns
FormModel

Parameters

NameTypeWhat it is
schemaobjectobjectA request schema node.
valueobjectobjectThe starting record.
rulesobjectobjectRules by field path; * gets the whole record.
serializefunctionfunction(draft, body) => body: change what is sent.

Example

import { createForm } from '@xoong/forms';

const form = createForm({ rules: { slug: (v) => (/^[a-z-]+$/.test(v) ? '' : 'Lower case letters only') } });
form.set('slug', 'My Page');
TypeScript declaration
function createForm(options?: { schema?: SchemaNode; value?: any; rules?: Rules; serialize?: (draft: any, body: any) => any }): FormModel;

Guide Forms

attach(form, { schema, record?, hadRecord? })

@xoong/forms import { attach } from '@xoong/forms';

The binder that <form xo-submit> uses, for a form you handle yourself.

Returns
{ model, submit, detach, rescan }

Example

const { model } = attach(document.querySelector('form'), { schema });
TypeScript declaration
function attach(form: HTMLFormElement, options: { schema: SchemaNode; record?: () => any; hadRecord?: boolean }): { model: FormModel; submit(send: (body: any) => Promise<any>): Promise<any>; detach(): void; rescan(): void };

Guide Forms

setMessages({ code: (params) => message })

@xoong/forms import { setMessages } from '@xoong/forms';

For another language or your own wording. Each message receives its parameters, for example { min: 3 }. The codes are required, type, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, minLength, maxLength, pattern, format, enum, minItems, maxItems, uniqueItems and variant. resetMessages() brings the English ones back.

Returns
void

Example

import { setMessages } from '@xoong/forms';

setMessages({ required: () => 'Bắt buộc', minLength: ({ min }) => `Ít nhất ${min} ký tự` });
TypeScript declaration
function setMessages(partial: Record<string, (params: Record<string, any>) => string>): void;

Guide Forms

sanitizeHtml(html, { images? }?)

@xoong/richtext import { sanitizeHtml } from '@xoong/richtext';

Keeps p, h2, h3, strong, em, u, ul, ol, li, blockquote, br, a (safe links only) and img (safe sources only). images: false removes <img>. It needs a DOM.

Returns
string

Example

import { sanitizeHtml } from '@xoong/richtext';

const clean = sanitizeHtml(untrusted, { images: false });
TypeScript declaration
function sanitizeHtml(html: string, options?: { images?: boolean }): string;

Guide Rich text

Template syntax

What goes between braces inside an <xo-view>: paths, formats and view variables. 11 entries.

{path}

A path reads a field of the current item (. is the item itself, a.b is nested, a[0].b indexes) and then the view's p-* parameters. The text is escaped. A javascript: URL becomes about:blank, and on* attributes and srcdoc are not interpolated. {{name}} and \{name} stay as written, and braces around anything that is not a path (JSON, CSS) are left alone.

Example

<xo-view get="getPerson" p-id="1">
  <h2>{name}</h2>
  <a href="mailto:{email}">{email}</a>
</xo-view>

Guide Data and tables

{path | format}

The formats are date, datetime, time, relative, number, json and localized. They use Intl and the page language. A value that cannot be formatted is shown as it is. Add your own with setFormat(). An unknown name is reported once on the console and the plain value is shown.

FormatShows
dateA calendar date in the page language: Nov 25, 2026.
datetimeDate and time.
timeTime of day.
relative"3 days ago", "tomorrow".
numberA number with the language's separators.
jsonThe value as JSON text.
localizedA value that is an object by language ({ en, vi }): the active language, then its base language, then English, then any filled one.

Example

<xo-view get="listPeople">
  <p xo-each="items">{name}: {createdAt | relative}, {total | number}</p>
</xo-view>

Guide Data and tables

$loading, $refreshing

$loading is true while a request runs and there is nothing to show yet. $refreshing is true while a request runs and the previous result is still on screen (a sort or a page loads), so the view does not collapse. The view also carries data-refreshing for CSS.

Example

<xo-view get="listPeople">
  <p xo-if="$loading">Loading…</p>
  <p xo-if="$refreshing">Updating…</p>
</xo-view>

Guide Loading, empty and error

$error

A XoongError with status, code and detail, or nothing when the load worked.

Example

<xo-view get="listPeople">
  <p xo-if="$error">{$error.code}: {$error.detail}</p>
</xo-view>

Guide Loading, empty and error

$index, $n

$index counts from 0 and $n from 1. In nested xo-each loops $i0 (the outer one), $i1, ... name the position at each depth.

Example

<li xo-each="items">{$n}. {name}</li>

Guide Data and tables

$hasPrev, $hasNext, $from, $to

For lists that take limit and offset and answer with total. $from and $to are the numbers of the first and the last row shown (the "1–12 of 48" text).

Example

<xo-view get="listPeople" p-limit="10">
  <button xo-param="offset-=10" xo-if="$hasPrev">Previous</button>
  <span>{$from}–{$to}</span>
  <button xo-param="offset+=10" xo-if="$hasNext">Next</button>
</xo-view>

Guide Data and tables

$selectedCount, $selected

$selectedCount is the number of ticked rows (xo-select), $selected the rows themselves, for xo-body="$selected".

Example

<button xo-click="deletePerson" xo-selected xo-if="$selectedCount">Delete {$selectedCount}</button>

Guide Data and tables

$stream, $streaming

$stream holds the events of the latest streamed call (an operation that answers with server-sent events) and $streaming whether it still runs.

Example

<p xo-if="$streaming">Receiving…</p>

$form

Inside a <form xo-submit>: $form.dirty, $form.valid, $form.submitting, $form.stale, and by field path $form.errors['title.vi'], $form.touched[...], $form.filled[...].

Example

<xo-view get="getPerson" p-id="1">
  <form xo-submit="updatePerson">
    <input name="name" aria-label="Name">
    <span xo-if="$form.dirty">Unsaved changes</span>
    <button>Save</button>
  </form>
</xo-view>

Guide Forms

p-<name>

On <xo-view>: p-id="42" is the path parameter id. p-id="?id" reads ?id= from the page URL and p-id=":id" reads a route parameter. Inside the view {id} reads it.

Example

<xo-view get="getPerson" p-id="?id"><h1>{name}</h1></xo-view>

Guide Data and tables

hidden="{flag}"

When {flag} is the whole value of hidden, disabled, checked, selected, readonly, required, open, multiple, inert, autofocus, controls, loop, muted or reversed, the attribute is present while the value is true (an empty list is false).

Example

<button disabled="{$loading}" xo-refresh>Refresh</button>

Guide Data and tables