An html block has no network, so it cannot load scripts from a CDN. It names one of these instead, and Potion injects it: <!-- potion:use chart.js@4 -->. Pin an exact version in notes you want to render the same forever.
59 libraries · 58 with a tested example
What it does
Example
Global
apexcharts
Charts
Polished interactive charts with built-in tooltips, zoom and legends.
A chart meant to be explored rather than read once.
Tested4Tips (3)
Labels default to #373d3f, invisible on dark themes: set `chart.foreColor` from `getComputedStyle(document.body).color`, and `theme.mode`/`tooltip.theme` from the `color-scheme` meta.
Set `chart.background: 'transparent'` — the default white card ignores the theme.
Render inside requestAnimationFrame with `width: '100%'`, and wrap the chart in a fixed-height box with `overflow: hidden`: the SVG keeps settling after render and, as the last thing in the frame, can overhang it and raise scrollbars that squeeze the chart on desktop.
ApexCharts
chart.js
Charts
Bar, line, pie, radar and scatter charts on a canvas.
The default for plotting data. A table beats it for three numbers; reach for it once the shape of the data is the point.
Tested4Tips (3)
Labels and ticks default to a fixed grey that vanishes on dark themes: set `Chart.defaults.color` from `getComputedStyle(document.body).color` before creating the chart.
Give the canvas a parent with an explicit height and set `maintainAspectRatio: false`; with no height the chart collapses.
Set `animation: false` so the first frame the host measures is the finished chart.
A chart no charting library has a name for. Harder to get right than chart.js, so prefer that unless the layout is genuinely custom.
Tested7Tips (2)
Draw in a fixed coordinate space with `viewBox` and `width:100%`: the SVG scales to the block, so nothing has to be measured before the frame is laid out.
Axes already draw with `currentColor`, which follows the theme; set text labels to `fill: currentColor` too, and soften axis lines to a translucent grey.
Chart types chart.js does not have, or thousands of points. It is 1MB, so do not use it for a bar chart.
Tested5Tips (4)
About 1MB: the first load of a note can take many seconds on a slow connection, so use it only for chart types chart.js lacks (treemap, sankey, heatmap), never for a plain bar or line chart.
Give the container an explicit height and call `echarts.init` inside requestAnimationFrame, so it reads the real width after layout.
Set `backgroundColor: 'transparent'` — the built-in dark theme paints its own navy ground.
Colour tiles and series explicitly with dark solid colours and white labels; automatic shading (`colorSaturation`) produces pale tiles that white text vanishes on.
echarts
frappe-charts
Charts
Small, clean SVG charts.
A simple chart where echarts or plotly would be wildly oversized.
Tested1Tips (3)
Axis labels and legend text use a fixed grey: override with `.frappe-chart text { fill: currentColor !important }` so they follow the theme, and soften grid lines to a translucent grey.
Create the chart inside requestAnimationFrame so it reads the real width, and set `animate: false` so the first measured frame is the finished chart.
It redraws when the frame resizes, so a capture right after load can show a half-drawn chart; judge it once it has settled.
frappe
plotly.js-basic-dist
Charts
Scientific and statistical plotting.
Analysis output rather than a dashboard tile: error bars, log axes, many scatter points. The basic bundle has only scatter, bar and pie traces — no box plots, histograms or heatmaps (use echarts for those).
Tested2Tips (3)
Only scatter, bar and pie traces exist in this bundle. Any other `type` (box, histogram, heatmap) does not error — it silently draws as scatter lines.
Set `paper_bgcolor` and `plot_bgcolor` to `'transparent'` and `font.color` from `getComputedStyle(document.body).color`; the defaults are a white paper with #444 text that ignore the theme.
Give the container an explicit height, draw inside requestAnimationFrame, and pass `{ displayModeBar: false, responsive: true }`.
Only when a table needs to be *used* rather than read — sorted, searched, or paged. A plain markdown table is the default and handles most tabular content.
Tested6Tips (1)
Ships CSS written for a white page: restyle its surfaces with `color: inherit` and translucent greys, or the table stays white in every theme.
gridjs
js-yaml
Tables & data
YAML parsing and serialisation.
A block whose data is YAML — config, front matter, a spec pasted into the note.
Tested4Tips (2)
Keep YAML in `<script type="text/yaml">`: it is not executed, and its indentation survives. Read it with `textContent`.
`jsyaml.load` is safe by default in v4 (no custom JS types); still escape parsed values before writing them as HTML.
jsyaml
papaparse
Tables & data
CSV and TSV parsing, including messy real-world files.
Turning pasted CSV into rows for a table or chart. Hand-rolled split(',') breaks on quoted fields.
Tested5Tips (2)
Keep CSV in `<script type="text/csv">` and parse `textContent.trim()` with `{ header: true, dynamicTyping: true, skipEmptyLines: true }`.
Escape parsed text before writing it as HTML — CSV is data the block did not author.
Exploring many notes as rows — group films by style, sort by rating, filter as you type. gridjs is lighter for a plain sortable table. Edits made in the table stay in the block; a block cannot write back.
Tested6Tips (1)
Ships CSS written for a white page: restyle its surfaces with `color: inherit` and translucent greys, or the table stays white in every theme.
Tabulator
jsxgraph
ships css
Maths
Interactive geometry and function plots with sliders and draggable points.
Letting the reader move a slider or drag a point and watch the maths respond — a parameter changing a curve, a tangent following a point, a theorem holding as a triangle moves.
Tested1.13.3Tips (4)
Plot a function as board.create('curve', [t => t, t => f(t), min, max]). A 'functiongraph' plots no points in the sandbox.
Pass JavaScript functions, never string terms like 'a*x^2': strings go through JessieCode, which needs eval.
Axes, ticks and labels are drawn in fixed dark colours: give the board a light ground of its own.
Give the board div an explicit height and touch-action: none, so dragging a point on a phone does not scroll the note.
JXG
mathjs
Maths
Expression parser, units, matrices and exact fractions: math.evaluate("12 cm to inch").
A calculator, unit converter or formula sheet — anything where the user types an expression rather than numbers into fixed fields.
Tested15Tips (1)
Style form controls yourself (color: inherit, background, border): an input does not inherit the block text colour.
math
temml
ships css
Maths
LaTeX maths rendered as MathML: temml.render(tex, element).
An equation inside a block — a note's own $…$ maths does not reach into html blocks. MathML needs no fonts, so it works in the sandbox where KaTeX would not.
Tested0.13.5Tips (2)
Needs Temml-Local.css and the Temml font; the host injects both from this entry, and without them the maths renders blank.
Its stylesheet uses currentColor throughout, so rendered maths follows the block's text colour with no restyling.
temml
fabric
Drawing
Canvas objects you can select, transform and serialise.
An editor-like surface: place, resize and rotate items.
Tested6Tips (2)
Construct fabric.Canvas inside requestAnimationFrame: it reads the canvas CSS size once, at construction.
Retina scaling is on by default; no devicePixelRatio code needed.
fabric
konva
Drawing
Canvas scene graph with layers, shapes, dragging and hit detection.
An interactive canvas where the user moves things around.
Tested9Tips (2)
Build the Stage inside requestAnimationFrame from the container clientWidth; Konva handles devicePixelRatio itself.
Set touch-action: none on the container so shapes drag on a phone instead of scrolling the note.
Real geometry work — intersections, offsets, path manipulation.
Tested0.12.18Tips (2)
Write plain JavaScript against the paper global: PaperScript needs its parser, which calls new Function.
Call paper.setup(canvas) inside requestAnimationFrame so the canvas has its CSS size, and give the drawing a solid panel.
paper
pixi.js
Drawing
GPU-accelerated 2D rendering for thousands of sprites.
Far more moving objects than canvas 2D can keep smooth. It is 800KB, so not for a handful of shapes.
Tested4Tips (3)
Pin @4: pixi.js 5 and later generate code with new Function on the WebGL path, which the sandbox CSP blocks.
Version 4 takes new PIXI.Application(width, height, options) and transparent: true, not the newer single options object.
Stop app.ticker on visibilitychange: version 4 keeps rendering off-screen.
PIXI
rough-notation
Drawing
Hand-drawn circles, underlines, boxes, brackets and highlights around existing text.
Marking up words that are already on the page — circle a term, underline a claim, bracket a paragraph. It follows the element, so the mark stays on its words when the text reflows; hand-placed roughjs shapes do not.
Tested0Tips (5)
Call annotate / annotationGroup(...).show() inside requestAnimationFrame: it measures the text it marks.
Use translucent highlight colours so the marked text stays legible on every theme.
circle and box measure ONE box. An inline target that wraps spans from the start of its first line to the end of its last, so the ellipse encloses the whole paragraph instead of the phrase. Keep a circled phrase unbreakable (display: inline-block; white-space: nowrap) or give it a line of its own.
underline and highlight take multiline: true and follow a wrap; circle and box have no such option — choose the type by whether the target can break.
A circle is drawn OUTSIDE the text box, so leave vertical room: in a tight line-height it crosses the line above. Padding on the container, or padding: [6, 10] on the mark.
RoughNotation
roughjs
Drawing
Hand-drawn, sketchy rendering of shapes and charts.
A diagram that should read as a sketch rather than a finished figure.
Tested4Tips (2)
rough.svg(el) returns nodes you append yourself; rough.canvas draws straight onto a canvas.
Pass stroke: 'currentColor' so the sketch follows the block's text colour; a fixed viewBox with width: 100% needs no sizing code.
rough
sketch
Drawing
Hand-written explainer boards: equations, arrows, circles and brackets written stroke by stroke — one board, or a story of scenes the reader steps through.
Explaining an idea the way a maths video does — an equation built up with circled terms, arrows and notes, over several boards when the idea has steps. One JSON scene; no drawing code, no network, no web fonts.
Tested0.6.0Tips (12)
Pin the minor, not just the major: [email protected]. Below 1.0 every minor may break, and the host serves whatever it stored for the pin you write.
Use Sketch.board(el, scene) on one element; mount() scans for <script type="text/sketch"> tags, which a block does not need.
A lesson with steps is "scenes": [{ label, items, hold?, clear? }] instead of "items" — each is written, held, then wiped. Ids live inside a scene, so every scene may reuse "eq". In "static" mode the scenes stack down the page as a storyboard.
"advance": "click" stops each scene when it is complete and shows a next button; the last scene never waits. The reader taps it — nothing advances an unwatched board.
"pencil" picks the tool at the pen tip: "pencil", "crayon", "marker", "chalkpen", "chalk" or "stylus" (true means pencil); the tool takes the colour being written. "chalkpen" reads best on the default dark board.
"controls": "hover" lays the play/scrub bar over the board's bottom edge and shows it only while pointed at (on touch, while paused) — a cleaner board. true keeps it under the board; false hides it.
Pause needs 0.6 or later: in 0.5 a press on the bar also reached the board and toggled play back on.
A line item is smoothed by default: pass "smooth": false for a triangle or any shape with corners.
Scene "sound": true synthesises the writing sound with Web Audio (no files), but browsers only allow it after a tap on play or the board; an autoplaying board stays silent until then.
Keep the default dark board ("background" unset) when the scene picks ink colours such as yellow or cyan; "none" draws on the page, where chosen colours can vanish on a light theme.
Place notes inside the board width (1000 units by default): a label offset past the edge with dx is clipped, not wrapped.
In a JS object the text markup takes doubled backslashes, same as JSON: "\\frac{a}{b}", "\\id{c}{c^{2}}".
Sketch
two.js
Drawing
2D vector drawing across canvas, SVG or WebGL.
Clean vector shapes and smooth motion without p5's sketch framing.
Tested0.8Tips (2)
Use type: Two.Types.canvas and size from the container inside requestAnimationFrame.
.bind('update', cb).play() drives animation without a loop of your own.
Two
zdog
Drawing
Flat, rounded pseudo-3D illustrations on canvas or SVG, draggable to rotate.
A small 3D-looking object the reader can spin — an icon, a model, a playful illustration — without a full 3D engine.
Tested1.1.3Tips (3)
Set the canvas width and height attributes and the same size in CSS: Zdog draws at the attribute size, and a CSS-only size stretches it.
Put touch-action: none on the canvas so dragRotate spins the model on a phone instead of scrolling the note.
Its colours are chosen, not inherited: give the illustration a solid ground of its own.
Zdog
three
3D
3D scenes on WebGL: meshes, lights, cameras, orbit controls, bloom.
Something that has to be seen in three dimensions and turned: a molecule, a room, a solar system, a product, a 3D chart. For flat drawing or 2D animation use a canvas or SVG; this is the heaviest library on the list.
—Tips (8)
Everything is on the one global: `THREE.Scene`, and the bundled add-ons beside it (`THREE.OrbitControls`, `THREE.RoundedBoxGeometry`, `THREE.Line2` / `LineGeometry` / `LineMaterial`, `THREE.CSS2DRenderer` / `CSS2DObject`, `THREE.EffectComposer` / `RenderPass` / `UnrealBloomPass` / `OutputPass`). There is no `import`, and an add-on not in this list is not available.
Nothing can be loaded: no GLTF, OBJ, HDR or remote texture (`connect-src` is none). Build geometry in code, and make textures from a `<canvas>` (`THREE.CanvasTexture`) or a `data:` image.
Give the stage an explicit height and size the renderer from it (`renderer.setSize(stage.clientWidth, stage.clientHeight)`) inside a `ResizeObserver`; a canvas with no height collapses the block.
Paint a solid ground of your own (`renderer.setClearColor`) and choose lights and colours against it: a lit scene on a transparent canvas reads differently on every theme.
Cap `renderer.setPixelRatio(Math.min(devicePixelRatio, 2))` and draw only while the block is on screen (an `IntersectionObserver` gating `setAnimationLoop`): a WebGL loop is the costliest thing a note can run.
Give the block a feed section, `<template data-potion-feed>` with a drawing of the scene (inline SVG): the feed shows it instead of running the block. A browser allows only a handful of WebGL contexts, and a feed of live 3D cards uses them up.
With `OrbitControls`, set `enableZoom = false` unless zoom is the point, so the mouse wheel keeps scrolling the note.
WebGL only: the WebGPU renderer and TSL are not in the bundle.
THREE
dagre
Diagrams
Automatic layered layout for flowcharts and architecture diagrams, groups included.
A diagram too dense to place by hand: many nodes, crossing links, labelled groups. dagre only computes positions and edge routes; the block draws them as SVG and can make nodes draggable.
Tested0.8.5Tips (9)
Pin [email protected]: the classic graphlib API (new dagre.graphlib.Graph({ compound: true })). The @dagrejs/dagre 1.x fork is a different package.
It lays out, it does not draw: after dagre.layout(g), read g.node(id) (centre x, y, width, height) and g.edge(e).points, and draw rects and paths yourself.
Measure each label before layout (a canvas 2D context with the same font) and give the node its width and height; dagre cannot see text.
Groups are compound nodes: g.setNode(groupId, { label }) then g.setParent(nodeId, groupId); read the group box from g.node(groupId) like any node.
Edge labels need width and height on the edge (g.setEdge(a, b, { label, width, height })) so dagre leaves room for them; draw the label at g.edge(e).x, y.
A real diagram comes out wider than a note column (1500-2000 units is normal). Do not shrink it to fit — text drops to 4px. Put the svg in a scrolling box (overflow:auto, max-height) at a readable zoom, with Fit and +/- buttons, as the example does.
The host frame caps every svg at max-width:100% !important. To size it past the column, set it inline: svg.style.setProperty('max-width', 'none', 'important').
A node that only points INTO the flow (an attacker, a data source) ranks at the top. Add the edge reversed to dagre and reverse its points when drawing, and it sits below instead.
Draggable nodes: pointer events with setPointerCapture work for mouse and touch; convert with svg.getScreenCTM().inverse(), set touch-action:none on nodes, and redraw the moved node's links as straight clipped lines.
dagre
jsmind
ships css
Diagrams
Mind maps from a node tree, with collapsible branches.
Brainstorms and topic maps where hierarchy matters and branches should collapse. A small map that never needs to collapse is simpler as inline SVG.
Tested0Tips (4)
Mount the map inside requestAnimationFrame, after layout, so its container has a width; mounted earlier it renders empty.
Use view: { engine: 'svg' }: with the default canvas engine the connector lines stop short of restyled nodes.
mode: 'side' keeps every branch on one side, so the map fits a phone's width.
Restyle its white node theme on the jmnode and jmexpander elements, with !important.
jsMind
leader-line
Diagrams
Draws connector lines and arrows between HTML elements.
Annotating a normal HTML layout with pointers, without moving to a canvas.
Tested1Tips (2)
Draw inside requestAnimationFrame: it reads each anchor's position. The SVG goes into the block's own body, so it lines up inside the frame.
Pass color: getComputedStyle(document.body).color so the line follows the theme.
LeaderLine
smiles-drawer
Diagrams
Chemical structure diagrams from SMILES strings.
Drawing a molecule — caffeine, aspirin, a reaction product — from its SMILES string, which markup cannot lay out.
Tested2.4.1Tips (3)
Draw with SmilesDrawer.SvgDrawer into an <svg>. The canvas Drawer renders through a temporary SVG it appends to the body, and on the web the block stays blank.
Redraw when the block's width changes and set preserveAspectRatio to 'xMidYMid meet' on the svg; a drawing made at one width stretches in the expanded view.
Atom colours carry meaning: give each molecule a solid ground of your own and pass the matching theme ('dark' or 'light') to draw().
SmilesDrawer
maplibre-gl
ships css
Maps
Interactive street maps — pan, zoom, markers — with tiles served through Potion.
Showing where things are: notes that carry coordinates as properties, stops on a trip, one place. Not for a simple list of addresses, which markdown shows fine.
Tested4Tips (5)
The block has no network, so a style URL never loads. Build the map from `style: await potion.mapStyle('liberty')` — also `positron` (light, quiet), `bright`, `dark`, `fiord` (dark blue). Tiles, fonts and sprites then come through the host, from OpenFreeMap via Potion.
Follow the app theme, not the OS: `getComputedStyle(document.documentElement).colorScheme === 'dark'` picks `dark`, otherwise `liberty` or `positron`.
Give the map container an explicit height (`height: 360px`); a map in an auto-height frame has none. Add `attributionControl: { compact: true }` so the credits stay small.
Places are ordinary notes with number properties the author chooses (e.g. `lat`, `lng`): read them with `potion.getNotes({ fields: ['lat', 'lng'] })`, skip rows without both, and draw them as a GeoJSON source with a `circle` layer (or `new maplibregl.Marker()`). Fit with `map.fitBounds(bounds, { padding: 40, maxZoom: 15 })`.
Open a note from a click with `potion.openNote(id)`.
maplibregl
dayjs
Time
Date parsing, maths and formatting.
Axis labels, relative times or date maths inside a block.
Tested1Tips (1)
Nothing to adapt for the sandbox; use a fixed date in examples so they render the same every day.
dayjs
event-calendar
ships css
Time
Month, week, day and list calendars of events: EventCalendar.create(el, { view, events }).
Notes with dates shown as a calendar — due dates, a reading schedule, an agenda built from potion.getNotes(). Suits days and weeks rather than a long history.
Tested5Tips (3)
Theme it through its CSS variables on .ec (--ec-bg-color, --ec-border-color, --ec-button-*); --ec-text-color already follows currentcolor.
Per-event colours are eventBackgroundColor and eventTextColor on the event object.
Mount inside requestAnimationFrame: it measures the container width for the grid.
EventCalendar
luxon
Time
Dates and times with real time-zone handling.
Anything involving zones, durations or calendar maths. For plain formatting, dayjs is smaller.
Tested3Tips (1)
Build dates with DateTime.fromISO and an explicit zone; setZone works for any IANA zone with no extra data file.
luxon
animejs
Animation
Lightweight animation of CSS properties, SVG and objects.
A few coordinated animations where gsap would be overkill.
Tested3Tips (2)
Pin @3: animejs 4 and later ship ES modules only, with no global build a block can load.
The global is anime; anime.stagger() is built in.
anime
canvas-confetti
Animation
Confetti bursts on a canvas.
A celebration beat in something interactive. Rarely the right answer twice.
Tested1Tips (2)
Use confetti.create(canvas, { resize: true, useWorker: false }) on your own canvas: the default full-page overlay and its worker do not fit a sandboxed block.
Fire it from a button; a burst on load plays before anyone is looking.
confetti
gsap
Animation
Timeline-based animation of DOM, SVG and canvas.
Sequenced or choreographed motion. A single transition should stay CSS.
Tested3Tips (2)
Nothing to measure at construction; start the timeline after one requestAnimationFrame so layout has settled.
Put animated shapes on a solid panel you choose: their accent colours are picked, not inherited.
gsap
lottie-web
Animation
Plays After Effects vector animations from JSON.
A designed animation you already have as Lottie JSON, inlined into the block.
Tested5Tips (3)
Inline the animation JSON as animationData: loading a path or URL needs the network.
Keep After Effects expressions out of the JSON: evaluating one calls eval, which the sandbox blocks.
Give the container an explicit CSS height and call loadAnimation inside requestAnimationFrame.
lottie
compromise
Text & search
English NLP: people, places, organisations, dates, topics, verb tenses.
Pulling structure out of note text — who is mentioned, which topics recur — without an AI call. English only.
Tested14Tips (2)
Use the built-in .people(), .places(), .organizations() and .topics(); anything beyond them needs a plugin the host does not load.
English only: other languages come back as empty groups, so code the empty state.
nlp
diff
Text & search
Text diffing by character, word or line.
Showing what changed between two versions of something inside a note.
Tested9Tips (2)
Tint added and removed spans with a stronger, solid-looking fill (around 30% alpha) so the change reads on every theme.
Diff.diffWords returns { value, added, removed } parts; escape value before inserting it as HTML.
Diff
fuse.js
Text & search
Fuzzy search over a list of objects, with scoring.
A search box in a block that should tolerate typos. Exact substring matching does not need this.
Tested7Tips (2)
includeScore gives 0 to 1 with lower meaning closer; invert it before showing a match percentage.
Style the search input yourself (color, background, border): inputs do not inherit the block text colour.
Fuse
highlight.js
ships css
Text & search
Syntax highlighting for code, 190+ languages.
Showing code *inside* a block. A plain fenced code block in the note is already highlighted — do not use this for that. Ships a dark theme; restyle .hljs if the block is light.
Tested11Tips (2)
Its github-dark stylesheet picks token colours, so give the code its own solid dark ground (#0d1117) instead of restyling tokens per theme.
Call hljs.highlightElement() on the mounted <code class="language-…"> node; a note's own fenced code is already highlighted server-side.
hljs
mark.js
Text & search
Highlights search terms inside rendered text: new Mark(el).mark("term").
A block that shows text and lets the reader search it, so matches light up in place.
Tested8Tips (2)
Set both background and color on mark: the default yellow-on-black does not read on every theme.
Call unmark() before each mark() pass so earlier highlights do not linger.
Mark
marked
Text & search
Markdown to HTML, in the browser.
Rendering markdown the block itself holds as data. The note's own body is already markdown — do not use it for that.
Tested18Tips (2)
Keep the markdown in a <script type="text/markdown"> tag so its whitespace survives, and read it with textContent.
Only render markdown you wrote with innerHTML: a block has no trustworthy sanitiser for markdown from elsewhere.
marked
minisearch
Text & search
Tiny full-text search index with ranking, prefix and fuzzy matching.
A search box over note titles or contents inside a dashboard. fuse.js is better for fuzzy matching a short list; this is for ranked search over longer text.
Tested7Tips (2)
Build result rows with textContent, not innerHTML, so document text cannot inject markup.
Style the search input yourself (color, background, border): inputs do not inherit the block text colour.
MiniSearch
wordcloud
Text & search
Word clouds drawn on a canvas.
Showing which words or tags dominate a set of notes at a glance. A bar chart is better when exact counts matter.
Tested1Tips (3)
Size the canvas inside requestAnimationFrame, after layout — measured earlier, the block has no width yet and the cloud draws into nothing.
Size the canvas inside requestAnimationFrame and scale it by devicePixelRatio, with weightFactor scaled to match.
Draw on a solid panel you choose: WordCloud picks its own word colours.
WordCloud
htm
Interface
JSX-like tagged templates with no build step.
Always alongside preact — `const html = htm.bind(preact.h)` gives you JSX syntax a block can actually run. On its own it does nothing.
Tested3Tips (2)
htm does nothing alone: load it with preact (potion:use htm@3, preact@10) and bind it with htm.bind(preact.h).
Tagged templates compile at call time with no eval, so it is safe under the sandbox CSP.
htm
interactjs
Interface
Drag, resize, pinch and snap for any element.
Free positioning or resizing. For simple list reordering, sortablejs is the smaller answer.
Tested1Tips (2)
Set touch-action: none on the draggable and its container, or the note scrolls under a finger mid-drag.
Use the restrictRect and restrictSize modifiers so a dragged or resized box stays inside its stage.
interact
nouislider
ships css
Interface
Range sliders, including two-handle ranges.
Letting the reader vary an input and watch the block respond — the difference between a figure and a model.
Tested15Tips (2)
Its stylesheet draws a white track and handles: restyle .noUi-target, .noUi-connect and .noUi-handle with translucent greys and an explicit fill.
Handles stick out about 17px past each end: leave that much side margin inside the block or they clip at the frame.
noUiSlider
preact
Interface
A 4KB React-compatible renderer: components, state, diffing.
A block whose UI has real state — a filter, a toggle, a list that re-renders. Plain DOM is the default; reach for this when hand-written innerHTML starts fighting you. Pair with htm for JSX-like markup, since a block has no build step.
Tested10Tips (2)
The host loads preact core only: useState and other hooks live in preact/hooks and are not there. Use a class component (preact.Component with this.setState).
Pair it with htm on the same directive (potion:use preact@10, htm@3) for JSX-like markup with no build step.
preact
sortablejs
Interface
Drag-and-drop reordering of lists and grids.
A block where the reader arranges things — ranking, planning, sorting into columns.
Tested1Tips (1)
Put touch-action: none on the drag handle, not the whole row, so the rest of the list still scrolls the note on a phone.
Sortable
swiper
ships css
Interface
Touch carousels and slide decks.
A slide deck or a swipeable gallery inside a note — and it works with a thumb on iOS.
Tested14Tips (3)
Slides cannot load remote images; use solid panels, inline SVG or data: URLs.
Its navigation arrows are inline SVG with fill currentColor: set color on .swiper-button-next and -prev, and background on .swiper-pagination-bullet.
Keep it horizontal so a swipe never fights the note scrolling vertically.
Swiper
abcjs
Audio
Sheet music from ABC notation text: staves, notes, chords, lyrics.
Showing music as notation — a tune, a scale, an exercise — written as a few lines of ABC text instead of a picture of a score.
Tested6.7.0Tips (2)
Pass foregroundColor: 'currentColor' so the score follows the theme's text colour, and responsive: 'resize' so it scales to the block width.
Rendering only: ABCJS.synth playback loads soundfont samples from the network, which the sandbox blocks. For sound, play the notes with tone in the same block.
ABCJS
tonal
Audio
Music theory: notes, intervals, scales, chords and keys.
Explaining how music is built — the notes of a scale, the chords in a key, the interval between two notes — computed rather than typed out by hand. Pairs with abcjs to show it and tone to hear it.
Tested6.4.3Tips (1)
Pure computation with no DOM of its own: render its answers (Tonal.Key.majorKey, Tonal.Chord.get, Tonal.Interval.distance) as your own markup.
Tonal
tone
Audio
Web Audio framework: synths, effects, scheduling.
Making sound in a note — a scale, a rhythm, an audible demonstration. Never start audio without the reader asking for it.
Tested15Tips (5)
Start audio from a button that calls await Tone.start(): browsers only allow sound after a tap.
Use synths only; sample files would need the network.
A keyboard or single-voice instrument wants Tone.Synth, not PolySynth: PolySynth keeps every overlapping note sounding, so fast playing stacks into a chord.
For playable keys, call triggerAttack on pointerdown and triggerRelease on pointerup and pointerleave. triggerAttackRelease with a fixed duration cuts a held key short and drags out a tapped one.
Keep a Tone.Waveform or Analyser in the same block as the synth it listens to. Another block cannot reach this block's synth.
Building a colour ramp for a heatmap or checking contrast. Note v3 ships no browser build — use v2.
Tested2Tips (2)
Label a swatch with chroma.contrast() or luminance() to pick black or white text; a fixed text colour fails on half the scale.
Swatches choose their own colours, so they are their own ground; nothing else needs to follow the theme.
chroma
lodash
Utilities
Utilities for grouping, sorting, chunking and deep access.
Shaping awkward nested data. Modern array methods cover the simple cases without it.
Tested4Tips (1)
Never use _.template: it compiles with Function, which the sandbox CSP blocks. groupBy, orderBy, map and the rest are safe.
_
qrcode-generator
Utilities
Generates a QR code as SVG or table markup.
Putting a link on screen for someone to scan off the page.
Tested1Tips (3)
Pin @1: version 2 dropped the root qrcode.js build this entry loads.
createSvgTag({ scalable: true }) has no intrinsic size; give the svg an explicit width and height or it collapses.
Keep dark modules on a solid white panel so the code scans on every theme.
qrcode
caveat
Fonts
A handwriting font — marker-on-whiteboard lettering. Use as font-family: Caveat.
A block that should read as handwritten notes: a whiteboard explainer, an annotated sketch. System 'cursive' differs on every device; this is the same everywhere.
Tested5Tips (3)
A typeface, not code: write `font-family: Caveat` — there is no global to call.
A font entry: load it with the directive, then set font-family to its global name. There is no script to call.
The font lands after the page first lays out and makes the text taller; the iOS frame now re-measures when fonts finish loading. Leave a little bottom padding under the last line anyway.