AJ.
TypeScript•TypeScriptReact

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>;