AJ.
TypeScriptTypeScriptReact

Sandpack Templates

This snippet gives you more control over Sandpack, letting you compose editors, previews, tests, and files however you want without being boxed into the default setup.

sandpack-utils.ts

import type {
  CodeEditorProps,
  PreviewProps,
  SandpackProviderProps,
  SandpackTests,
} from "@codesandbox/sandpack-react";
import {
  Children,
  type ComponentProps,
  isValidElement,
  type ReactNode,
} from "react";

export interface SandpackProps extends SandpackProviderProps {
  editorProps?: CodeEditorProps;
  previewProps?: PreviewProps;
  testProps?: ComponentProps<typeof SandpackTests>;
}

export const COMMON_FILES = {
  "tsconfig.json": {
    code: JSON.stringify({
      include: ["./**/*"],
      compilerOptions: {
        strict: true,
        esModuleInterop: true,
        lib: ["dom", "es2015"],
        jsx: "react-jsx",
      },
    }),
    hidden: true,
    readOnly: true,
  },
};

export function getFiles(children: ReactNode) {
  const fileEntries = Children.toArray(children)
    .map((child) => {
      if (!isValidElement<{ name: string; children: string }>(child)) {
        return null;
      }

      return [child.props.name, child.props.children];
    })
    .filter((child): child is [string, string] => {
      return child !== null;
    });

  return Object.fromEntries(fileEntries);
}

export const TestTemplateSandpack: (
  props: SandpackProps,
) => SandpackProviderProps = ({ files: _files, children, ...props }) => {
  const files = getFiles(children);

  return {
    files: {
      "package.json": {
        code: JSON.stringify({
          dependencies: {},
          devDependencies: { typescript: "^4.0.0" },
          main: Object.keys(files)[0],
        }),
        hidden: true,
        readOnly: true,
      },
      ...COMMON_FILES,
      ...files,
    },
    customSetup: {
      environment: "parcel",
    },
    ...props,
  };
};

export const ReactTemplateSandpack: (
  props: SandpackProps,
) => SandpackProviderProps = ({ files: _files, children, ...props }) => {
  const files = getFiles(children);

  return {
    files: {
      "package.json": {
        code: JSON.stringify({
          dependencies: {
            react: "^19.0.0",
            "react-dom": "^19.0.0",
            "react-scripts": "^4.0.0",
          },
          devDependencies: {
            "@types/react": "^19.0.0",
            "@types/react-dom": "^19.0.0",
            typescript: "^4.0.0",
          },
          main: "/index.tsx",
        }),
        hidden: true,
        readOnly: true,
      },
      ...COMMON_FILES,
      "/index.tsx": {
        code: `import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";

import App from "./App";

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);`,
        hidden: true,
        readOnly: true,
      },
      "/public/index.html": {
        code: `<!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <div id="root"></div>
    </body>
  </html>`,
        hidden: true,
        readOnly: true,
      },
      ...files,
    },
    customSetup: {
      environment: "create-react-app-typescript",
    },
    ...props,
  };
};

export type GetSandpackProps = Omit<SandpackProps, "template"> & {
  template: "react" | "test";
};

export function getSandpackProps({ template, ...props }: GetSandpackProps) {
  switch (template) {
    case "test":
      return TestTemplateSandpack(props);
    default:
      return ReactTemplateSandpack(props);
  }
}

sandpack.tsx

import { getSandpackProps } from './sandpack-utils'

<SandpackProvider
  {...getSandpackProps({
    template: 'react' | 'test',
    // customize based on your needs
    style: {
      "--sp-layout-height": "420px",
    } as CSSProperties,
    options: {
      classes: {
        "sp-tab-container": "outline-none!",
        "sp-code-editor": "[&_.cm-gutterElement]:text-xs",
      },
    },
    ...props,
  })}
>
  <SandpackLayout className="border-none! rounded-none!">
    <SandpackCodeEditor
      showLineNumbers
      showInlineErrors
      wrapContent
      {...editorProps}
    />
    {template === "test" ? (
      <SandpackTests {...testProps} />
    ) : (
      <SandpackPreview {...previewProps} />
    )}
  </SandpackLayout>
</SandpackProvider>