Custom Toolbox
The custom toolbox replaces Blockly’s built-in flyout with an HTML toolbox of
Morphic Block tiles: no off-screen Blockly workspaces, just DOM you can
style. Give mount() a container for it:
engine.mount({ workspaceContainer: document.getElementById("workspace")!, toolboxContainer: document.getElementById("toolbox")!,});Categories come from the definitions. To set the toolbox up later, or with
options, use engine.mountToolbox() instead:
engine.mountToolbox(document.getElementById("toolbox")!, { categories: definitions.categories,});Dragging a tile into the workspace — or onto the codespace — creates the actual Blockly block in the model.
Options
Section titled “Options”| Option | Default | Purpose |
|---|---|---|
modeLabel |
true |
Header at the top of the toolbox: true shows Mode: <name>, false none, a string that text, a function (mode) => text its result |
blocks |
all | Show only a subset of blocks (list of identifiers) |
categories |
— | Category grouping; with neither this nor the mount config’s toolbox.categories, blocks render as a flat list |
highlight |
true |
Colour code shown as text on tiles (render: "text-based") with its element’s highlighting, like the codespace; false leaves it plain |
touch |
true |
Tiles can be dragged with a finger or pen; see Touch and keyboard |
keyboard |
true |
Tiles can be reached with Tab and added with Enter or Space; see Touch and keyboard |
layout |
"stacked" |
"stacked": every category under its heading; "tabs": a list of the category names and the tiles of the chosen one; see Categories |
categoryHeadings |
true |
A heading with the category’s name above its tiles |
collapsible |
true |
A click on a heading collapses or expands its category |
collapsed |
all open | true collapses all at the start, a list of names only those |
onCategoryToggle |
— | (name, collapsed) => …, e.g. to remember the state |
scrollbar |
false |
false hides the scrollbar and fades the edges where more tiles wait; true shows it; see Scrolling |
custom |
— | How each custom element is drawn, by element name; see Custom elements |
label |
what the tile shows | The element whose text names each tile for screen readers, or (mode) => element; see Spoken labels |
All of them can also be set in mount() under toolbox, so a toolbox set up by
toolboxContainer gets them too:
engine.mount({ workspaceContainer, toolboxContainer, toolbox: { modeLabel: false, blocks: ["text_print", "loop_for"] },});Options passed to mountToolbox() win over the ones in mount().
Category entries are { name, color?, blocks? } — when a category omits its
blocks list, the framework derives it from block definitions whose
category field matches the name.
Tiles and modes
Section titled “Tiles and modes”Each tile renders all of a block’s elements; the active toolbox mode’s CSS
decides which are visible (see
Blocks & Elements for the
markup). The toolbox re-renders when the toolbox mode changes, via
show({ toolbox }) or a preset switch, and honours the mode’s
render for showing code
elements as blocks or as source text.
Categories
Section titled “Categories”With categories, each one gets a heading with its name. A click on it, or Enter or Space, collapses or expands the category, and the state stays when the tiles are drawn again (a mode or preset change). The markup:
<div class="morphic-toolbox morphic-toolbox-stacked"> <div data-category="Output" data-collapsed style="--morphic-category-color: #5C81A6"> <button class="morphic-category-heading" aria-expanded="false">Output</button> <div class="morphic-category-tiles" hidden>…tiles…</div> </div></div>layout: "tabs" shows one category at a time, like Blockly’s own toolbox: a
list of the names (.morphic-category-tabs, buttons .morphic-category-tab
with aria-selected) and the chosen category’s tiles. The first is shown at
the start; tiles without a category stay in view. Where the list sits is your
CSS, for example beside the tiles:
.morphic-toolbox-tabs { display: grid; grid-template-columns: auto 1fr; gap: 0.5rem; }.morphic-category-tabs { display: flex; flex-direction: column; gap: 4px; }.morphic-category-tab[aria-selected="true"] { background: var(--morphic-category-color); color: white; }The toolbox’s width counts every category, collapsed or not, so opening one
never widens it. Without categories the tiles are a flat list, and "tabs"
warns through onWarning.
Scrolling
Section titled “Scrolling”By default the toolbox hides its scrollbar and fades the edge where more tiles wait above or below. The container gets classes to style that yourself:
| Class | While |
|---|---|
morphic-toolbox |
always |
morphic-toolbox-more-above |
tiles are hidden above |
morphic-toolbox-more-below |
tiles are hidden below |
The default styles have no weight (:where()), so any rule of yours wins, for
example a shadow instead of the fade:
.morphic-toolbox-more-below { mask-image: none; box-shadow: inset 0 -12px 12px -12px rgb(0 0 0 / 0.3); }scrollbar: true keeps the normal scrollbar (style it with scrollbar-width
and scrollbar-color); the classes stay. This works when the toolbox
container is the element that scrolls (overflow-y: auto).
Touch and keyboard
Section titled “Touch and keyboard”The toolbox works without a mouse. Both ways are on by default, and each can
be turned off in the toolbox options (touch: false, keyboard: false).
Touch and pen. A tile starts moving when it is dragged sideways or after a
long press; an up or down swipe still scrolls the toolbox. It can be dropped on
the workspace and on every codespace. A toolbox laid out sideways gives its
tiles touch-action: pan-x, so a swipe along it scrolls and a swipe up or
down drags.
Keyboard. Every tile is a button in the Tab order, named for screen readers after what it shows: code as the codespace would write it, text as it is, and images by their alt text. Enter or Space adds the block after the selected one when it fits there, else as a new stack, and selects it, so pressing again builds a chain. Focus stays on the tile. Give the focus a visible style:
.morphic-block:focus-visible { outline: 2px solid; }Spoken labels
Section titled “Spoken labels”What a tile shows is not always what a screen reader should say: an icon mode
shows a picture, a code mode reads print(%1) aloud. An element that no mode
lists is hidden on every tile but still belongs to the block, so it can hold
the spoken name. label tells the toolbox to name tiles after it:
{ "elementTypes": { "icon": "image", "title": "text", "spoken": "text" }, "blocks": [ { "identifier": "print", "elements": { "icon": "icons/print.svg", "title": "Print", "spoken": "Print block: writes a message" } } ]}engine.mount({ workspaceContainer, toolboxContainer, toolbox: { label: "spoken" } });A block without that element keeps the default name. For several languages, give each its own element and pass a function; it is asked again on every redraw, so after a language switch, redraw the toolbox:
let language = "en";engine.mount({ /* … */ toolbox: { label: () => `spoken-${language}` } });
language = "de";engine.show({ toolbox: engine.getToolboxMode() });A read aloud button on the tiles is the app’s: a custom element can speak the same text with the browser’s speech.
The workspace itself belongs to Blockly. To move and edit blocks there with the keyboard, add Blockly’s keyboard navigation plugin to your app; the framework leaves that choice to you.
Styling
Section titled “Styling”The toolbox is intentionally unstyled beyond structural classes. Target:
.morphic-block { /* every tile */ }.morphic-mode-icon-tiles .morphic-element-title { /* per mode, per element */ }[data-category="Output"] { /* category wrapper */ }See Styling Modes for the full CSS contract.
The toolbox takes the width your layout gives it; a block or a line of code
wider than that is cut off. The framework publishes the widest block or code
shown as text (render: "text-based") on a tile as
--morphic-toolbox-block-width on the toolbox container, updated whenever the
tiles are drawn again (a mode or font change). The variable is set on the
container, so use it there, adding your own tile padding, and let the column
around it grow to its minimum width:
#toolbox { min-width: calc(var(--morphic-toolbox-block-width) + 40px);}.layout { grid-template-columns: minmax(250px, min-content) 1fr;}Only blocks and code count, since neither wraps; long descriptions, which wrap, do not widen it. Code shown as text is measured on the page, so a toolbox that is hidden while its tiles are drawn counts only its blocks until the next redraw. Without such a rule nothing changes.
Tiles that are just the block
Section titled “Tiles that are just the block”A tile has no background of its own; any box around the block comes from your CSS. To show nothing but the Blockly block, give the toolbox a mode that lists only the code element, and let its CSS make the tile exactly as big as the block:
"modes": [{ "name": "block-only-tiles", "elements": ["concept"] }].morphic-block.morphic-mode-block-only-tiles { background: none; border: 0; padding: 0; width: fit-content;}