You can set up a new Observable Framework project using the official template by running this command (you should probably cd into the directory where you want the project to live first):
npx @observablehq/framework@latest create All of the default options it asks about should be fine, and it's definitely a good idea to let it initialize a git repository since it will make deploying using GitHub Pages easier below.
Look at package.json and note the scripts object. Each can be run via npm run <name>.
To run an http server on localhost that will update when you modify the files in the project, run:
npm run devIt will automatically open a browser window, but note that it's just a normal URL (albeit one that's pointing to localhost and using a non-standard port). If you want to open it in a different browser, just copy and paste the URL.
- Markdown basics
- Frontmatter: fenced metadata at the top of the file
- Basic syntax: headers, paragraphs, lists
- Inline HTML: you can interleave raw html within the markdown
- Images use a special (odd) syntax:

- HTML classes and layout
- Grids for complex layouts:
grid,grid-cols-#,grid-rowspan-# - Cards for framing content:
card - Notes:
note,tip,warning,caution: callouts
- Grids for complex layouts:
- CSS styles
- page styles can be added in a
<style>...</style>block at the top of the Markdown file (right after the frontmatter) - individual html elements can have inline styles using the
styleattribute<div style="color:red; background:white;"> - consult the MDN reference to see all the proprties you can change
- page styles can be added in a
- JS code blocks: the equivalent of cells in Notebook
- create multi-line blocks of javascript code with triple-backticks (and don't forget the
jssuffix):```js <code goes here> ``` - a block with a single expression will insert its value (text or object) into the page content at that point
- declaring something as
constis a way to share it by name with other code blocks—note that a cell containing one or more declarations will not be inserted into the page content - calling
display(...)will add its argument to the pageA useful pattern for keeping your code clean is to declare your complex pieces of content (like D3 code) as variables in a code block at the top of your file and then just referencing it in a call to
display()within the page content
- create multi-line blocks of javascript code with triple-backticks (and don't forget the
- Inline expressions: Inserting calculated values into markdown
- Use the
${expression}syntax to reference a variable declared elsewhere or even call a function and insert its return value (which should either be a string or an HTML DOM node)
- Use the
- File attachments: Load data and media files from within your project directory
- Look in
example-report.mdfor an example of loading a JSON file
- Look in
- Loading data: Write a loader script with the proper file extension to use javascript or python to do your data processing then emit just the subset of your data that the D3 code will need for rendering.
- The advantage of this over using a FileAttachment, is that the loader script will be run once when you build the site and its output will be cached, so you can potentially load less data and avoid waiting for your slow loader script to execute on each page load.
- Look in
launches.csv.jsfor an example loader script (creating a CSV with JavaScript) and inexample-dashboard.mdto see how its output is accessed as aFileAttachment(note the lack of a.jsfile extension)
- User Interface: the
Inputsmodule provides most of the UI elements you're familiar with from Notebooks - External libraries: you can use the
importstatement to pull in third-party code from the vast NPM library
- Edit the
observable.config.jsfile and pick atitleand athemefor your project - Try adding a
<style>...</style>block at the beginning (but after the frontmatter) ofexample-report.mdand write some rules that re-style the<h2>and<h3>headings:h2{ /* try changing: color, font-family, font-size, text-transform, text-shadow, etc. */ } h3{ /* make some additional style choices for the smaller subheads */ }
- Try wrapping a few words in one of the page’s sentences in a
<span>tag and setting an ‘inline style’ using thestyleattribute:Lorem ipsum <span style="background: linear-gradient(transparent, cyan)">dolor sit amet</span>, consectetur adipiscing elit.
- Try wrapping some other words in a span with a class name that you've chosen (it can be anything, but try to stick to lowercase characters and hyphens):
Lorem ipsum dolor sit amet, consectetur <span class="essential">adipiscing</span> elit.
- now add a rule to the
<style>block that applies to it (in CSS, prepending a "." to a name makes it clear it's refencing a class rather than a tag name):.essential{ text-decoration: underline; }
- now add a rule to the
- Create a new page whose file name is
project.mdbut whose page title is set to "Project Sketches" using markdown frontmatter - Use a File Attachment (or the
![]()shorthand) to load an image then add it to the page as the main content of a<details>element (don't forget to also set a<summary>message). - Make a 3 x 3 grid (similar to the 2 x 2 grids here) but have the middle row be a single cell that spans all 3 columns:
A B C --D-- E F G - Use the color classes to make each label in the grid a different color from its neighbors
- Use a slider input to display the first n words from a long piece of text:
- Declare a
constvariable in a code block that pointing to an array containing single-word strings- You can create one by splitting a long text string like:
"foo bar baz".split(" ") - Consider using a FileAttachment to load a text file rather than adding the string inline
- You can create one by splitting a long text string like:
- Add a code block that contains an
Inputs.rangecontrol—make sure its maximum is set to be the total number of elements in your array of words. - Add another code block that uses the current range value to select the number of words from your array to display, then display a single string with the first n words from your array on the page (take a look at Array.slice(), .join(), and possibly display() for this)
- Declare a
- Try reading individual CSV files from a Zip archive
- Download this Zip of gapminder data and make sure it remains a Zip file (i.e., if your browser automatically decompresses it, make a new archive or try running this from the command line):
curl -O https://datavis.cs.columbia.edu/files/data/gapminder.zip
- Move
gapminder.zipinto your project’ssrc/datasubdirectory and load it fromproject.mdusing the special .zip() method on the FileAttachment object - Use
.filenamesto find out what its contents are called, then use the.file()method to extract the "Continents" file and the.csv()method to unpack its data - Display the rows using an
Inputs.table - Add a list of all the country names to the page (using each row’s
Entityattribute)
- Download this Zip of gapminder data and make sure it remains a Zip file (i.e., if your browser automatically decompresses it, make a new archive or try running this from the command line):
- Write a loader that fetches just the life expectancy data and filters it:
- Note that while you're debugging your script you can run it directly from the command line to see its output. For instance if your loader file is called
life-2010.csv.jsyou can run it with:node src/data/life-2010.csv.js
- Look at the
launches.csv.jsto see how it uses itstext()helper method to make an asynchronous HTTP request and then parse the result - Import
csvParsefrom thed3-dsvmodule and use it to parse the gapminder data once it has arrived - Filter the rows and discard all of them except the data for 2010 string)
- Use
csvFormatto write the 2010 data to stdout - Load your 2010 data from
project.mdand display the rows using anInputs.table
- Note that while you're debugging your script you can run it directly from the command line to see its output. For instance if your loader file is called
- Copy your now-working loader to a file called
gdp-2010.csv.jsand modify its source URL to fetch GDP data- Load the GDP data in
project.mdthe same way you loaded the life expectancy data
- Load the GDP data in
- Create a scatter plot drawing each country as a dot whose x-position is GDP and y-position is life expectancy (you might want to use a
scaleLinearfor life expectancy and ascaleLogfor GDP). Make sure you're using the actual extent of the data to set the domains properly- Add a radio button input that lets you choose between three colors for the dots
- Use the selected color for drawing the dots and have the chart re-render itself each time it's changed
- If you feel like going further:
- add another loader that extracts the 2010 population data
- Use population to set the radius of the dots
- Have the radio buttons choose between 'gdp', 'population', and 'life expectancy'. Use whichever is selected to color the dots using a sequential palette.
If you have a GitHub account, you can create a world-accessible version of your project. First, add your repository to your account, then follow these instructions for creating a .github/workflows/deploy.yml file.
Once this file has been added and you've enabled Pages on the repo, you'll be able to update the live version of the site just by pushing code changes to GitHub.
Alternatively, if you have access to a web server that allows you to upload files manually, you can create a version that can be SFTP/rsync'd to it by running:
npm run buildLook in the build/ subdirectory to see the files it generated—note that index.html is the landing page and any other pages you created are the other *.html files in the directory.
Once your demo framework project is living somewhere on the web, share the link on #participation