Demoboard: a live editor with every package on NPM

Quickly create Javascript and Markdown demos that import any package on npm. No more messing with package.json and node_modules!

Just want to try it? Go to Demoboard »

Once upon a time, when I was a young lad, the web was simpler. Code was borrowed shared with “View Source”. Package management wasn’t a thing because git hadn’t been invented yet. And writing JavaScript meant adding a <script> tag to your HTML and… starting writing.

Woooooooow I love this!

Would I go back to this world? Hell no. Modern JavaScript is unbelievably awesome. But one thing I look back on with rose-tinted glasses is how easy it was to get started. You just copied and pasted some code into a file, and it worked — there was nothing to transpile, no package.json to manage, and node_modules wouldn’t have fit on a 512mb hard drive anyway.

The thing is, back then you could learn JavaScript without learning the entire ecosystem. If you copied and pasted some code, it would just work. A little like this:

index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Button from '@material-ui/core/Button'

ReactDOM.render(
  <Button variant='outlined' color='primary'>
    🔥 I'm a material button! 🔥
  </Button>,
  document.getElementById('root')
)
Build In Progress

#Holy JavaScript Batman?!

See that editor above? It uses JavaScript import to pull in React and Material UI. There’s no package.json — imports just work. And there’s nothing special about the React or Material UI packages to make this so. You can use any package. For example, if you don’t like React, you can use Vue instead:

index.html
index.js
styles.css
<!DOCTYPE html>
<html>
  <head>
    <title>Vue.js Starter</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div id="app">
      <div class="center">
        <h1 style="color: #4fc08d;">{{ message }}</h1>
      </div>
    </div>
    <script type="module" src="index.js"></script>
  </body>
</html>
Build In Progress

By the way, say you’ve made some changes to one of these editors, and you want to save or share them? Hit the fork button, and you’ll have your very own Demoboard. Just copy and paste the new page’s URL to share it — and login with GitHub to make sure you can save new changes down the track.

#But how does all this magic work?

Simple. The editor transpiles your code with Babel, uses a babel plugin to find any resulting require() statements, and then uses this info to build a special browser-loadable module for each file.

But what about bare imports like react, vue or @material-ui/core/Button? Demoboard downloads each file from UNPKG on the fly, then applies the same CommonJS special-sauce transform on UNPKG modules. Thanks to UNPKG’s ability to put version ranges in URLs, you can even request specific versions of NPM packages, like this React Hooks example that imports react@next:

index.js
styles.css
import React from 'react@next'
import ReactDOM from 'react-dom@next'

function App() {
  let [count, setCount] = React.useState(1)

  return (
    <div className='center'>
      <button onClick={() => setCount(count + 1)}>
        <h1>{"Hello, ".repeat(count)}React Hooks!</h1>
      </button>
    </div>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
Build In Progress

#An editor for learning.

Ok, this is cool and all. But what’s the point? Isn’t Frontend Armory supposed to teach me JavaScript?

The thing about learning is that it takes effort. It takes practice. And that’s why Demoboard is designed to get out of your way and make coding fun — the more JavaScript you write, the more you learn.

Of course, an editor will only get you so far. Students need guidance. Frontend Armory has some great Demoboard based courses and guides, but there’s always more ground to cover. And that’s where you come in.

Demoboard is designed to let you create demos and lessons for your favorite topic, using MDX.

This is unreal!

#README.md

You know how GitHub displays the README.md file when you load a project? So does Demoboard. Here’s the README for Demoboard itself:

README.md
Cards.js
index.js
styles.css
import { Card, CardGrid, TwitterButton } from './Cards'

<h1 className='frontend-armory'>
  <img src="https://frontarm.com/logo.png" alt="Frontend Armory Logo" />
  Frontend Armory
  <span className='demoboard'>Demoboard</span>
</h1>

<CardGrid>
  <Card
    title="React"
    color="#12c6e4"
    href="https://frontarm.com/demoboard/?id=bff12110-d1c6-46d9-b384-706b84bd9dcb"
  />
  <Card
    title="Vue.js"
    color="#4fc08d"
    href="https://frontarm.com/demoboard/?id=ea41c975-72b2-4c7a-a3f9-e29eb9acd6d2"
  />
  <Card
    title="Vanilla Javascript"
    color="#f2dc3e"
    textColor="black"
    href="https://frontarm.com/demoboard/?id=344821fa-577d-42ed-939c-8d6468d7685c"
  />
  <Card
    title="React with Hooks"
    color="#dd3c6f"
    href="https://frontarm.com/demoboard/?id=baab500e-ba99-4709-abff-aab99af2043b"
  />
</CardGrid>


The simplest editor alive.
--------------------------

Try pasting this code into the editor on the left:

```markdown
import Button from '@material-ui/core/Button'

# Hello World

<p>
<Button variant='outlined' color='primary'>
I'm a material button!
</Button>
</p>

Now hit ctrl-z/command-z to undo your change.
```

Thanks to [MDX](https://mdxjs.com), you can intermix Markdown and JSX. Thanks to [UNPKG](https://unpkg.com/#/) and a little magic, you can import *any* package from NPM. And all with an instant preview as you type.


You can...
----------

- Import Markdown files as React components
- Import React components into Markdown files
- Add a `README.md` file that will be displayed on load
- Add an `index.html` file to customize your HTML
- Add a `markdown.css` file to edit the default markdown styles
- Add styles with `.css` and `.scss` files
- Specify versions for NPM imports with `@`
- Login to save new versions of a demo
- Export sources to a zip file


> Noooo way.

<TwitterButton />


Learning a lot from these demos?
--------------------------------

Then you'll learn even more from our courses! They're packed full of demos and live exercises, so that you can build confidence with the topic *while* you learn.

- [React fundamentals &raquo](https://frontarm.com/courses/react-fundamentals/)
- [Mastering Asynchronous JavaScript &raquo](https://frontarm.com/courses/async-javascript/)
Build In Progress

Under the hood, Demoboard uses MDX to turn markdown into JSX. This means that you can write in Markdown like you’re used to, but you can still import any React component on NPM. There’s also a button at the top-right to toggle between /README.md and /, letting you jump quickly between a demo and it’s docs.

#What will you demo?

My favorite thing about Demoboard is the “fork” button. With a single button press, you have a new URL all to yourself. Just start typing, then show the world… and show me too, because I can’t wait to see what you make.

To give you some idea of the possibilities, I’ll be sharing some demos that the community have put together here.

First up is a HTML/SASS only Demoboard by Nathan Taylor — who is also behind the design of Frontend Armory itself.

style.scss
index.html
//Color Functions
$hue: 225;
$shift: 30;
$color: hsl($hue,30%,60%);
$light: lighten(adjust-hue($color,$shift), 20%);
$lighter: lighten(adjust-hue($color,$shift*2), 40%);
$dark: darken(adjust-hue($color,-$shift), 10%);
$darker: darken(adjust-hue($color,-$shift*2), 20%);
$pop:  hsl($hue - 180,80%,60%);

//Config Vars
$base: 1.2vh;
$short: 200ms;
$mid: 600ms;
$long: 2000ms;
$ease-in: cubic-bezier(0.755, 0.050, 0.855, 0.060); 
$ease-out: cubic-bezier(0.230, 1.000, 0.320, 1.000); 
$grid-size: 10rem;
$rows: 5;
$cols: 5;

@mixin abfab{
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

//the rest of my typically messy styling
.level{
  transition: transform $mid $ease-out, opacity $mid $ease-out;
  opacity: 0;
  transform: translateY(-$grid-size*2)  translateX($grid-size*2);
  pointer-events: none;
  &[l="1"]{
    opacity: 1;
    pointer-events: auto;
    transform: translateY(0)  translateX(0);
  }
  &:hover .finish{
    transform: scale(1);
    opacity: 1;
    pointer-events: auto;
    transition: transform $short $ease-out, opacity $short linear;
  }
}

input{
  opacity: 0;
  pointer-events: none;
}

input:checked + .level + input + .level{
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0)  translateX(0);
}

input:checked + .level{
  opacity: 0 !important;
  pointer-events: none !important;
  transform: translateY($grid-size*2)  translateX(-$grid-size*2) !important;
}

.button, .goal{
  position: absolute;
  top: 5%;
  left: 20%;
  width: 75%;
  height: 75%;
  border-radius: $grid-size;
  background: $pop;
  text-align: center;
  line-height: $grid-size*0.8;
  box-shadow: -$grid-size*0.05 $grid-size*0.05 $grid-size*0.05 $color;
  transition: box-shadow $short $ease-out, transform $short $ease-out;
  &:hover{
    transform: translateX($grid-size*0.05) translateY(-$grid-size*0.05);
    box-shadow: -$grid-size*0.2 $grid-size*0.2 $grid-size*0.3 $color;
  }
  &:active{
    transform: translateX($grid-size*-0.05) translateY(-$grid-size*-0.05);
    box-shadow: -$grid-size*0 $grid-size*0 $grid-size*0 $color;
  }
  cursor: pointer;
}

.button{
  background: $light;
}

input:checked + .button{
  pointer-events: none;
  transform: translateX($grid-size*-0.05) translateY(-$grid-size*-0.05);
  box-shadow: -$grid-size*0 $grid-size*0 $grid-size*0 $color;
}

#level-one{
  position: absolute;
  z-index: 10000;
}

.step{
  width: $grid-size*1.01;
  height: $grid-size*1.01;
  background: $light;
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
  transform: translateY($grid-size/2)  translateX(-$grid-size/2);
  pointer-events: none;
  transition: transform $mid $ease-in, opacity $mid linear;
  
  &:before, &:after{
    content: '';
    @include abfab;
    pointer-events: none !important;
  }
  &:before{
    left: -20%;
    background: $dark;
    transform-origin: 100% 100%;
    transform: skewy(-45deg);
    width: 20%;
  }
  
  &:after{
    top: 100%;
    background: $color;
    transform-origin: 0% 0%;
    transform: skewX(-45deg);
    height: 20%;
  }
  
  &.start, &.finish{
    &:before{
      left: -50%;
      width: 50%;
    }
    &:after{
      height: 50%;
    }
  }
  
  &.start,
  &:hover, 
  &:hover + .step, 
  &:hover + .path > .step:first-child, 
  &:hover + .path + .step,
  &:hover + .bridge > input:checked + .step,
  &:hover + .bridge > input:not(:checked) + .step + .step{
    transform: scale(1);
    opacity: 1;
    pointer-events: auto;
    transition: transform $short $ease-out, opacity $short linear;
  }
  
  @for $r from 1 through 4 {
    &[r="#{$r}"]{
      top: $r*10rem;
    }
  }
  
  @for $c from 1 through 4 {
    &[c="#{$c}"]{
      left: $c*10rem;
    }
  }
  
  @for $r from 0 through 4 {
    @for $c from 0 through 4 {
      &[c="#{$c}"][r="#{$r}"]{
        z-index: ( $r + 1 ) * ( $cols - $c );
      }
    }
  }

}

.bridge{
  pointer-events: none;
}

.center{
  position: absolute;
  top: 50%;
  left: 50%;
  width: $grid-size * $cols;
  height: $grid-size * $rows;
  transform: perspective(100rem) translate3d(-50%,-50%,0) rotateZ(-45deg);
}

html{
  font-size: $base;
  
}

body{
  height: 100vh;
  width: 100vw;
  overflow: hidden;
  background: $darker;
  color: white;
  font-size: 2rem;
  font-family: 'Source Sans Pro', sans-serif;
}
h1{
  font-size: 10rem;
  margin: 0;
  font-family: 'Teko', sans-serif;
}
p{
  margin-top: 0;
}

.share{
  display: inline-block;
  margin-top: 2rem;
  text-decoration: none;
  padding: 2rem;
  border-radius: 5rem;
  background: $pop;
  font-family: 'Teko', sans-serif;
  color: darken($darker,10%);
  box-shadow: -$grid-size*0.05 $grid-size*0.05 $grid-size*0.05 darken($darker,10%);
  transition: box-shadow $short $ease-out, transform $short $ease-out;
  &:hover{
    transform: translateX($grid-size*0.1) translateY(-$grid-size*0.1);
    box-shadow: -$grid-size*0.15 $grid-size*0.15 $grid-size*0.25 darken($darker,10%);
  }
  &:active{
    transform: translateX($grid-size*-0.05) translateY(-$grid-size*-0.05);
    box-shadow: -$grid-size*0 $grid-size*0 $grid-size*0 darken($darker,10%);
  }
  cursor: pointer;
}
.message{
  transform: rotate(45deg) translatey(-10rem);
  text-align: center;
  width: 40rem;
}



.sig{
  position: fixed;
  bottom: 8px;
  right: 8px;
  text-decoration: none;
  font-size: 12px;
  font-weight: 100;
  font-family: sans-serif;
  color: rgba(255,255,255,0.4);
  letter-spacing: 2px;
}
Build In Progress

Have you built a neat Demoboard that you’d like to put here? Let me know at james@frontarm.com!

#How you can help

My mission with Frontend Armory is to create the most effective way to learn frontend engineering on the internet. That’s a big goal, and I can’t do it alone. Here’s how you can help:

  • If you’ve created a demo, then show it off with the #demoboard hash tag! I’ll be blogging about what people create.
  • Share this post using the buttons on the left.
  • If there’s anything you love or hate, let me know at james@frontarm.com — your honest feedback is invaluable.
  • Are you learning JavaScript/React, or managing a team that is? Then join Frontend Armory Pro, or get in touch to discuss how I can help. Incidentally, it’s membership is currently half price for Black Friday!
  • If you just want to support the project but don’t need the courses, then join Frontend Armory Pro anyway!
  • And if you haven’t already, create a free account to stay in the loop with new demos, tools and courses.

Finally, I want to thank you for reading. Putting Demoboard together has been an adventure, and I’m super grateful that you’re showing an interest. I’ll be back in touch soon with some examples of what people have created — stay tuned!

About James

Hi! I've been playing with JavaScript for over half my life, and am building Frontend Armory to help share what I've learned along the way!

Tokyo, Japan