In this tutorial, we'll build a sophisticated code playground that allows users to write, run, and see the output of HTML, CSS, and JavaScript code in real-time. This is similar to platforms like CodePen or JSFiddle but built from scratch with advanced features.
Features
Three-panel editor (HTML, CSS, JavaScript)
Real-time preview
Syntax highlighting
Responsive layout
Code formatting/beautification
Console output
Error handling
Local storage for saving code
Export/import functionality
Download Zip File
Structure
code-playground/
│
├── index.html
├── styles.css
├── script.js
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Code Playground</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<span>🧪 Code Playground</span>
<div>
<label>
🌙
<input type="checkbox" id="darkToggle" />
Dark Mode
</label>
<input type="file" id="importFile" accept=".zip" />
</div>
</header>
<div class="tabs" id="tabBar"></div>
<div class="editor-container">
<div class="editor">
<label>HTML</label>
<textarea id="html"></textarea>
<label>CSS</label>
<textarea id="css"></textarea>
<label>JS</label>
<textarea id="js"></textarea>
</div>
<div class="preview">
<iframe id="preview" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
</div>
<div class="controls">
<input type="text" id="snippetName" placeholder="Snippet name" />
<button onclick="saveTab()">💾 Save</button>
<button onclick="newTab()">➕ New Tab</button>
<button onclick="deleteTab()">🗑️ Delete</button>
<button onclick="exportCode()">⬇ Export</button>
<button onclick="clearEditor()">🧹 Clear</button>
</div>
<script src="script.js"></script>
</body>
</html>
CSS Styling
:root {
--bg: #ffffff;
--text: #000000;
--panel: #f1f1f1;
--border: #ccc;
}
body.dark {
--bg: #1e1e1e;
--text: #ffffff;
--panel: #2c2c2c;
--border: #444;
}
body {
margin: 0;
font-family: sans-serif;
background: var(--bg);
color: var(--text);
height: 100vh;
display: flex;
flex-direction: column;
}
header {
background: var(--panel);
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
}
.editor-container {
display: flex;
flex: 1;
overflow: hidden;
}
.editor {
flex: 1;
padding: 10px;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border);
box-sizing: border-box;
}
textarea {
flex: 1;
margin: 5px 0;
font-family: monospace;
font-size: 14px;
padding: 10px;
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
resize: none;
}
.preview {
flex: 1;
}
iframe {
width: 100%;
height: 100%;
border: none;
background: white;
}
.controls, .tabs {
background: var(--panel);
padding: 8px;
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
border-top: 1px solid var(--border);
}
button, select, input {
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
padding: 5px 10px;
font-size: 14px;
}
.tab {
padding: 4px 8px;
cursor: pointer;
border: 1px solid var(--border);
}
.tab.active {
background: #4caf50;
color: white;
}
label {
font-weight: bold;
}
JavaScript Implementation
const htmlEl = document.getElementById("html");
const cssEl = document.getElementById("css");
const jsEl = document.getElementById("js");
const preview = document.getElementById("preview");
const snippetName = document.getElementById("snippetName");
const tabBar = document.getElementById("tabBar");
const darkToggle = document.getElementById("darkToggle");
const importFile = document.getElementById("importFile");
let currentTab = null;
function updatePreview() {
const doc = `
${htmlEl.value}
<style>${cssEl.value}</style>
<script>${jsEl.value}<\/script>
`;
const frameDoc = preview.contentDocument || preview.contentWindow.document;
frameDoc.open();
frameDoc.write(doc);
frameDoc.close();
}
htmlEl.addEventListener("input", updatePreview);
cssEl.addEventListener("input", updatePreview);
jsEl.addEventListener("input", updatePreview);
function saveTab() {
const name = snippetName.value.trim();
if (!name) return alert("Name required.");
const data = {
html: htmlEl.value,
css: cssEl.value,
js: jsEl.value,
};
localStorage.setItem("tab_" + name, JSON.stringify(data));
currentTab = name;
renderTabs();
}
function loadTab(name) {
const data = JSON.parse(localStorage.getItem("tab_" + name));
if (data) {
htmlEl.value = data.html;
cssEl.value = data.css;
jsEl.value = data.js;
snippetName.value = name;
currentTab = name;
updatePreview();
renderTabs();
}
}
function newTab() {
snippetName.value = "";
htmlEl.value = cssEl.value = jsEl.value = "";
currentTab = null;
renderTabs();
updatePreview();
}
function deleteTab() {
if (currentTab) {
localStorage.removeItem("tab_" + currentTab);
newTab();
renderTabs();
}
}
function renderTabs() {
tabBar.innerHTML = "";
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith("tab_")) {
const name = key.replace("tab_", "");
const div = document.createElement("div");
div.className = "tab" + (name === currentTab ? " active" : "");
div.textContent = name;
div.onclick = () => loadTab(name);
tabBar.appendChild(div);
}
}
}
async function exportCode() {
const zip = new JSZip();
zip.file("index.html", htmlEl.value);
zip.file("style.css", cssEl.value);
zip.file("script.js", jsEl.value);
const blob = await zip.generateAsync({ type: "blob" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = (snippetName.value || "code") + ".zip";
a.click();
URL.revokeObjectURL(url);
}
importFile.addEventListener("change", async function () {
const file = this.files[0];
if (!file) return;
const zip = await JSZip.loadAsync(file);
const html = await zip.file("index.html")?.async("string");
const css = await zip.file("style.css")?.async("string");
const js = await zip.file("script.js")?.async("string");
htmlEl.value = html || "";
cssEl.value = css || "";
jsEl.value = js || "";
updatePreview();
alert("Code imported!");
});
function clearEditor() {
htmlEl.value = cssEl.value = jsEl.value = "";
updatePreview();
}
// Dark mode toggle
darkToggle.addEventListener("change", () => {
const dark = darkToggle.checked;
document.body.classList.toggle("dark", dark);
localStorage.setItem("darkMode", dark ? "1" : "0");
});
// Init
window.addEventListener("load", () => {
if (localStorage.getItem("darkMode") === "1") {
document.body.classList.add("dark");
darkToggle.checked = true;
}
renderTabs();
updatePreview();
});
Conclusion
This advanced code playground provides a professional coding environment with all the essential features developers need. You can further enhance it by adding:
Multiple tabs/sessions
Collaboration features
Code autocompletion
Custom themes
Keyboard shortcuts
GitHub integration
The complete implementation gives you a solid foundation to build upon and customize for your specific needs.
View Demo