Blocks & Elements
The fundamental unit of the framework is the Morphic Block. It represents one program construct (a print statement, a loop, a condition, …) and carries a set of named elements — the visual parts it can show. Which parts it does show is decided later, by modes.
Elements
Section titled “Elements”Elements are declared per block as a plain name: content map:
{ "identifier": "text_print", "elements": { "title": "Print", "description":"Prints a value to the console", "concept": "Output %1", "python": "print(%1)", "javascript": "console.log(%1);" }}Element names are free-form — title, icon, concept, python,
javascript are conventions, not keywords. You invent the vocabulary that fits
your use case (natural languages, programming languages, verbosity levels, …).
Element types
Section titled “Element types”What an element name means is declared once, globally, in the elementTypes
registry — not repeated per block:
{ "elementTypes": { "icon": "image", "title": "text", "description":"text", "concept": "code", "python": "code" }}The type drives rendering behavior:
| Type | Toolbox tile | Workspace block |
|---|---|---|
text |
Rendered as HTML label | Never shown |
code |
Rendered as Blockly SVG or source text | Used as the Blockly template (<img> in content becomes a FieldImage) |
image |
Rendered as <img> |
Never shown |
custom |
Drawn by a function of the app | Never shown |
code elements use the template syntax
(%1 input slots, %FIELDNAME fields, <img> tags) and double as the text
rendering for the codespaces.
Custom elements
Section titled “Custom elements”A custom element is drawn on the tiles by a function of the app, so a tile
can hold anything the three other types do not: an info button, a sound to
play, a read aloud button, a video. The block’s content for the element is
passed to the function, so each block has its own:
{ "elementTypes": { "title": "text", "concept": "code", "info": "custom" }, "modes": [ { "name": "novice-tiles", "elements": ["title", "concept", "info"] }, { "name": "expert-tiles", "elements": ["concept"] } ], "blocks": [ { "identifier": "print", "elements": { "title": "Print", "concept": "print %1", "info": "Writes a message to the output" } } ]}engine.mount({ workspaceContainer, toolboxContainer, toolbox: { custom: { info: (content, { block, element, mode }) => { const button = document.createElement("button"); button.textContent = "ⓘ"; button.setAttribute("aria-label", content); button.onclick = () => showHelp(content); return button; }, }, },});The function returns a DOM node, or a string shown as text. It runs again on every redraw of the toolbox, e.g. when the toolbox mode switches. Like every element, modes decide where it shows: here the novice tiles have the button, the expert tiles do not.
A press on a button, link or input inside a custom element is for that
control: it does not drag the tile, and Enter or Space there does not add the
block. A custom element is left out of the tile’s name for screen readers,
since its controls carry their own. Without a function in toolbox.custom, the tile shows the
content as text and mount() warns.
The rendered tile
Section titled “The rendered tile”In the HTML toolbox, all elements are always rendered; the active mode’s CSS controls which are visible:
<div class="morphic-block morphic-mode-icon-tiles morphic-block-text_print" style="--morphic-block-color: #5C81A6"> <div class="morphic-element-icon"><img src="assets/log.svg"></div> <div class="morphic-element-concept">Output <span class="morphic-slot"></span></div> <div class="morphic-element-python">print(...)</div> <div class="morphic-element-title">Print</div></div>This is what your per-mode CSS targets — see Styling Modes.
Block identifiers
Section titled “Block identifiers”Block identifiers are free-form too — including names that collide with
Blockly’s built-ins like math_number or logic_boolean. Internally the
framework registers every block as morphic:<identifier>, so your names can
never clobber the stock blocks (which the framework still uses for shadows,
placeholders, and connection checks).
You always work with the clean identifier: it keys the behaviors map, shadow
and placeholder references, and toolbox block lists. A reference resolves to
your block when it matches one of your definitions, and to the Blockly stock
type otherwise — "shadow": "math_number" keeps Blockly’s number block, while
"shadow": "my_number" uses yours if you defined it.
A live Blockly block still carries the namespaced type in block.type. To look
up its definition, turn it back into the clean identifier with toCleanId:
import { toCleanId } from "morphic-blocks";
const id = toCleanId(block.type); // "morphic:text_print" → "text_print"Types without the prefix, such as Blockly’s math_number, come back unchanged.