- ClayJS
- Build
- 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 anh2, thenol.cards[sortable="cards"]ofli.card[data-id].- Each card holds a hidden
.detail: aneditabledescription, a checklist ofli[data-id]withpersistcheckboxes, anol.activityofli[data-at], and edit-only actions. - Count per column and progress per card are
editor-uispans 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:sortedlogs “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-type | Content |
|---|---|
text | .body[editable="toolbar-on-select"]: paragraphs, H2/H3, lists, quotes |
callout | the same, styled as a callout |
todo | ul.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 aseditor-ui, withcontenteditable="false". Because the handles are injected, the script sets Sortable’shandleoption (sortable: drag to reorder). - The menu is one
editor-uielement 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-idandid) 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), throughclay.uploadwith thedata:fallback. - The outline (H1 to H3 across blocks) and the word count are
editor-uiprojections; 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 ofno-dirtyoutputs (budgeted, spent, left), a categories table (items="categories", each row a name, a persisted budget input, computed spent and left, a progress bar viacss: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
transientbound input,calc:matchandshow="item.match"per row,hiddenstripped from snapshots. - Rows get stable ids from
claimRowIds, a workaround from before sapjs minted unique ids. A new app writesdata-id=""on the blueprint and letstrigger-addmint 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-dirtytotals.
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).