Library / SDK
observablehq/stdlib avatar
observablehq/stdlib

The Observable standard library is a set of runtime builtins, not a notebook framework

The Observable standard library.

1,002 stars87 forksJavaScriptISC

At a glance

What is it?
The @observablehq/stdlib package is the standard library for Observable Notebooks 1.0, exposing DOM construction, file reading, generators, promises, a require loader and renderers for HTML, Markdown, SVG and LaTeX as a set of reactive builtins. It fits a developer embedding Observable's reactive cell model in a JavaScript project, and it does not fit a project on Notebooks 2.0, where the successor is Notebook Kit.
Who is it for?
The Observable standard library fits a JavaScript developer who wants Observable's reactive cell semantics, its DOM builders and its renderers without adopting the whole notebook product, and it does not fit a team building on Notebooks 2.0, because the README points at Notebook Kit for that.
Can I use it commercially?
Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 20 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

This is Notebooks 1.0, and the successor is named in the first lines

The @observablehq/stdlib package is described as the Observable standard library for Notebooks 1.0. The README is direct about the version boundary. For the standard library in Notebooks 2.0, see Notebook Kit. For examples, see the Observable standard library page at observablehq.com.

That first line is the most consequential fact in the document, because Observable's notebook model changed between the two generations. A Notebooks 1.0 notebook is a set of reactive cells where a cell's value is derived from the cells it references, and the runtime recomputes dependents when a dependency changes.

This package is the 1.0 runtime. The DOM helpers, the file attachment readers, the require loader, the tagged template renderers, and the small set of reactive primitives like now and width are the builtins that a Notebooks 1.0 notebook had in scope, and the API reference in the README documents them one by one with source links into src/.

The distinction matters for anyone arriving at the library from Observable's current product. The APIs here are not deprecated in the sense of being marked for removal, but they belong to a generation of the product that the successor replaced, and the README's pointer to Notebook Kit is the maintainers' own guidance on where new work belongs.

The package is published by Observable, Inc., licensed under ISC, and the last push was on 2026-10-02.

The API surface is a list of globals a notebook cell can call

The reference list in the README is the whole public surface, and it is organised by capability.

DOM creates HTML and SVG elements. Files reads local files into memory, and FileAttachments reads remote files. Generators holds utilities for generators and iterators, and Promises holds utilities for promises. require loads third-party libraries. Then there are the renderers: html renders HTML, md renders Markdown, svg renders SVG, and tex renders LaTeX. Finally a few reactive primitives, now for the current value of Date.now, width for the current page width, invalidation to dispose resources, and visibility to wait for visibility.

That last group is the part that makes this a reactive library rather than a DOM utility belt. width and now are cells that change over time, invalidation is the mechanism for disposing resources when a cell is re-evaluated, and visibility lets a cell wait until it is actually on screen before it does expensive work. Those four are the seam between the standard library and the reactive runtime that drives it.

The DOM group is the largest and shows the design principle. Every DOM helper is documented against its tagged template literal equivalent, and the documentation consistently recommends the template form instead. DOM.element("h1") is equivalent to html`<h1>`, DOM.input("file") is equivalent to html`<input type=file>`, and DOM.range(-180, 180, 1) is equivalent to html`<input type=range min=-180 max=180 step=1>`. The advice is that in general you probably want to use the html or svg tagged template literal instead of the DOM function, and the DOM functions exist for the cases where a programmatic form is more natural.

The DOM builders exist for the cases templates cannot express

The reason the DOM functions are not simply deprecated is visible in a few of their behaviours.

DOM.canvas(width, height) returns a new canvas element, and the documentation notes that if you are using 2D Canvas rather than WebGL you should use DOM.context2d instead, for automatic pixel density scaling. DOM.context2d(width, height, dpi) takes the device pixel ratio explicitly, defaulting to window.devicePixelRatio, and the canvas is reached through context.canvas. That is a real difference in output size, not a stylistic one.

DOM.download(object, name, value) returns an anchor element containing a button that downloads a file representing the object, which may be anything supported by URL.createObjectURL such as a File or a Blob. The object may also be a function, in which case it is called on click, and if it is a promise, or a function returning a promise, the promise is awaited before the download, in which case an additional click may be required.

DOM.element(name, attributes) returns a new element, with attributes set if supplied, and it switches to document.createElementNS when the name has the prefix svg:, math: or xhtml:, so DOM.element("svg:svg") produces a namespaced SVG element.

DOM.select(values) takes an array of values and returns a select element with the options.

The pattern across all of them is the same. The function form takes plain JavaScript values, which is what you want when the values are computed, and the template form is what you want when the markup is static. The library provides both and documents the trade-off rather than picking one.

Three runtime dependencies, and d3-require is the one that matters

The package.json is short and the dependency list explains what the standard library actually needs.

The three runtime dependencies are d3-array at 3.2.0, d3-dsv at 3.0.1 and d3-require at 1.3.0. d3-array and d3-dsv are the array and delimited-value parsing utilities from the d3 family, and and the API reference documents require as the entry that loads third-party libraries.

The d3-require dependency is the interesting one because require in a notebook is a security decision as much as a convenience. Loading a third-party library at runtime means fetching and evaluating code from a URL, and the set of libraries reachable that way is what d3-require defines. The standard library delegates that policy to a module rather than inventing its own, which is the reason to keep the dependency list short and legible.

The build tooling is Rollup, with rollup-plugin-terser for minification and @rollup/plugin-node-resolve. The prepublishOnly script removes dist and runs rollup -c, so the published bundle is always rebuilt from source rather than reused from a previous run. The development dependencies include mocha for the test suite, eslint for linting, and exceljs, which is a test fixture dependency rather than a runtime one.

The engines field requires Node 14.5.0 or newer, the package is type module, and the main and module entries both point at src/index.js, with the jsdelivr and unpkg fields pointing at dist/stdlib.js for the browser bundle. The exports map offers a umd condition for that bundle and a default for the source.

The tests are mocha, and the CI runs lint alongside them

The test script is a single line that runs two things:

code
mocha 'test/**/*-test.js' && eslint src test

That ordering is the interesting part. The lint pass runs on both src and test, and it only runs if the mocha suite passed, so a failure in either direction fails the build. The test directory at the root is where the mocha specs live, and the naming convention the glob expects is the -test.js suffix.

The repository also carries a .eslintrc.json and an .eslintignore, a .prettierrc, a CONTRIBUTING.md, and a stdlib.sublime-project, which is a Sublime Text project file, a convenience for contributors rather than a build input. The bin/ directory sits at the root alongside src/ and test/, and the rollup.config.js drives the bundling.

The CI badge in the README header points at a Node CI workflow, so the package is built and tested on GitHub Actions against a set of Node versions.

The release history shows v5.8.8 on 2024-08-06, v5.8.7 on 2024-03-26 and v5.8.6 on 2023-12-18, all on the same 5.8.x patch line. The last push was on 2026-10-02, so there is work in the tree that is not in a published tag, and the version field in package.json still reads 5.8.8.

Where the Observable standard library is the wrong tool

The first wrong-tool case is Notebooks 2.0. The README names the successor, and the API you would write against 2.0 is not this API. Building a 2.0 project on the 1.0 standard library is working against a generation the project itself has moved past.

The second is a plain JavaScript project with no reactive model. A large part of this library is about cells that recompute. If you are writing a normal application, a tagged template library that builds DOM and a require loader are the useful parts, and you can get them from a DOM library and a bundler instead. Taking the standard library for the four reactive primitives means taking the whole reactive runtime model with it.

The third is the browser-only assumption. The DOM, Files, FileAttachments, width and visibility builtins are all browser APIs, and the package is published with a jsdelivr and unpkg bundle for exactly that reason. A Node process that wants the file and promise helpers can use them, but the library is not designed for server-side rendering, and nothing in the README describes a Node rendering path.

The fourth is the version gap between the tag and the tree. The published version is 5.8.8 from August 2024, while the repository has been pushed to as recently as 2026-10-02. A team pinning a version from npm gets code that is two years older than the branch, and the README does not describe a compatibility policy between them, so the version you should pin has to be chosen by looking at the changelog rather than by reading the README.

Editorial conclusion

The Observable standard library fits a JavaScript developer who wants Observable's reactive cell semantics, its DOM builders and its renderers without adopting the whole notebook product, and it does not fit a team building on Notebooks 2.0, because the README points at Notebook Kit for that. Before depending on it, check the release you are pinning, since the latest tagged version is v5.8.8 from 2024-08-06 while the last push was on 2026-10-02, and note the licence is ISC and the package requires Node 14.5.0 or newer.

Frequently asked questions

What is the Observable standard library?

The @observablehq/stdlib package is the Observable standard library for Notebooks 1.0, a set of runtime builtins covering DOM construction, local and remote file reading, generators, promises, a require loader, renderers for HTML, Markdown, SVG and LaTeX, and reactive primitives such as now, width, invalidation and visibility.

What is the difference between the Observable standard library and Notebook Kit?

They serve different notebook generations. The @observablehq/stdlib package is the standard library for Notebooks 1.0, and the README directs you to Notebook Kit for the standard library in Notebooks 2.0.

What does Observable stdlib require to run and build?

Node 14.5.0 or newer, and the runtime dependencies are d3-array, d3-dsv and d3-require. The build uses Rollup, and the test script runs mocha against the test/**/*-test.js glob followed by eslint on src and test.

Should I use DOM helpers or the tagged template literals in Observable stdlib?

The documentation recommends the html or svg tagged template literal in general, and presents the DOM functions as the programmatic alternative for values that are computed. DOM.context2d is the exception worth noting, since it applies automatic pixel density scaling that DOM.canvas does not.

Official sources

  1. License: ISC
  2. observablehq/stdlib on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/observablehq-stdlib.svg)](https://hysenlabs.com/projects/observablehq-stdlib)

Was this useful?