clay.js
  1. ClayJS
  2. Build
  3. Recipes

Recipes

Complete patterns for common apps: kanban, block writer, finance and more.

Kanban with card detail#

examples/kanban.html. Plugins sync,sortable,undo,indicator, satellites clay-options and clay-ui.

  • #board > .columns[sortable="columns"] > section.column[data-id], each with a handle and an h2, then ol.cards[sortable="cards"] of li.card[data-id].
  • Each card holds a hidden .detail: an editable description, a checklist of li[data-id] with persist checkboxes, an ol.activity of li[data-at], and edit-only actions.
  • Count per column and progress per card are editor-ui spans re-rendered on every mutation.
  • The open card is in the hash and painted by the view-state style (Detail views: the record is the modal). The filter is in the hash too.
  • clay:sorted logs “Moved to Column” in the card when it changes column.
  • Templates: card, checklist item, column, chrome.

Extend with labels (a settings element listing <li data-label="bug" data-color="…">, cards carrying data-labels="bug ui"), due dates (data-due plus a hash filter “due this week”), WIP limits (an attribute on the column, a warning in an editor-ui badge), and a list view (a projection or display: contents).

With sapjs, the counts and progress badges become paints, the filters an each= view or a frozen stylesheet, and checklists nested items= lists with a shared blueprint: sapjs guide section Chrome builds a smaller version as a complete, tested app.

Block writer#

examples/writer.html. Plugins sync,sortable,undo,upload,indicator, satellite clay-ui.

One editable per block, not one big editor. The document is #blocks[sortable="blocks"], a list of sibling .block[data-id][data-type] elements:

data-typeContent
text.body[editable="toolbar-on-select"]: paragraphs, H2/H3, lists, quotes
calloutthe same, styled as a callout
todoul.todos of li[data-id] with a persist checkbox and an editmode:contenteditable span; Enter adds the next item, Backspace on an empty item removes it
divider<hr>
image<figure> with <img> and an editmode:contenteditable caption

This model gives drag, block menus, non-rich blocks and per-block Squire undo (which limits the peer-rollback trap), at the cost of selection and Enter-to-split across blocks.

  • A gutter (handle, + insert, ⋯ block menu) is cloned from a template into each block as editor-ui, with contenteditable="false". Because the handles are injected, the script sets Sortable’s handle option (sortable: drag to reorder).
  • The menu is one editor-ui element positioned under the clicked button. Insert actions clone a block template and give it an id; block actions duplicate (re-iding every descendant, Element identity: data-id and id) or delete.
  • The slash menu opens on keyup / when the caret’s paragraph contains only /; choosing an item removes the / (and the block, if it held nothing else) and inserts after it.
  • Images come from a hidden file input in chrome or from a pasted screenshot (pasteImage), through clay.upload with the data: fallback.
  • The outline (H1 to H3 across blocks) and the word count are editor-ui projections; the outline rebuilds only when its signature changes.

Missing pieces to add on the same model: a table block (<table> with editmode:contenteditable cells and your own Tab and add-row code), a code block (<pre><code editmode:contenteditable> with a copy button in chrome), a toggle (<details>, noting open saves), anchor ids on headings (assign id from text on blur, not on load).

Finance tracker#

examples/finance.html. Plugins sync,undo,indicator&exclude=richclay, satellites clay-options, clay-ui, sap.js.

  • main#app[sap] with a summary of no-dirty outputs (budgeted, spent, left), a categories table (items="categories", each row a name, a persisted budget input, computed spent and left, a progress bar via css:used) and a transactions table (items="txns", each row date, memo, category with a datalist, amount).
  • Category spent: calc:spent="sum(root.txns.filter(t => t.cat === item.name), 'amount')".
  • Search: a transient bound input, calc:match and show="item.match" per row, hidden stripped from snapshots.
  • Rows get stable ids from claimRowIds, a workaround from before sapjs minted unique ids. A new app writes data-id="" on the blueprint and lets trigger-add mint ids (sap.js: records, views and totals in markup).
  • CSV import (edit only) and export (both modes).
  • The Regions and JSON under sync acknowledge workaround for no-dirty totals.

The authored file already shows every computed value, so sap mounts without changing the record and a reader without JavaScript sees correct totals.

CRM follow-up list#

Table-first. <tr data-id data-stage="lead" data-next="2026-10-14"> rows with editmode:contenteditable name and company cells, a persisted <select> for stage, a persisted <input type="date"> for the next contact, and an editable notes cell or a hidden notes element shown in the record-as-modal detail. Projections into editor-ui: “due today” (rows whose data-next is today or earlier) and a pipeline board (columns by stage, each card a link to the row’s detail). Totals by stage with sap if deals carry amounts. mailto: links for contact. CSV import merges by email or id. Single owner.

Flashcards#

Decks as <section data-id> records, cards as <article data-id data-due data-interval data-ease> with editmode:contenteditable front and back. Study mode is tab-local: the hash holds the deck and the current card, a runtime stylesheet shows one card and hides the back until revealed. Grading writes the new data-due, data-interval and data-ease on the card (an SM-2 style schedule), inside one undo commit. The “due today” count is an editor-ui projection.

Typed table (a spreadsheet you can be honest about)#

A fixed schema of named columns, rows as <tr item data-id> in a sap items list, inputs bound and persisted per cell, computed columns with calc:, totals with sum, filters by hiding, CSV in and out. Tab between cells with a keydown handler on the table. A few hundred to two thousand rows per file.

Slides#

One <section class="slide" data-id data-layout="title|bullets|image|quote"> per slide in a 16:9 box, editable text, sortable to reorder, images through upload. Present mode is tab-local (hash #present=3): fullscreen, scroll-snap, arrow keys, speaker notes in an <aside> hidden while presenting. PDF through @page { size: 16in 9in } and the browser’s print.

Content site#

Without the CMS: editable regions for copy, upload and quickcrop for images, sortable lists for features. With the CMS: a rules tag and ?plugins=cms (cms: content-managed pages).

Part of the ClayJS complete guideDownload the whole guide as Markdown