Crank Component Authoring
Crank 0.7.8+ is required. This skill was built against 0.7.8. Always check npm for the latest version before generating code, as APIs may have changed.
JSX Template Tag (No Build Step)
Crank provides a jsx tagged template literal that runs directly in the browser with no transpiler, no bundler, and no build step. This is the recommended approach for single-file HTML artifacts, prototypes, and demos.
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Crank App</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import {jsx, renderer} from "https://cdn.jsdelivr.net/npm/@b9g/crank/standalone.js";
function *Counter() {
let count = 0;
const => this.refresh(() => count++);
for ({} of this) {
yield jsx`
<button ${count}</button>
`;
}
}
renderer.render(jsx`<${Counter} />`, document.getElementById("app"));
</script>
</body>
</html>
The standalone module exports both the jsx tag and the DOM renderer in a single import. For full documentation, see the JSX Template Tag guide.
With a Build Step (JSX syntax)
When using a bundler, you can use standard JSX syntax with the @jsxImportSource pragma:
/** @jsxImportSource @b9g/crank */
import {renderer} from "@b9g/crank/dom";
function *Timer({message}) {
let seconds = 0;
// Mount: start interval
const interval = setInterval(() => this.refresh(() => seconds++), 1000);
// Update: loop receives fresh props on each re-render
for ({message} of this) {
yield (
<div>
<p>{message}: {seconds}s</p>
<button => this.refresh(() => seconds = 0)}>Reset</button>
</div>
);
}
// Cleanup: runs on unmount
clearInterval(interval);
}
renderer.render(<Timer message="Elapsed" />, document.getElementById("app"));
Not React — Quick Reference
| React |
Crank |
Why |
onClick |
onclick |
Lowercase DOM event names |
onChange |
onchange |
Lowercase DOM event names |
className |
class |
Standard HTML attributes |
htmlFor |
for |
Standard HTML attributes |
dangerouslySetInnerHTML |
innerHTML |
Direct DOM property |
useState(init) |
let x = init |
Variable in generator scope |
setState(val) |
this.refresh(() => x = val) |
Explicit refresh |
useEffect(fn, []) |
Code before first yield |
Generator mount phase |
useEffect(() => cleanup) |
Code after for loop / this.cleanup(fn) |
Generator cleanup |
useRef(null) |
let el = null + ref={n => el = n} |
Variable + ref prop |
useContext(ctx) |
this.consume(key) |
No Provider components |
<Ctx.Provider value={v}> |
this.provide(key, v) |
Called in generator body |
API Surface
These are the complete public exports.
// Core — components, elements, and rendering infrastructure
import {
createElement, // Create an element (called automatically by JSX)
Fragment, // Group children without a wrapper node ("")
Portal, // Render children into a different root node
Copy, // Reuse the previously rendered child tree
Text, // Render a text node with explicit text prop
Raw, // Insert raw HTML/markup via value prop
Element, // Element class (for type checking)
isElement, // Test if a value is a Crank element
cloneElement, // Clone an element with merged props
Context, // Component context class
Renderer, // Base renderer class (for custom renderers)
} from "@b9g/crank";
// DOM renderer
import {renderer, DOMRenderer} from "@b9g/crank/dom";
// HTML string renderer (SSR)
import {renderer as htmlRenderer, HTMLRenderer} from "@b9g/crank/html";
// JSX template tag (no build step)
import {jsx, html} from "@b9g/crank/jsx-tag";
// Standalone — re-exports everything above in one import
import {jsx, html, Fragment, renderer, domRenderer, htmlRenderer, DOMRenderer, HTMLRenderer} from "@b9g/crank/standalone";
Context methods (this inside generator components)
this.refresh(callback?) // Mutate state and re-render
this.schedule(callback?) // Run after this render commits (once)
this.after(callback?) // Run after every render commits
this.flush(callback?) // Run after the entire render tree commits
this.cleanup(callback?) // Run on unmount
this.consume(key) // Read a provided value from an ancestor
this.provide(key, value) // Provide a value to descendants
this.addEventListener(type, listener) // Listen for DOM or custom events
this.removeEventListener(type, listener) // Remove an event listener
this.dispatchEvent(event) // Dispatch an event up the tree
Philosophy
Crank components are plain JavaScript functions and generators. State is variables. Props are values. Updates are explicit.
- The framework preserves generator scope across yields — local variables are your state.
this.refresh(() => { ... }) atomically mutates state and triggers a re-render.
- Props are plain values — destructure and transform them freely.
- Shared logic is plain classes, functions, and modules.
JSX Template Tag — Quick Reference
jsx`
<!-- host element -->
<div />
<!-- component element with shorthand close -->
<${Component}>children<//>
<!-- comment-style close -->
<${Component}>children<//Component>
<!-- fragment shorthand -->
<>
<p>first</p>
<p>second</p>
</>
<!-- keyed fragment -->
<${Fragment} key=${id}>
<dt>${term}</dt>
<dd>${definition}</dd>
<//>
<!-- boolean, string, interpolated string, expression, and spread props -->
<input disabled type="text" class="a ${b} c" value=${val} ...${props} />
<!-- conditional child -->
${show && jsx`<${Alert} message=${msg} />`}
<!-- mapped children with keys -->
${items.map((d) => jsx`<li key=${d.id}>${d.name}</li>`)}
<!-- commenting out a tree: expressions inside comments are discarded -->
<!--
<${Component}
<p>${text}</p>
<//>
-->
`
Multiple root elements are supported — the template tag automatically wraps them in a fragment.
References
Read these two files for complete API coverage and idiomatic patterns:
- Component Specification — complete API reference: all component types, lifecycle, context methods, reconciliation, async behavior, special props, JSX modes
- Style Guide — do/don't patterns: component structure, state updates, props, cleanup, refs, error handling
Examples (consult as needed for the relevant task)
- Greeting — Hello world: functional components, props, composition
- TodoMVC — Full CRUD app: custom events, list management, filtering, localStorage
- Hacker News — Data dashboard: async fetching, hash routing, recursive tree rendering
- Password Strength — Interactive form widget: real-time validation, derived state, visual feedback
- Wizard — Multi-step form: stateful navigation, FormData collection, generator lifecycle
- Animated Letters — Animation: CSS transitions, exit animations, requestAnimationFrame
Additional Guides (for deeper reading on specific topics)
- Getting Started
- Elements
- Components
- Handling Events
- Async Components
- Special Props and Components
- Lifecycles
- Hydration
- Reusable Logic
- Working with TypeScript
- JSX Template Tag
- Reference for React Developers
- Custom Renderers
Blog Posts
- Introducing Crank
- Why Be Reactive?
Other
Source: bikeshaving/crank — distributed by TomeVault.
1---2name: crank-component-authoring3description: Build web apps, dashboards, landing pages, widgets, calculators, forms, quizzes, charts, visualizations, animations, dynamic SVGs, MathML equations, blogs, or games as single-file HTML with no build step, using Crank.js, an elegant UI framework which allows you to write components with plain JavaScript functions, generators, and promises. Use when user asks to create something interactive, build a single-file HTML app, or start a greenfield frontend project. Always trigger when converting code from React, Vue, Svelte, Solid, or any other web framework to Crank.js, when the user mentions Crank by name, or when comparing different web/UI frameworks. Not for projects already using other frameworks. Use when this capability is needed.4---56# Crank Component Authoring78**Crank 0.7.8+ is required.** This skill was built against 0.7.8. Always check npm for the latest version before generating code, as APIs may have changed.910## JSX Template Tag (No Build Step)1112Crank provides a `jsx` tagged template literal that runs directly in the browser with no transpiler, no bundler, and no build step. This is the recommended approach for single-file HTML artifacts, prototypes, and demos.1314```html15<!DOCTYPE html>16<html lang="en-US">17<head>18 <meta charset="utf-8" />19 <meta name="viewport" content="width=device-width" />20 <title>Crank App</title>21</head>22<body>23 <div id="app"></div>24 <script type="module">25 import {jsx, renderer} from "https://cdn.jsdelivr.net/npm/@b9g/crank/standalone.js";2627 function *Counter() {28 let count = 0;29 const onclick = () => this.refresh(() => count++);3031 for ({} of this) {32 yield jsx`33 <button onclick=${onclick}>Count: ${count}</button>34 `;35 }36 }3738 renderer.render(jsx`<${Counter} />`, document.getElementById("app"));39 </script>40</body>41</html>42```4344The standalone module exports both the `jsx` tag and the DOM `renderer` in a single import. For full documentation, see the [JSX Template Tag guide](references/11-jsx-template-tag.md).4546## With a Build Step (JSX syntax)4748When using a bundler, you can use standard JSX syntax with the `@jsxImportSource` pragma:4950```jsx51/** @jsxImportSource @b9g/crank */52import {renderer} from "@b9g/crank/dom";5354function *Timer({message}) {55 let seconds = 0;56 // Mount: start interval57 const interval = setInterval(() => this.refresh(() => seconds++), 1000);5859 // Update: loop receives fresh props on each re-render60 for ({message} of this) {61 yield (62 <div>63 <p>{message}: {seconds}s</p>64 <button onclick={() => this.refresh(() => seconds = 0)}>Reset</button>65 </div>66 );67 }6869 // Cleanup: runs on unmount70 clearInterval(interval);71}7273renderer.render(<Timer message="Elapsed" />, document.getElementById("app"));74```7576## Not React — Quick Reference7778| React | Crank | Why |79|---|---|---|80| `onClick` | `onclick` | Lowercase DOM event names |81| `onChange` | `onchange` | Lowercase DOM event names |82| `className` | `class` | Standard HTML attributes |83| `htmlFor` | `for` | Standard HTML attributes |84| `dangerouslySetInnerHTML` | `innerHTML` | Direct DOM property |85| `useState(init)` | `let x = init` | Variable in generator scope |86| `setState(val)` | `this.refresh(() => x = val)` | Explicit refresh |87| `useEffect(fn, [])` | Code before first `yield` | Generator mount phase |88| `useEffect(() => cleanup)` | Code after `for` loop / `this.cleanup(fn)` | Generator cleanup |89| `useRef(null)` | `let el = null` + `ref={n => el = n}` | Variable + ref prop |90| `useContext(ctx)` | `this.consume(key)` | No Provider components |91| `<Ctx.Provider value={v}>` | `this.provide(key, v)` | Called in generator body |9293## API Surface9495These are the complete public exports.9697```js98// Core — components, elements, and rendering infrastructure99import {100 createElement, // Create an element (called automatically by JSX)101 Fragment, // Group children without a wrapper node ("")102 Portal, // Render children into a different root node103 Copy, // Reuse the previously rendered child tree104 Text, // Render a text node with explicit text prop105 Raw, // Insert raw HTML/markup via value prop106 Element, // Element class (for type checking)107 isElement, // Test if a value is a Crank element108 cloneElement, // Clone an element with merged props109 Context, // Component context class110 Renderer, // Base renderer class (for custom renderers)111} from "@b9g/crank";112113// DOM renderer114import {renderer, DOMRenderer} from "@b9g/crank/dom";115116// HTML string renderer (SSR)117import {renderer as htmlRenderer, HTMLRenderer} from "@b9g/crank/html";118119// JSX template tag (no build step)120import {jsx, html} from "@b9g/crank/jsx-tag";121122// Standalone — re-exports everything above in one import123import {jsx, html, Fragment, renderer, domRenderer, htmlRenderer, DOMRenderer, HTMLRenderer} from "@b9g/crank/standalone";124```125126### Context methods (`this` inside generator components)127128```js129this.refresh(callback?) // Mutate state and re-render130this.schedule(callback?) // Run after this render commits (once)131this.after(callback?) // Run after every render commits132this.flush(callback?) // Run after the entire render tree commits133this.cleanup(callback?) // Run on unmount134this.consume(key) // Read a provided value from an ancestor135this.provide(key, value) // Provide a value to descendants136this.addEventListener(type, listener) // Listen for DOM or custom events137this.removeEventListener(type, listener) // Remove an event listener138this.dispatchEvent(event) // Dispatch an event up the tree139```140141## Philosophy142143Crank components are plain JavaScript functions and generators. State is variables. Props are values. Updates are explicit.144145- The framework preserves generator scope across yields — local variables are your state.146- `this.refresh(() => { ... })` atomically mutates state and triggers a re-render.147- Props are plain values — destructure and transform them freely.148- Shared logic is plain classes, functions, and modules.149150## JSX Template Tag — Quick Reference151152```js153jsx`154 <!-- host element -->155 <div />156157 <!-- component element with shorthand close -->158 <${Component}>children<//>159160 <!-- comment-style close -->161 <${Component}>children<//Component>162163 <!-- fragment shorthand -->164 <>165 <p>first</p>166 <p>second</p>167 </>168169 <!-- keyed fragment -->170 <${Fragment} key=${id}>171 <dt>${term}</dt>172 <dd>${definition}</dd>173 <//>174175 <!-- boolean, string, interpolated string, expression, and spread props -->176 <input disabled type="text" class="a ${b} c" value=${val} ...${props} />177178 <!-- conditional child -->179 ${show && jsx`<${Alert} message=${msg} />`}180181 <!-- mapped children with keys -->182 ${items.map((d) => jsx`<li key=${d.id}>${d.name}</li>`)}183184 <!-- commenting out a tree: expressions inside comments are discarded -->185 <!--186 <${Component} onclick=${handler}>187 <p>${text}</p>188 <//>189 -->190`191```192193Multiple root elements are supported — the template tag automatically wraps them in a fragment.194195## References196197Read these two files for complete API coverage and idiomatic patterns:1981. [Component Specification](references/spec.bs) — complete API reference: all component types, lifecycle, context methods, reconciliation, async behavior, special props, JSX modes1992. [Style Guide](references/12-crank-style-guide.md) — do/don't patterns: component structure, state updates, props, cleanup, refs, error handling200201## Examples (consult as needed for the relevant task)202- [Greeting](references/greeting.js) — Hello world: functional components, props, composition203- [TodoMVC](references/todomvc.js) — Full CRUD app: custom events, list management, filtering, localStorage204- [Hacker News](references/hackernews.js) — Data dashboard: async fetching, hash routing, recursive tree rendering205- [Password Strength](references/password-strength.js) — Interactive form widget: real-time validation, derived state, visual feedback206- [Wizard](references/wizard.js) — Multi-step form: stateful navigation, FormData collection, generator lifecycle207- [Animated Letters](references/animated-letters.js) — Animation: CSS transitions, exit animations, requestAnimationFrame208209## Additional Guides (for deeper reading on specific topics)210- [Getting Started](references/01-getting-started.md)211- [Elements](references/02-elements.md)212- [Components](references/03-components.md)213- [Handling Events](references/04-handling-events.md)214- [Async Components](references/05-async-components.md)215- [Special Props and Components](references/06-special-props-and-components.md)216- [Lifecycles](references/07-lifecycles.md)217- [Hydration](references/08-hydration.md)218- [Reusable Logic](references/09-reusable-logic.md)219- [Working with TypeScript](references/10-working-with-typescript.md)220- [JSX Template Tag](references/11-jsx-template-tag.md)221- [Reference for React Developers](references/13-reference-for-react-developers.md)222- [Custom Renderers](references/14-custom-renderers.md)223224## Blog Posts225- [Introducing Crank](references/2020-04-15-introducing-crank.md)226- [Why Be Reactive?](references/2025-08-20-why-be-reactive.md)227228## Other229- [Changelog](references/CHANGELOG.md)230231---232> Source: [bikeshaving/crank](https://github.com/bikeshaving/crank) — distributed by [TomeVault](https://tomevault.io).233<!-- tomevault:4.0:skill_md:2026-06-24 -->