Let's Make an Interactive Coding Tutorial
Tags: livecodes, tutorial, webdev, javascript
Published: 2023-08-05 06:31:51

Let's assume we want to make an interactive coding tutorial on a blog or a tutorials website. The tutorial should have a playground that helps us guide the students to write code, see the results and complete an assignment task.

We are going to use [LiveCodes](https://livecodes.io/docs) and its powerful [SDK](https://livecodes.io/docs/sdk) to achieve this. So, here we go.

<small>This article was originally published at [LiveCodes blog](https://blog.livecodes.io/lets-make-an-interactive-coding-tutorial).</small>

<hr />

## What Are We Building?

We are going to build a playground for a simple interactive coding tutorial.

We will add some HTML, add styles using CSS and dynamically change its content using JavaScript. The student can interact with the playground, add code and see its result. At the end, the student is asked to complete a simple task (e.g. changing the value of a variable), then the task is verified and a feedback is given if the task was completed successfully.

## Demo

Check a live demo on [LiveCodes blog](https://blog.livecodes.io/lets-make-an-interactive-coding-tutorial#demo).

The full source code (in vanilla JavaScript and also in React TSX) can be found [here](https://blog.livecodes.io/lets-make-an-interactive-coding-tutorial/#full-code).

<hr />

## What is LiveCodes?

LiveCodes is a [feature-rich](https://livecodes.io/docs/features/), [open-source](https://github.com/live-codes/livecodes), [client-side](https://livecodes.io/docs/why#client-side), code playground that supports [80+ languages and frameworks](https://livecodes.io/docs/languages/). Playgrounds can be [embedded](https://livecodes.io/docs/features/embeds) in any web pages. A powerful, yet easy-to-use, [SDK](https://livecodes.io/docs/sdk) allows creating and communicating with playgrounds.

LiveCodes is free with no limits to usage, no ads and no account required. It is [MIT-licensed](https://livecodes.io/docs/license) which allows its use, distribution and modification even for commercial projects.

Read more in the [announcement](https://dev.to/livecodes_io/introducing-livecodes-3300).

{% embed https://github.com/live-codes/livecodes %}

Enough talk, let's get started!

## The Playground

The LiveCodes [SDK](https://livecodes.io/docs/sdk) makes it easy to create code playgrounds. The SDK can be [loaded from CDN](https://livecodes.io/docs/sdk/#cdn) or can be [installed from npm](https://livecodes.io/docs/sdk/#npm-package). To keep things simple, we will just use the CDN.

The SDK is available for [vanilla JavaScript/TypeScript](https://livecodes.io/docs/sdk/js-ts), [React](https://livecodes.io/docs/sdk/react), [Vue](https://livecodes.io/docs/sdk/vue) and [Svelte](https://livecodes.io/docs/sdk/svelte). Again, for simplicity, we will use vanilla JavaScript.

A new playground can be created using the [`createPlayground`](https://livecodes.io/docs/sdk/js-ts#createplayground) function. It takes the container HTML element (or its selector) and optionally [embed options](https://livecodes.io/docs/sdk/js-ts#embed-options).

```html
<div id="container"></div>

<script type="module">
  import { createPlayground } from 'https://unpkg.com/livecodes';

  createPlayground('#container', { /* embed options */ });
</script>
```


<small>[Preview](https://livecodes.io/?mode=result&html=%3Cdiv+id%3D%22container%22%3E%3C%2Fdiv%3E%0A%0A%3Cscript+type%3D%22module%22%3E%0A++import+%7B+createPlayground+%7D+from+%27https%3A%2F%2Funpkg.com%2Flivecodes%27%3B%0A%0A++createPlayground%28%27%23container%27%2C+%7B+%2F*+embed+options+*%2F+%7D%29%3B%0A%3C%2Fscript%3E%0A&css=body+%7B+margin%3A+50px+2em%3B+%7D")</small>


This creates a playground that the user can interact with and see the result immediately, without having to install or build anything (not even for any of the [supported languages](https://livecodes.io/docs/languages/)).

It is generally a good practice to specify the SDK version and a [permanent URL](https://livecodes.io/docs/features/permanent-url) for the app to avoid later potential breaking changes with updates to the SDK or the app.

```html
<div id="container"></div>

<script type="module">
  import { createPlayground } from 'https://unpkg.com/livecodes@0.4.0';

  const options = {
    // permanent URL avoids later breaking changes
    appUrl: 'https://v25.livecodes.io',
  };
  createPlayground('#container', options);
</script>
```

<small>[Preview](https://livecodes.io/?mode=result&html=%3Cdiv+id%3D%22container%22%3E%3C%2Fdiv%3E%0A%0A%3Cscript+type%3D%22module%22%3E%0A++%2F%2F+highlight-next-line%0A++import+%7B+createPlayground+%7D+from+%27https%3A%2F%2Funpkg.com%2Flivecodes%400.1.2%27%3B%0A%0A++const+options+%3D+%7B%0A++++%2F%2F+highlight-next-line%0A++++appUrl%3A+%27https%3A%2F%2Fv25.livecodes.io%27%2C%0A++%7D%3B%0A++createPlayground%28%27%23container%27%2C+options%29%3B%0A%3C%2Fscript%3E%0A&css=body+%7B+margin%3A+50px+2em%3B+%7D)</small>

We now have an empty playground. Let's fill it with code.

## Adding Content

When creating the playground, embed options can be used to add content (e.g. using the options [`config`](https://livecodes.io/docs/sdk/js-ts#config), [`params`](https://livecodes.io/docs/sdk/js-ts#params), [`import`](https://livecodes.io/docs/sdk/js-ts#import) or [`template`](https://livecodes.io/docs/sdk/js-ts#template)).

This loads the playground prefilled with the code you specified.

```html
<div id="container"></div>

<script type="module">
  import { createPlayground } from 'https://unpkg.com/livecodes@0.4.0';

  const options = {
    appUrl: 'https://v25.livecodes.io',
    config: {
      markup: {
        language: 'html',
        content: 'Welcome to the tutorial!',
      },
    },
  };
  createPlayground('#container', options);
</script>
```

<small>[Preview](https://livecodes.io/?mode=result&html=%3Cdiv+id%3D%22container%22%3E%3C%2Fdiv%3E%0A%0A%3Cscript+type%3D%22module%22%3E%0A++import+%7B+createPlayground+%7D+from+%27https%3A%2F%2Funpkg.com%2Flivecodes%400.1.2%27%3B%0A%0A++const+options+%3D+%7B%0A++++appUrl%3A+%27https%3A%2F%2Fv25.livecodes.io%27%2C%0A++++%2F%2F+highlight-start%0A++++config%3A+%7B%0A++++++markup%3A+%7B%0A++++++++language%3A+%27html%27%2C%0A++++++++content%3A+%27Welcome+to+the+tutorial%21%27%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++++%2F%2F+highlight-end%0A++%7D%3B%0A++createPlayground%28%27%23container%27%2C+options%29%3B%0A%3C%2Fscript%3E%0A&css=body+%7B+margin%3A+50px+2em%3B+%7D)</small>


## Adding Tutorial Steps

Now the student can see the playground, interact with it and see the result. So let's add some tutorial steps, where new code is added to the playground in different editors (HTML, CSS and JavaScript). This can gradually introduce new concepts to the student without losing context or having to move to a new playground.

The [`createPlayground`](https://livecodes.io/docs/sdk/js-ts#createplayground) function returns a promise that resolves to the playground instance. This instance has some useful [methods](https://livecodes.io/docs/sdk/js-ts/#sdk-methods) that allows interaction with the already running playground (e.g. [`run`](https://livecodes.io/docs/sdk/js-ts#run), [`getConfig`](https://livecodes.io/docs/sdk/js-ts#getconfig), [`setConfig`](https://livecodes.io/docs/sdk/js-ts#setconfig), [`getCode`](https://livecodes.io/docs/sdk/js-ts#getcode), [`format`](https://livecodes.io/docs/sdk/js-ts#format), [`show`](https://livecodes.io/docs/sdk/js-ts#show), [`runTests`](https://livecodes.io/docs/sdk/js-ts#runtests), [`onChange`](https://livecodes.io/docs/sdk/js-ts#onchange), [`getShareUrl`](https://livecodes.io/docs/sdk/js-ts#getshareurl)).

```html
<div id="container"></div>
<button id="next">Next</button>

<script type="module">
  import { createPlayground } from "https://unpkg.com/livecodes@0.4.0";

  // add data for tutorial steps
  const steps = [
    {
      config: {
        markup: {
          language: "html",
          content: '<h1>Hello,\n  <span id="title">World</span>!\n</h1>',
        },
      },
    },
  ];

  const options = {
    appUrl: "https://v25.livecodes.io",
    config: {
      markup: {
        language: "html",
        content: "Welcome to the tutorial!",
      },
    },
  };

  createPlayground("#container", options).then((playground) => {
    const nextBtn = document.querySelector("#next");
    nextBtn.addEventListener("click", async () => {
      const currentStep = steps[0];
      // send the config object to the playground
      await playground.setConfig(currentStep.config);
    });
  });
</script>
```

<small>[Preview](https://livecodes.io/?mode=result&html=%3Cdiv+id%3D%22container%22%3E%3C%2Fdiv%3E%0A%3C%21--+highlight-next-line+--%3E%0A%3Cbutton+id%3D%22next%22%3ENext%3C%2Fbutton%3E%0A%0A%3Cscript+type%3D%22module%22%3E%0A++import+%7B+createPlayground+%7D+from+%22https%3A%2F%2Funpkg.com%2Flivecodes%400.1.2%22%3B%0A%0A++%2F%2F+highlight-start%0A++const+steps+%3D+%5B%0A++++%7B%0A++++++config%3A+%7B%0A++++++++markup%3A+%7B%0A++++++++++language%3A+%22html%22%2C%0A++++++++++content%3A+%27%3Ch1%3EHello%2C%5Cn++%3Cspan+id%3D%22title%22%3EWorld%3C%2Fspan%3E%21%5Cn%3C%2Fh1%3E%27%2C%0A++++++++%7D%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++%5D%3B%0A++%2F%2F+highlight-end%0A%0A++const+options+%3D+%7B%0A++++appUrl%3A+%22https%3A%2F%2Fv25.livecodes.io%22%2C%0A++++config%3A+%7B%0A++++++markup%3A+%7B%0A++++++++language%3A+%22html%22%2C%0A++++++++content%3A+%22Welcome+to+the+tutorial%21%22%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++%7D%3B%0A%0A++%2F%2F+highlight-start%0A++createPlayground%28%22%23container%22%2C+options%29.then%28%28playground%29+%3D%3E+%7B%0A++++const+nextBtn+%3D+document.querySelector%28%22%23next%22%29%3B%0A++++nextBtn.addEventListener%28%22click%22%2C+async+%28%29+%3D%3E+%7B%0A++++++const+currentStep+%3D+steps%5B0%5D%3B%0A++++++await+playground.setConfig%28currentStep.config%29%3B%0A++++%7D%29%3B%0A++%7D%29%3B%0A++%2F%2F+highlight-end%0A%3C%2Fscript%3E%0A&css=body+%7B+margin%3A+50px+2em%3B+%7D)</small>

Now we have defined an array (`steps`) that will hold the data of each step in the tutorial. The first step has a [`config` object](https://livecodes.io/docs/configuration/configuration-object) that is to be sent to the playground.

A button with the id `"next"` controls moving between steps. An event handler for its `click` event sends the current step `config` to the playground using the method [`setConfig`](https://livecodes.io/docs/sdk/js-ts#setconfig).

## More Steps

So, let's add some more steps. Clicking the button should move us to the next step.

```html
<div id="container"></div>
<button id="next">Next</button>

<script type="module">
  import { createPlayground } from "https://unpkg.com/livecodes@0.4.0";

  const steps = [
    {
      config: {
        markup: {
          language: "html",
          content: '<h1>Hello,\n  <span id="title">World</span>!\n</h1>',
        },
      },
    },
    // add a new step
    {
      config: {
        markup: {
          language: "html",
          content: '<h1>Hello,\n  <span id="title">World</span>!\n</h1>',
        },
        style: {
          language: 'css',
          content: '#title {\n  color: blue;\n}',
        },
      },
    },    
  ];

  const options = {
    appUrl: "https://v25.livecodes.io",
    config: {
      markup: {
        language: "html",
        content: "Welcome to the tutorial!",
      },
    },
  };

  createPlayground("#container", options).then((playground) => {
    let step = 0;
    const nextBtn = document.querySelector("#next");
    nextBtn.addEventListener("click", async () => {
      const lastStep = steps.length - 1;
      const currentStep = steps[step];
      await playground.setConfig(currentStep.config);
      step = step < lastStep ? step + 1 : 0;
    });
  });
</script>
```

<small>[Preview](https://livecodes.io/?mode=result&html=%3Cdiv+id%3D%22container%22%3E%3C%2Fdiv%3E%0A%3Cbutton+id%3D%22next%22%3ENext%3C%2Fbutton%3E%0A%0A%3Cscript+type%3D%22module%22%3E%0A++import+%7B+createPlayground+%7D+from+%22https%3A%2F%2Funpkg.com%2Flivecodes%400.1.2%22%3B%0A%0A++const+steps+%3D+%5B%0A++++%7B%0A++++++config%3A+%7B%0A++++++++markup%3A+%7B%0A++++++++++language%3A+%22html%22%2C%0A++++++++++content%3A+%27%3Ch1%3EHello%2C%5Cn++%3Cspan+id%3D%22title%22%3EWorld%3C%2Fspan%3E%21%5Cn%3C%2Fh1%3E%27%2C%0A++++++++%7D%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++++%2F%2F+highlight-start%0A++++%7B%0A++++++config%3A+%7B%0A++++++++markup%3A+%7B%0A++++++++++language%3A+%22html%22%2C%0A++++++++++content%3A+%27%3Ch1%3EHello%2C%5Cn++%3Cspan+id%3D%22title%22%3EWorld%3C%2Fspan%3E%21%5Cn%3C%2Fh1%3E%27%2C%0A++++++++%7D%2C%0A++++++++style%3A+%7B%0A++++++++++language%3A+%27css%27%2C%0A++++++++++content%3A+%27%23title+%7B%5Cn++color%3A+blue%3B%5Cn%7D%27%2C%0A++++++++%7D%2C%0A++++++%7D%2C%0A++++%7D%2C++++%0A++++%2F%2F+highlight-end%0A++%5D%3B%0A%0A++const+options+%3D+%7B%0A++++appUrl%3A+%22https%3A%2F%2Fv25.livecodes.io%22%2C%0A++++config%3A+%7B%0A++++++markup%3A+%7B%0A++++++++language%3A+%22html%22%2C%0A++++++++content%3A+%22Welcome+to+the+tutorial%21%22%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++%7D%3B%0A%0A++createPlayground%28%22%23container%22%2C+options%29.then%28%28playground%29+%3D%3E+%7B%0A++++%2F%2F+highlight-next-line%0A++++let+step+%3D+0%3B%0A++++const+nextBtn+%3D+document.querySelector%28%22%23next%22%29%3B%0A++++nextBtn.addEventListener%28%22click%22%2C+async+%28%29+%3D%3E+%7B%0A++++++%2F%2F+highlight-start%0A++++++const+lastStep+%3D+steps.length+-+1%3B%0A++++++const+currentStep+%3D+steps%5Bstep%5D%3B%0A++++++await+playground.setConfig%28currentStep.config%29%3B%0A++++++step+%3D+step+%3C+lastStep+%3F+step+%2B+1+%3A+0%3B%0A++++++%2F%2F+highlight-end%0A++++%7D%29%3B%0A++%7D%29%3B%0A%3C%2Fscript%3E%0A&css=body+%7B+margin%3A+50px+2em%3B+%7D)</small>

The variable `step` keeps track of the step index. The corresponding data is read from the `steps` array and sent to the playground.

However, there is now a lot of duplication in the code of each step. Also, if the student makes some changes to the HTML, the next step will overwrite them.

So, let's read the current `config` object from the playground (using the method [`getConfig`](https://livecodes.io/docs/sdk/js-ts#getconfig)) and then merge the changes for the new step. Now, we can remove the duplication in steps.

```html
<div id="container"></div>
<button id="next">Next</button>

<script type="module">
  import { createPlayground } from "https://unpkg.com/livecodes@0.4.0";

  const steps = [
    {
      config: {
        markup: {
          language: "html",
          content: '<h1>Hello,\n  <span id="title">World</span>!\n</h1>',
        },
      },
    },
    {
      config: {
        // remove duplication
        style: {
          language: 'css',
          content: '#title {\n  color: blue;\n}',
        },
      },
    },    
  ];

  const options = {
    appUrl: "https://v25.livecodes.io",
    config: {
      markup: {
        language: "html",
        content: "Welcome to the tutorial!",
      },
    },
  };

  createPlayground("#container", options).then((playground) => {
    let step = 0;
    const nextBtn = document.querySelector("#next");
    nextBtn.addEventListener("click", async () => {
      const lastStep = steps.length - 1;
      const currentStep = steps[step];
      // get previous config
      const prevConfig = step === 0 ? options.config : await playground.getConfig();
      // merge previous with new config
      await playground.setConfig({ ...prevConfig, ...currentStep.config });
      step = step < lastStep ? step + 1 : 0;
    });
  });
</script>
```

<small>[Preview](https://livecodes.io/?mode=result&html=%3Cdiv+id%3D%22container%22%3E%3C%2Fdiv%3E%0A%3Cbutton+id%3D%22next%22%3ENext%3C%2Fbutton%3E%0A%0A%3Cscript+type%3D%22module%22%3E%0A++import+%7B+createPlayground+%7D+from+%22https%3A%2F%2Funpkg.com%2Flivecodes%400.1.2%22%3B%0A%0A++const+steps+%3D+%5B%0A++++%7B%0A++++++config%3A+%7B%0A++++++++markup%3A+%7B%0A++++++++++language%3A+%22html%22%2C%0A++++++++++content%3A+%27%3Ch1%3EHello%2C%5Cn++%3Cspan+id%3D%22title%22%3EWorld%3C%2Fspan%3E%21%5Cn%3C%2Fh1%3E%27%2C%0A++++++++%7D%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++++%7B%0A++++++%2F%2F+highlight-start%0A++++++config%3A+%7B%0A++++++++style%3A+%7B%0A++++++++++language%3A+%27css%27%2C%0A++++++++++content%3A+%27%23title+%7B%5Cn++color%3A+blue%3B%5Cn%7D%27%2C%0A++++++++%7D%2C%0A++++++%7D%2C%0A++++++%2F%2F+highlight-end%0A++++%7D%2C++++%0A++%5D%3B%0A%0A++const+options+%3D+%7B%0A++++appUrl%3A+%22https%3A%2F%2Fv25.livecodes.io%22%2C%0A++++config%3A+%7B%0A++++++markup%3A+%7B%0A++++++++language%3A+%22html%22%2C%0A++++++++content%3A+%22Welcome+to+the+tutorial%21%22%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++%7D%3B%0A%0A++createPlayground%28%22%23container%22%2C+options%29.then%28%28playground%29+%3D%3E+%7B%0A++++let+step+%3D+0%3B%0A++++const+nextBtn+%3D+document.querySelector%28%22%23next%22%29%3B%0A++++nextBtn.addEventListener%28%22click%22%2C+async+%28%29+%3D%3E+%7B%0A++++++const+lastStep+%3D+steps.length+-+1%3B%0A++++++const+currentStep+%3D+steps%5Bstep%5D%3B%0A++++++%2F%2F+highlight-start%0A++++++const+prevConfig+%3D+step+%3D%3D%3D+0+%3F+options.config+%3A+await+playground.getConfig%28%29%3B%0A++++++await+playground.setConfig%28%7B+...prevConfig%2C+...currentStep.config+%7D%29%3B%0A++++++%2F%2F+highlight-end%0A++++++step+%3D+step+%3C+lastStep+%3F+step+%2B+1+%3A+0%3B%0A++++%7D%29%3B%0A++%7D%29%3B%0A%3C%2Fscript%3E%0A&css=body+%7B+margin%3A+50px+2em%3B+%7D)</small>

## Changing Layout

We still have a problem!

Although we were able to add CSS code, the style editor was not activated. We can correct this by using the [`show`](https://livecodes.io/docs/sdk/js-ts#show) method.

We can even control the position of the cursor, so that your student is guided where to start typing.

```html
<div id="container"></div>
<button id="next">Next</button>

<script type="module">
  import { createPlayground } from "https://unpkg.com/livecodes@0.4.0";

  const steps = [
    {
      // active editor and cursor position
      panel: 'markup',
      position: { line: 2, column: 20 },
      config: {
        markup: {
          language: "html",
          content: '<h1>Hello,\n  <span id="title">World</span>!\n</h1>',
        },
      },
    },
    {
      panel: 'style',
      position: { line: 2, column: 10 },
      config: {
        style: {
          language: 'css',
          content: '#title {\n  color: blue;\n}',
        },
      },
    },    
  ];

  const options = {
    appUrl: "https://v25.livecodes.io",
    config: {
      markup: {
        language: "html",
        content: "Welcome to the tutorial!",
      },
    },
  };

  createPlayground("#container", options).then((playground) => {
    let step = 0;
    const nextBtn = document.querySelector("#next");
    nextBtn.addEventListener("click", async () => {
      const lastStep = steps.length - 1;
      const currentStep = steps[step];
      const prevConfig = step === 0 ? options.config : await playground.getConfig();
      await playground.setConfig({ ...prevConfig, ...currentStep.config });
      // set layout
      await playground.show(currentStep.panel, currentStep.position);
      step = step < lastStep ? step + 1 : 0;
    });
  });
</script>
```

<small>[Preview](https://livecodes.io/?mode=result&html=%3Cdiv+id%3D%22container%22%3E%3C%2Fdiv%3E%0A%3Cbutton+id%3D%22next%22%3ENext%3C%2Fbutton%3E%0A%0A%3Cscript+type%3D%22module%22%3E%0A++import+%7B+createPlayground+%7D+from+%22https%3A%2F%2Funpkg.com%2Flivecodes%400.1.2%22%3B%0A%0A++const+steps+%3D+%5B%0A++++%7B%0A++++++%2F%2F+highlight-start%0A++++++panel%3A+%27markup%27%2C%0A++++++position%3A+%7B+line%3A+2%2C+column%3A+20+%7D%2C%0A++++++%2F%2F+highlight-end%0A++++++config%3A+%7B%0A++++++++markup%3A+%7B%0A++++++++++language%3A+%22html%22%2C%0A++++++++++content%3A+%27%3Ch1%3EHello%2C%5Cn++%3Cspan+id%3D%22title%22%3EWorld%3C%2Fspan%3E%21%5Cn%3C%2Fh1%3E%27%2C%0A++++++++%7D%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++++%7B%0A++++++%2F%2F+highlight-start%0A++++++panel%3A+%27style%27%2C%0A++++++position%3A+%7B+line%3A+2%2C+column%3A+10+%7D%2C%0A++++++%2F%2F+highlight-end%0A++++++config%3A+%7B%0A++++++++style%3A+%7B%0A++++++++++language%3A+%27css%27%2C%0A++++++++++content%3A+%27%23title+%7B%5Cn++color%3A+blue%3B%5Cn%7D%27%2C%0A++++++++%7D%2C%0A++++++%7D%2C%0A++++%7D%2C++++%0A++%5D%3B%0A%0A++const+options+%3D+%7B%0A++++appUrl%3A+%22https%3A%2F%2Fv25.livecodes.io%22%2C%0A++++config%3A+%7B%0A++++++markup%3A+%7B%0A++++++++language%3A+%22html%22%2C%0A++++++++content%3A+%22Welcome+to+the+tutorial%21%22%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++%7D%3B%0A%0A++createPlayground%28%22%23container%22%2C+options%29.then%28%28playground%29+%3D%3E+%7B%0A++++let+step+%3D+0%3B%0A++++const+nextBtn+%3D+document.querySelector%28%22%23next%22%29%3B%0A++++nextBtn.addEventListener%28%22click%22%2C+async+%28%29+%3D%3E+%7B%0A++++++const+lastStep+%3D+steps.length+-+1%3B%0A++++++const+currentStep+%3D+steps%5Bstep%5D%3B%0A++++++const+prevConfig+%3D+step+%3D%3D%3D+0+%3F+options.config+%3A+await+playground.getConfig%28%29%3B%0A++++++await+playground.setConfig%28%7B+...prevConfig%2C+...currentStep.config+%7D%29%3B%0A++++++%2F%2F+highlight-next-line%0A++++++await+playground.show%28currentStep.panel%2C+currentStep.position%29%3B%0A++++++step+%3D+step+%3C+lastStep+%3F+step+%2B+1+%3A+0%3B%0A++++%7D%29%3B%0A++%7D%29%3B%0A%3C%2Fscript%3E%0A&css=body+%7B+margin%3A+50px+2em%3B+%7D)</small>

Now, let's add some steps for JavaScript code. We can even open the integrated [console](https://livecodes.io/docs/features/console) to show logs. Let's also change the text of the "next" button with each step.

```html
<div id="container"></div>
<button id="next">Start Tutorial</button>

<script type="module">
  import { createPlayground } from "https://unpkg.com/livecodes@0.4.0";

  const steps = [
    {
      btnText: 'Next',
      panel: 'markup',
      position: { line: 2, column: 20 },
      config: {
        markup: {
          language: "html",
          content: '<h1>Hello,\n  <span id="title">World</span>!\n</h1>',
        },
      },
    },
    {
      btnText: 'Next',
      panel: 'style',
      position: { line: 2, column: 10 },
      config: {
        style: {
          language: 'css',
          content: '#title {\n  color: blue;\n}',
        },
      },
    },
    // add new steps
    {
      btnText: 'Next',
      panel: 'script',
      position: { line: 2, column: 19 },
      config: {
        script: {
          language: 'javascript',
          content:
            '// change this value to "LiveCodes"\nconst newTitle = "???";\ndocument.querySelector("#title").textContent = newTitle;\nconsole.log(newTitle);',
        },
      },
    },
    {
      btnText: 'Final Result',
      panel: 'console',
    },
    {
      btnText: 'Restart Tutorial',
      panel: 'result',
    },
  ];

  const options = {
    appUrl: "https://v25.livecodes.io",
    config: {
      markup: {
        language: "html",
        content: "Welcome to the tutorial!",
      },
    },
  };

  createPlayground("#container", options).then((playground) => {
    let step = 0;
    const nextBtn = document.querySelector("#next");
    nextBtn.addEventListener("click", async () => {
      const lastStep = steps.length - 1;
      const currentStep = steps[step];
      const prevConfig = step === 0 ? options.config : await playground.getConfig();
      await playground.setConfig({ ...prevConfig, ...currentStep.config });
      await playground.show(currentStep.panel, currentStep.position);
      // change the button text for new steps
      nextBtn.textContent = currentStep.btnText;
      step = step < lastStep ? step + 1 : 0;
    });
  });
</script>
```

<small>[Preview](https://livecodes.io/?mode=result&html=%3Cdiv+id%3D%22container%22%3E%3C%2Fdiv%3E%0A%3C%21--+highlight-next-line+--%3E%0A%3Cbutton+id%3D%22next%22%3EStart+Tutorial%3C%2Fbutton%3E%0A%0A%3Cscript+type%3D%22module%22%3E%0A++import+%7B+createPlayground+%7D+from+%22https%3A%2F%2Funpkg.com%2Flivecodes%400.1.2%22%3B%0A%0A++const+steps+%3D+%5B%0A++++%7B%0A++++++%2F%2F+highlight-next-line%0A++++++btnText%3A+%27Next%27%2C%0A++++++panel%3A+%27markup%27%2C%0A++++++position%3A+%7B+line%3A+2%2C+column%3A+20+%7D%2C%0A++++++config%3A+%7B%0A++++++++markup%3A+%7B%0A++++++++++language%3A+%22html%22%2C%0A++++++++++content%3A+%27%3Ch1%3EHello%2C%5Cn++%3Cspan+id%3D%22title%22%3EWorld%3C%2Fspan%3E%21%5Cn%3C%2Fh1%3E%27%2C%0A++++++++%7D%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++++%7B%0A++++++%2F%2F+highlight-next-line%0A++++++btnText%3A+%27Next%27%2C%0A++++++panel%3A+%27style%27%2C%0A++++++position%3A+%7B+line%3A+2%2C+column%3A+10+%7D%2C%0A++++++config%3A+%7B%0A++++++++style%3A+%7B%0A++++++++++language%3A+%27css%27%2C%0A++++++++++content%3A+%27%23title+%7B%5Cn++color%3A+blue%3B%5Cn%7D%27%2C%0A++++++++%7D%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++++%2F%2F+highlight-start%0A++++%7B%0A++++++btnText%3A+%27Next%27%2C%0A++++++panel%3A+%27script%27%2C%0A++++++position%3A+%7B+line%3A+2%2C+column%3A+19+%7D%2C%0A++++++config%3A+%7B%0A++++++++script%3A+%7B%0A++++++++++language%3A+%27javascript%27%2C%0A++++++++++content%3A%0A++++++++++++%27%2F%2F+change+this+value+to+%22LiveCodes%22%5Cnconst+newTitle+%3D+%22%3F%3F%3F%22%3B%5Cndocument.querySelector%28%22%23title%22%29.textContent+%3D+newTitle%3B%5Cnconsole.log%28newTitle%29%3B%27%2C%0A++++++++%7D%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++++%7B%0A++++++btnText%3A+%27Final+Result%27%2C%0A++++++panel%3A+%27console%27%2C%0A++++%7D%2C%0A++++%7B%0A++++++btnText%3A+%27Restart+Tutorial%27%2C%0A++++++panel%3A+%27result%27%2C%0A++++%7D%2C%0A++++%2F%2F+highlight-end%0A++%5D%3B%0A%0A++const+options+%3D+%7B%0A++++appUrl%3A+%22https%3A%2F%2Fv25.livecodes.io%22%2C%0A++++config%3A+%7B%0A++++++markup%3A+%7B%0A++++++++language%3A+%22html%22%2C%0A++++++++content%3A+%22Welcome+to+the+tutorial%21%22%2C%0A++++++%7D%2C%0A++++%7D%2C%0A++%7D%3B%0A%0A++createPlayground%28%22%23container%22%2C+options%29.then%28%28playground%29+%3D%3E+%7B%0A++++let+step+%3D+0%3B%0A++++const+nextBtn+%3D+document.querySelector%28%22%23next%22%29%3B%0A++++nextBtn.addEventListener%28%22click%22%2C+async+%28%29+%3D%3E+%7B%0A++++++const+lastStep+%3D+steps.length+-+1%3B%0A++++++const+currentStep+%3D+steps%5Bstep%5D%3B%0A++++++const+prevConfig+%3D+step+%3D%3D%3D+0+%3F+options.config+%3A+await+playground.getConfig%28%29%3B%0A++++++await+playground.setConfig%28%7B+...prevConfig%2C+...currentStep.config+%7D%29%3B%0A++++++await+playground.show%28currentStep.panel%2C+currentStep.position%29%3B%0A++++++%2F%2F+highlight-next-line%0A++++++nextBtn.textContent+%3D+currentStep.btnText%3B%0A++++++step+%3D+step+%3C+lastStep+%3F+step+%2B+1+%3A+0%3B%0A++++%7D%29%3B%0A++%7D%29%3B%0A%3C%2Fscript%3E%0A&css=body+%7B+margin%3A+50px+2em%3B+%7D)</small>


## Assignments

So, we are progressing very well!

Now we want our student to complete a task. We then need to validate if the task was completed successfully. This can be achieved by running [automated tests](https://livecodes.io/docs/features/tests).

LiveCodes supports running automated tests using Jest and Testing Library. You may test the page DOM or test the [code exported from the script editor](https://livecodes.io/docs/features/tests/#importing-code).

Let's write some tests for our tutorial.

```js
import { screen } from "@testing-library/dom";
import "@testing-library/jest-dom";

test("Should display title", async () => {
  expect(screen.getByText("Hello", { exact: false })).toHaveTextContent(
    "Hello, LiveCodes!"
  );
});

test("Title should be blue", async () => {
  const style = window.getComputedStyle(document.querySelector("#title"));
  expect(style.color).toBe("rgb(0, 0, 255)");
});
```

We test that the student was indeed able to complete the task (by changing the value of the variable `newTitle` to become `"LiveCodes"`). Also we are testing the color of the title.

These tests can be added to [`tests`](https://livecodes.io/docs/configuration/configuration-object#tests) property of the [`config` object](https://livecodes.io/docs/configuration/configuration-object).

We can then run the tests using the SDK method [`runTests`](https://livecodes.io/docs/sdk/js-ts#runtests). This method returns a promise that resolves to the test results, which we can then show to our student.

## Final Result

Check the [final result](https://livecodes.io/?mode=result&x=code/N4IgLglmA2CmIC4QFUB2kawCYAIAKATgPYBWsAxmCADQhawDO5BEADpEaoiDeAIYBzBogDaAXVp9KEAG6wAolihEC3ALZ8CAawCurXhu17EoaH1QCdg+EgAWYNdF7lOYWOm4AeAIRYilAE9WWBx7RwA+AB0CSNRPMOgcMwsAXkiQd3SomNQcHHjYPixs2Ly8zzVYMD4ccltNBiq0lAAVADEAWgAOdJwAehLcsoqqmvswVg7YAEcdWWaADQ7kAEEOgGEiNVY+SAAjOF6XdHcwZoBJeRTsAVhegejS4crqnFQ+SuaZCFgAd1YVGAjq5Ts1fhAsGBbCl6N9yLAOuDIbZqDgIKgoBA+NAOkxsbAUgBGAB0AAZ7oMyvkMHBwgAZWSwTb0Bg4Fo6MAqLGJAAisDURE8fRpsEpwwYYACtMeQypeyIWACOGAMqpVKUDFYZgCCBwADM4AAPADcqrVeQNsENHSUBAoHFQupc0B0alQppy5otrl1pOJAE5+TgGOYGLjYCw9R6nuaSDoJRA9QEOsc3OgnacI9HZWrDAJ0bqAKyk1iGnAAJn52fNAF8zVTbITlfW1W5DWAOtiIAJHbVMwRq169a5cRAAF6wXUkwtVltU50qXUEAR7AAUAHZy6jN9vywBKQdUuuetUAYlTfHREebJ-Ntlg3fsuoAbKSZLZD7nNPne3wOURPzyY8YzKc9XGIaBWRVW81WgCAJVxSU4F1VBOFgQCyg1LU+B1fUjQwvIBD4Vgp1nGCqTjBMkxTEF0z7E4BznMpLWtX4CGI3U2OIgicA0ViIShKdSVJABSHi8wLHBCTIkCqR2LAlAsX0MOAnM8jA9AINZPYOU5XJoNksokUEqSZzUHj70fMBdUrcymLyPYpC0ARiB0VAsBo6BFxwZc13LAAWABmVEAuCisgoPeycHlAh6AIKdS2DIg4NwFzYACHiYrijp2KUeMbNLHjyB0AgGG8gF0TcRjyLKBd4pwLBNC0BgzDcNKMqirDtV1FieMoyBqNTU4MwYniux7DooH5BgRqqlSmNPO0GB0aAwBvQy8gk3tpLsmq8jbDtxt7eFRqY1TzUWxgVrAYllvIeEGCgqK6t1NL3HmmrLuW1biT1S9EgMtTauS7y7SwD6QKFCUpVFM0hXvIoxU8eVFTFcolBkNEsGaC8rxiEBwiFDG0fyDGsZx8DkoYLImOR3TOHJ9JUCtIECYAZWqAg1vZTkWGxIUdLAPSSfKQW9MZ8BGFZ8J1nvcgtBwABZJU2eSjkIE4AX6dQEmidkXWyYhZoluurI9ZkXWmBYdgcElYJmgFLAVruAmmIgbZAWVWo7V2WA8G1FyiDc3Aa31Yg1BwABycZWBmvo+jc1hnOJFw1D6OC5BcFkAAE-RJctI+zJjjglYM3FjnAUhwEQosBr0yj2MBUBaFndUjgA5FnI+oKK8h2ZnoDbwxdFYbve5wAEGExThdWAJIrxs1FnVdXty1JHAax7vbgdQPVu1n8fNqavQD+381kksaw24SMez7VIa6Mj+JCXCAAJWBoC8nunk8TVzAlkUWQADqKhoBYChv3cI3hIixHhi-W+G01Sb3Hsg7eqDEF13rtFJuLd2xt07u2BBQM5LmA-m3aGcAiFYInkQKeDpZ7z2ZovWoas3RCQ3lvRBeRjh7wEKfLhZQKGTnWsQr0F8rC3DbuQR6VDqE7zTNZKOp4RTNieC9aKLp0IwNQDWWR1D0GiKApw0RBj66YPro3Zurco4ELAHor0-cyFRytmwOxxjqGT2nr2OecEmEViXqw7a-oOHjx4fvERcjgzMFcfwwxsFzCX0kVHEgfAZB8Bcewex1CH7WUPmqSO8daj1AsCEKE8EcBpM0bbIgOB0gMjkMyRg6RtElzWszX4LQoBwErrUkAAB+AZ6RTSxD8MVSo6BiSzAjAENmH97QqFXOkZRXSXZ7mJAdTYJx0A9PaZ0zAwzUAl2SrAYkXkBCrl2Ssg8WSvSmKwXc2s7ivTmK9JY3BijI5tHRNiHAAAlK6q0bkkIHlIzgZVKFPMebXcebzrGR3+RKTQ3N-x82gECsojjB5RxNoCyFSC8V5DEEXGqrScBEHYBrVArIq4vKpMRVgyACBYujkLWOCB44yEJH6DOFAFSMGJBrPo6KvJFHRHwqOhRbgEHRWE8VtKvxGBIhEuR4ir5RxvgSr0OS25AI-qnUpNSoSlJRdybwyMCDhA5ki6KSpyBwXlmK2295ora2ih-IgvxiToqMePNwEoZrKuoaqpJkcUlpIyW4vJ2q8l5Eju7AEXMvZW1gO4DeYcti9Ozn6yAFgOhwT2OxAgAQ+h+DUEM7R8bPbpCzVLMVeaIAFs0MWsgCFS3ltiNo7NiyQBs1sEHMBDV4LYSVIAmgOB0kBEOTgVce5K7hFUUMK0wRKCrmTe4YktwwAACEAjvO7e-T+RB0iojnlaKQii-qQRCDWPcazOSv1SbAd5myFGLJAukA9X8cD1KZHyhg0CQDaLyJFWIN6Dmdqlt2vZ3SGB9pWrgPYIQDg6BdqiCdU6Z1zoXdwsFa0hE9PBO5D1G6qibG2BybAHMYarlGa6U4kyUNFtmXASgCylmjtvQcvIS77SrqQicuqd6iBbtgN23yq5SSokkxWQshY9zpBAzo65mqjwqZCTVGsxLDLMEKG4f2OFA7B1XJHDS1Q8bdzJRSsFd77yoFXKuYdhn3KzpSPO+VeQ4B4fLj00kRVcNvBZlupuPTaPjJulMpjczWMEGM6eZmhDIrb1JdmhgQXchV1C-RiLMyou81i9myOiXEHJalgi66IX-B0Ymdl5j8yYsmZxXYorqAorxe3U3YkRQsDyDkOgBkEp3ARmM3aiA8sLPofINOlzbnQn+bMBKDmsBWA9IG7HU57gBBQhwB0KSPEcNUrWsVAgdp0CLeW1XVbDARCraJbNg7E87QyE2bwlb3mUjvZwOvPplmHQMBTpwF7uo+C-EvGtRzrl3IkbAM97sM6tNxOB6DieAcIdYFuqRgHsO57Ehx6wR7MOBCohxynEqJ2wBnf+7vbsG9mvUMR1AZHBnUe3T7b8VcR2ycU8xUvUnpwue0K8c18ebW0vrJZi+04PSOd8-LsSWF7Y9tlyW695XngkjpPJ9577q2cAAGopI4GUlFMDMooopdF11nrpx+tpiG5HEbY20MMEnZNzDrnA1emzWVn6GzaJrSrpHWWFAtCOoCEHAgSUXQOmJ4XX1pWAU3TtekhgNviR2gFHIYzd0HoMEK4rr3CeU7zZT-BG66eiCZ8jn9CAaKhd31JXPRrrJQ5V3p2DlHQdIcEDci3f1cPx6JmnU326WxROrka9NnyhfEVgHjJXD7Vf-qFdne51s8fvo3V91s-3UcgHEAsOOqlvwIyojAEW8dRF0TeFj3ffa6-rpF+T6nrrxnq+18V3fiU3vE-F9T+Xyv2ejAuetO9yOAH8jQHu9cBeG+Yu7YEu2yAemwx29oh+DAx+BAqIx+n8DUaE1+H+ts9+P0Sej0z+CkWeOg90QBeeeS0BD+xBJeEoae-IFeomi+Ne1Bd850tyIBR4PB+QfQEaSMfQKMAQgw8MDg0A4QIAm8IAQiJgIAwaNgIA0iwgtAOS3A0htAEa8hih3AYa6S0S7AzgfuGhMhQisGKaYAwgCA4gWhhhVhogEgyhj0hAjAVQGhtAeO-gQBKg1hthyh8YnIagsyQsYq1hwAMhlaXM4RMhdsjAJgsRUs4RChCSEiShcR2hahJhSAmhIAcgpUlK3AXK0hQAA) after adding tests, styles and some final touches. See it in action on [LiveCodes blog](https://blog.livecodes.io/lets-make-an-interactive-coding-tutorial#final-result).

<figure>
  <img alt="Final result for tutorial demo" src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ud2mflgiak6g24gpbrk7.png">
  <figcaption>A screenshot for the final result of the tutorial demo</figcaption>
</figure>

## Full Code

The full code in vanilla JavaScript and also in React TSX can be found [here](https://blog.livecodes.io/lets-make-an-interactive-coding-tutorial/#full-code).

## Conclusion

That was an interesting project to build!

Using LiveCodes SDK, we have built a very basic tutorial where the student can learn how to add HTML elements, style them with CSS and change the text content with JavaScript. We were even able to check if the assignment was completed successfully.

During our work, we had an idea about the LiveCodes SDK and how to use it. Please refer to the [SDK documentations](https://livecodes.io/docs/sdk) for more details.

## What's Next?

You can now make your tutorials a lot more interactive and fun with LiveCodes. These tutorials can use any of the supported [languages and frameworks](https://livecodes.io/docs/languages). Tutorials may also cover your own libraries (with full editor [IntelliSense](https://livecodes.io/docs/features/intellisense)).

You may want to create full courses and even sell them. LiveCodes is [MIT-licensed](https://livecodes.io/docs/license).

Your imagination is the limit!

## Tell Us What You Think

Please [let us know](https://github.com/live-codes/livecodes/discussions) what you think. If you have a case that the SDK does not cover, [tell us about it](https://github.com/live-codes/livecodes/issues).

If you do use LiveCodes for your tutorials/courses, we would [love to know](https://livecodes.io/docs/contact) about them (we may even link to them!). And if you find LiveCodes to be useful, please [give us a star on GitHub](https://github.com/live-codes/livecodes) and please consider becoming a [sponsor](https://livecodes.io/docs/sponsor).

What will you learn/build/teach today?