Styling Modes with CSS
Modes have no built-in look — you define how each mode renders, one CSS file per mode. This is the framework’s styling contract.
The class contract
Section titled “The class contract”Every rendered tile carries the active mode, the block identifier, and one class per element:
<div class="morphic-block morphic-mode-icon-tiles morphic-block-text_print" style="--morphic-block-color: #5C81A6"> <div class="morphic-element-icon">…</div> <div class="morphic-element-title">Print</div> <div class="morphic-element-python">print(<span class="morphic-slot"></span>)</div></div>A mode stylesheet mostly decides which elements are visible and how they’re arranged:
/* icon-tiles.css — show icon + title, hide everything else */.morphic-mode-icon-tiles .morphic-element-icon { display: block; }.morphic-mode-icon-tiles .morphic-element-title { display: block; font-weight: 600; }Views show an element, not a mode, so workspace views and their blocks carry
morphic-shows-<element> in place of the mode class (see
Presets & Views):
/* python.css: monospace text on workspace blocks showing python */.morphic-workspace-root.morphic-shows-python .blocklyText { font-family: "Fira Code", monospace;}Mode stylesheets load for every mode, so rules for views can sit in any of them, or in your own CSS.
Blocks are sized for the font your CSS gives their text. The font family, size
and weight set on .blocklyText are picked up on every switch, and again
once a stylesheet or web font finishes loading, so text never spills out of its
block. The same applies to blocks shown on toolbox tiles, which take the font of
their element. Other properties, like colors, change nothing about the size.
When your app changes the block font itself, for example with a font size
switch, call engine.refresh() afterwards so blocks and tiles are measured
again.
Codespaces carry morphic-codespace-root and the element they show, read
only ones morphic-read-only too:
.morphic-codespace-root.morphic-shows-python { font-size: 15px; }.morphic-codespace-root.morphic-read-only { opacity: 0.9; }Block colors can be driven from CSS via the custom property:
.morphic-block-text_print { --morphic-block-color: #b469d6;}Blockly draws a thin grey line around the workspace. The framework leaves it out; two custom properties bring it back or style it, on the workspace or per mode:
.morphic-workspace-root { --morphic-workspace-border-color: #c6c6c6; --morphic-workspace-border-width: 1px;}Loading mode stylesheets
Section titled “Loading mode stylesheets”Three ways, in the mount config:
engine.mount({ // 1. A whole folder, by filename (Vite): modes/icon-tiles.css → mode "icon-tiles". // Links (?url) or CSS text (?raw) both work. modesFolder: import.meta.glob("./modes/*.css", { eager: true, query: "?url" }),
// 2. Mode by mode, from any bundler or none: CSS text or a link per mode modeStyles: { "icon-tiles": "/styles/icon-tiles.css", python: ".morphic-mode-python { … }", },
// 3. A base stylesheet applied regardless of mode baseStyle: { href: "/styles/morphic-base.css" },});Each value in modesFolder and modeStyles is either a link or the CSS itself.
The framework tells them apart on its own: CSS always contains a {, a link
never does. So use whatever your bundler gives you, whether a text import, a
URL import, or a file in public/. modesFolder takes precedence over
modeStyles for the same mode names.
Mode coverage validation
Section titled “Mode coverage validation”At mount, the framework checks that every declared mode has a stylesheet and warns about gaps — a mode without CSS renders all elements unstyled, which is almost never what you want. Missing coverage shows up early instead of as a visual surprise later.
Related
Section titled “Related”- Blocks & Elements — the full markup being styled
- Custom Toolbox — where tiles appear