Skip to content
Article posted on

How to Use Sandpack for Code Demos
A guide to building fully customisable code demos in React

Sandpack was released by CodeSandbox earlier this week, a package that takes code demos to the next level, supporting just about every JavaScript framework. In this article I'll be talking about the Sandpack React component, and how to get it customised & running.
Table of contents

Sandpack was released by CodeSandbox earlier this week, a package that takes code demos to the next level, supporting just about every JavaScript framework. In this article I'll be talking about the Sandpack React component, and how to get it customised & running.

What is Sandpack?

Sandpack, and in particular, sandpack-react, is a package that allows you to create interactive code demos that update as you type. Sandpack allows you to:
  • Install NPM dependencies
  • Compile code without an API
  • Display live previews
  • Build a custom layout using composable components
  • Create demos for all major JavaScript frameworks
My Sandpack example
Using the react template
export default function App() {
  return <h1>Hello World</h1>
}
DevTools

How does it work

Sandpack works by bundling your code in-browser, without a back end. The preview window is a an iframe, embedding a special page that bundles your code, and then displays it:

example.com
My Code Editor
It can compile React
App.js
export default function App () {
  return (
    <div>Welcome to my App!</div>          
  )
}

sandpack.example.com
Welcome to my App!
It can compile Svelte too

This page makes use of service workers to parallelize the bundling process, and to prevent your UI slowing down during as it compiles. Using an iframe prevents some CORS security issues (we wouldn't want any baddies nabbing your users' cookies!), but also it just makes it easier to get up and running—service workers can be finicky to set up.

How do we use it

Amazingly, all you need to do to get this running is import a single component, (you can optionally import the default styles too):

npm i @codesandbox/sandpack-react
import { Sandpack } from '@codesandbox/sandpack-react'
import '@codesandbox/sandpack-react/dist/index.css'
	
export default function App () {
  return <Sandpack template="react" />
}

Pretty nifty. Try changing the template attribute from "react" to "svelte" or "vue" and see it update live! All templates options are listed in the Sandpack API under SandpackPredefinedTemplate.

File format

To add files we need to use the customSetup attribute and provide a files object. Files can be written in two formats, string or object, with the key defining the path to the file:

const files = {
  '/App.js': `export default...`,
  
  '/Button.js': {
    code: `export default...`,
    active: true, // Default visible file on load? default `false`
    hidden: false // File visible in tab list? default `true`
  }
}

The path must always begin with a forward slash (/). We'll be exclusively using the object format in this article to prevent ambiguity.

Adding files

Something to remember while using template is that the main file must be called App, otherwise the default demo will show. In this example we're adding two basic files, App.js and Hello.js:

import { Sandpack } from '@codesandbox/sandpack-react'
import '@codesandbox/sandpack-react/dist/index.css'
import files from './files.js'
	
export default function () {
  return (
    <Sandpack
      template="react"
      customSetup={{ files }}
    />
  )
}

If you open files.js and rename /App.js, you'll see the default demo appearing instead.

Basic styling

We can add some basic styling to SandPack using CodeSandbox's interactive theme builder. Configure your style, then pass the resulting object to the theme attribute (this is the Dracula theme):

import { Sandpack } from '@codesandbox/sandpack-react'
import '@codesandbox/sandpack-react/dist/index.css'
import files from './files.js'
import theme from './theme.js'
	
export default function App () {
  return (
    <Sandpack
      template="svelte"
      customSetup={{ files }}
      theme={theme}
    />
  )
}
There's more info regarding this on the Sandpack docs under Custom UI.

CSS cheatsheet

If you don't plan on completely customising your code demos, you can even make use of a quick CSS cheatsheet I've written:

CSS cheatsheet
/* ===== SandpackProvider ============================= */
/* Overall wrapper */
div.sp-wrapper {}

/* ===== SandpackCodeEditor =========================== */
/* Tab bar */
div.sp-tabs {}

/* Individual tab */
button.sp-tab-button {}

/* Active tab */
button.sp-tab-button[data-active=true] {}

/* Wrapper for components */
div.sp-stack {}

/* Code editor wrapper */
div.sp-code-editor {}

/* Background for code editor */
div.cm-editor {}

/* Code editor content */
div.sp-code-editor div.cm-content {}

/* Line of code */
div.sp-code-editor div.cm-line {}

/* Active line of code */
div.sp-code-editor div.cm-line.cm-activeLine {}

/* Gutter background */
div.sp-code-editor div.cm-gutters {}

/* Line numbers */
div.sp-code-editor div.cm-gutter.cm-lineNumbers {}

/* ===== SandpackPreview ============================== */
/* Preview container */
div.sp-preview-container {}

/* Preview iframe */
iframe.sp-preview-iframe {}

/* Wrapper for buttons in preview container */
div.sp-preview-actions {}

/* Wrapper for loading icon in preview container */
div.sp-loading {}

/* Open in Sandbox button, appears while loading */
button.sp-icon-standalone {}

/* Error box */
div.sp-error {}

/* Error text */
div.sp-error-message {
  white-space: pre-wrap;
}

I'd only advise using this for little changes, otherwise you may see a FOUC (flash of unstyled content) if your customisations conflict with the default styles.

It's that easy

There you go, with just 5 minutes of work you can set up a live code demo with compiling, a live preview, code highlighting, and even a custom theme! Sandpack is magic.

Advanced setups

What we've learnt so far works well, but using a template limits what we can do with Sandpack (plus your main file has to be named App!). Let's have a look at some more complex setups using customSetup.

Options for customSetup

A more complex setup requires us to initialise the component with dependencies, an environment, and an entry file. The React template we used earlier does all this under the hood. This is the customSetup for a React demo:

customSetup={{
  entry: '/index.js',
  environment: 'create-react-app',

  dependencies: {
    react: '^17.0.0',
    'react-dom': '^17.0.0',
    'react-scripts': '^4.0.0',
  },

  files: {
    // ...
  }
}}

To get this working we have to set up React in the entry file (we've called it /index.js above), and give it an HTML to work with. To do this, we can refer to the simplest Sandpack example from earlier, which is handily providing us those files:

import { Sandpack } from '@codesandbox/sandpack-react'
import '@codesandbox/sandpack-react/dist/index.css'
	
export default function App () {
  return <Sandpack template="react" />
}

If we copy index.js, styles.css, index.html, into a setupFiles object, we can use them ourselves (check inside of setupFiles.js):

import { Sandpack } from '@codesandbox/sandpack-react'
import '@codesandbox/sandpack-react/dist/index.css'
import setupFiles from './setupFiles.js'
import files from './files.js'
	
export default function () {
  return (
    <Sandpack
      customSetup={{
        entry: '/index.js',
        environment: 'create-react-app',
        	
        dependencies: {
          react: '^17.0.0',
          'react-dom': '^17.0.0',
          'react-scripts': '^4.0.0',
        },
        	
        files: {
          ...files,
          ...setupFiles
        }
      }}
    />
  )
}

Try editing styles.css in the preview window and changing the colour of h1. You can also open setupFiles.js above, and change hidden to true (near the bottom) to see the irrelevant setup tabs disappear.

Make it reusable

To make setting up easier, we can create a basic factory function that will build customSetup for us, and allow us to throw in a couple of options. Don't worry, I've done all the work for you!

Each setup function I've created initialises the framework, and then allows you to define extra dependencies, your files, and the main file (by file name). Here's an example:

customSetup={setupReact({
  dependencies: {
    'date-fns': '^2.27.0'
  },
  files: {
    '/Main.js': // ...
  },
  main: 'Main'
})}

I've made versions for React, Vue, and Svelte. Here they are with working demos:

React

Show React factory and demo
setupReact.js
const indexJs = ({ main }) => `
import React, { StrictMode } from 'react'
import ReactDOM from 'react-dom'
import ${main} from './${main}.js'

const rootElement = document.getElementById('app')
ReactDOM.render(
  <StrictMode>
    <${main} />
  </StrictMode>,
  rootElement
)
`

const indexHtml = ({ main }) => `
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>${main}</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
`

const setupReact = (options) => ({
  entry: '/index.js',
  environment: 'create-react-app',

  dependencies: {
    react: '^17.0.0',
    'react-dom': '^17.0.0',
    'react-scripts': '^4.0.0',
    ...options.dependencies
  },

  files: {
    '/index.js': {
      hidden: true,
      code: indexJs(options)
    },

    '/public/index.html': {
      hidden: true,
      code: indexHtml(options)
    },

    ...options.files
  }
})

export default setupReact
import { Sandpack } from '@codesandbox/sandpack-react'
import '@codesandbox/sandpack-react/dist/index.css'
import setupReact from './setupReact.js'
import files from './files.js'
	
export default function () {
  return (
    <Sandpack
      customSetup={setupReact({
        dependencies: {
          'date-fns': '^2.27.0'
        },
        files: files,
        main: 'Main',
      })}
    />
  )
}

Vue

Show Vue factory and demo
setupVue.js
const indexHtml = ({ main }) => `
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <title>${main}</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
`

const indexJs = ({ main }) => `import { createApp } from 'vue'
import ${main} from './${main}.vue'

createApp(${main}).mount('#app')
`

const createVue = (options) => ({
  entry: '/index.js',
  environment: 'vue-cli',

  dependencies: {
    'core-js': '^3.6.5',
    vue: '^3.0.0-0',
    '@vue/cli-plugin-babel': '4.5.0',
    ...options.dependencies
  },

  files: {
    '/index.js': {
       code: indexJs(options),
       hidden: true
    },

    '/index.html': {
       code: indexHtml(options),
       hidden: true
    },

  ...options.files
  }
})

export default createVue
import { Sandpack } from '@codesandbox/sandpack-react'
import '@codesandbox/sandpack-react/dist/index.css'
import setupVue from './setupVue.js'
import files from './files.js'
	
export default function () {
  return (
    <Sandpack
      customSetup={setupVue({
        dependencies: {
          'date-fns': '^2.27.0'
        },
        files: files,
        main: 'Main'
      })}
    />
  )
}

Svelte

Show Svelte factory and demo
setupSvelte.js
const indexJs = ({ main }) => `
import ${main} from './${main}.svelte'

const app = new ${main}({
  target: document.body
})

export default app
`

const indexHtml = ({ main }) => `
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf8" />
    <meta name="viewport" content="width=device-width" />
    <title>${main}</title>
    <link rel="stylesheet" href="public/bundle.css" />
  </head>
  <body>
    <script src="bundle.js"></script>
  </body>
</html>
`

const createSvelte = (options) => ({
  entry: '/index.js',
  environment: 'svelte',

  dependencies: {
    svelte: '^3.0.0',
    ...options.dependencies
  },

  files: {
    '/index.js': {
      code: indexJs(options),
      hidden: true
    },

    '/public/index.html': {
      code: indexHtml(options),
      hidden: true
    },

    ...options.files
  }
})

export default createSvelte
import { Sandpack } from '@codesandbox/sandpack-react'
import '@codesandbox/sandpack-react/dist/index.css'
import setupSvelte from './setupSvelte.js'
import files from './files.js'
	
export default function () {
  return (
    <Sandpack
      customSetup={setupSvelte({
        dependencies: {
          'date-fns': '^2.27.0'
        },
        files: files,
        main: 'Main'
      })}
    />
  )
}

Build your own factory

These factories were based on the Sandpack templates, and there are a few more examples of setups over there (e.g. vanilla JS, React typescript). Take a look there if you'd like to see how to set them up.

You can even take these factories to the next level, like with my components, and wrap them in a component that applies the current website theme (light/dark, sans/serif) to the previews.

Customising the interface

We can go further to customise Sandpack, making use of its components and hooks. In this section, we'll be using a default template for simplicity.

Modular components

Sandpack also allows us to import smaller components that make up the default main component. First, we wrap everything inside SandpackProvider (and also SandpackThemeProvider if we're using the default theming), then we structure it as we like:

import {
  SandpackProvider,
  SandpackThemeProvider,
  FileTabs,
  SandpackCodeEditor,
  SandpackPreview,
  UnstyledOpenInCodeSandboxButton
} from '@codesandbox/sandpack-react'
import '@codesandbox/sandpack-react/dist/index.css'
	
export default function App () {
  return (
    <SandpackProvider template="react">
      <SandpackThemeProvider>
	
        <FileTabs />
        <div style={{
          display: 'grid',
          gridTemplateColumns: '50% 50%'
        }}>
          <SandpackCodeEditor showTabs={false}/>
          <SandpackPreview />
        </div>
        <UnstyledOpenInCodeSandboxButton>
          Open in CodeSandbox
        </UnstyledOpenInCodeSandboxButton>
	
      </SandpackThemeProvider>
    </SandpackProvider>
  )
}
Here we've split the code editor & preview window into two columns, placed the file tabs above, and added a custom Open in CodeSandbox link. Try moving SandpackPreview above the tabs instead, and remove the grid styles.

Hooks

A number of React hooks are also available to use which allow you to create your own components, for example you can make a custom set of tabs like this:

const { sandpack } = useSandpack()
const { openPaths, activePath, setActiveFile } = sandpack

return (
  <div>
    {openPaths.map(name => (
      <button
        key={name}
        onClick={() => setActiveFile(name)}
        data-active={name === activePath}
      >
        {name}
      </button>
    ))}
  </div>
)

You could even make a custom code editor using whichever package you'd like. In this example I'm making use of a simple textarea:

const { code, updateCode } = useActiveCode()

return (
  <textarea onChange={e => updateCode(e.target.value)} value={code} />
)

And here's a working example using the two components above:

import {
  SandpackPreview,
  SandpackProvider,
} from '@codesandbox/sandpack-react'
import CustomTabs from './CustomTabs.js'
import CustomEditor from './CustomEditor.js'
// import './customStyles.css'
	
export default function App () {
  return (
    <div className="custom-sandpack">
      <SandpackProvider
        template="react"
      >
        <CustomTabs />
        <CustomEditor />
        <div className="preview">
          <SandpackPreview
            showRefreshButton={false}
            showOpenInCodeSandbox={false}
          />
        </div>
      </SandpackProvider>
    </div>
  )
}

Try uncommenting import './customStyles.css' for a little custom styling.

With a little more work

With a little more work you can get a very nice code demo up and running:

Host the bundler yourself

By default, the bundler page (in the iframe, running the service workers) is hosted on CodeSandbox's server, but you can actually host this static page yourself. To find it, make sure you've installed the package and then look for this directory in your project:

node_modules/@codesandbox/sandpack-client/sandpack

Upload this folder as the root of a different domain name, or as a subdomain, (to prevent CORS vulnerabilities) and then specify the address within SandpackProvider:

<SandpackProvider
  bundlerURL="sandpack.example.com"
  // ...
>

And there you go! Remember that any NPM dependencies you've specified will still be retrieved from the web.

Summary

Sandpack is a fantastic resource for building code demos, allowing for truly customisable components—but this is actually just the beginning. The bundler itself is an incredibly powerful tool that I've hardly even had time to mention. For more info, take a look at the Sandpack documentation. Give me a Tweet if this article has been helpful, or if you're using Sandpack to build anything interesting!