Skip to content

Instantly share code, notes, and snippets.

@Pierre-Thibault
Last active June 28, 2026 02:34
Show Gist options
  • Select an option

  • Save Pierre-Thibault/3f0a249e458d16ad50b90ded9a55db70 to your computer and use it in GitHub Desktop.

Select an option

Save Pierre-Thibault/3f0a249e458d16ad50b90ded9a55db70 to your computer and use it in GitHub Desktop.
CSS HTML laboratory
<!--
MIT License
Copyright (c) 2026 Pierre Thibault
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Playground</title>
<!-- App chrome — not editable -->
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body { font-family: system-ui, -apple-system, sans-serif; }
.app {
display: grid;
grid-template-columns: 380px 1fr;
grid-template-rows: auto 1fr;
height: 100vh;
}
.app-header {
grid-column: 1 / -1;
background: var(--primary, #6366f1);
color: #fff;
padding: 11px 20px;
display: flex;
align-items: center;
gap: 10px;
z-index: 10;
}
.app-header h1 { margin: 0; font-size: 1.1rem; font-weight: 700; letter-spacing: .02em; }
.app-header span { margin-left: auto; font-size: .78rem; opacity: .75; }
.editor-pane {
background: #0f172a;
display: flex;
flex-direction: column;
overflow: hidden;
border-right: 1px solid #1e293b;
}
.editor-label {
padding: 8px 14px 4px;
font-size: .68rem;
font-weight: 700;
letter-spacing: .1em;
text-transform: uppercase;
color: #475569;
font-family: monospace;
border-bottom: 1px solid #1e293b;
}
#css-editor {
flex: 1;
background: transparent;
color: #cbd5e1;
border: none;
outline: none;
resize: none;
font-family: 'Fira Code', 'Cascadia Code', 'JetBrains Mono', monospace;
font-size: .8rem;
line-height: 1.75;
padding: 10px 14px;
tab-size: 2;
}
.editor-status {
padding: 5px 14px;
font-size: .7rem;
color: #334155;
font-family: monospace;
border-top: 1px solid #1e293b;
}
.demo-pane {
overflow-y: auto;
padding: 28px 32px 48px;
background: var(--bg, #f1f5f9);
}
</style>
<!-- Live-editable styles -->
<style id="live-styles">
/* ── Variables ────────────────────────────────── */
:root {
--primary: #6366f1;
--secondary: #ec4899;
--success: #22c55e;
--danger: #ef4444;
--warning: #f59e0b;
--bg: #f1f5f9;
--surface: #ffffff;
--text: #1e293b;
--muted: #64748b;
--border: #cbd5e1;
--radius: 10px;
--shadow: 0 2px 8px rgba(0,0,0,.10);
}
/* ── Typography ───────────────────────────────── */
.demo-pane {
color: var(--text);
font-size: 16px;
line-height: 1.6;
}
h1 { font-size: 2rem; margin: 0 0 8px; }
h2 { font-size: 1.5rem; margin: 0 0 8px; }
h3 { font-size: 1.2rem; margin: 0 0 8px; }
p { margin: 0 0 10px; color: var(--text); }
a { color: var(--primary); }
small { color: var(--muted); }
strong { font-weight: 700; }
em { font-style: italic; }
blockquote {
border-left: 3px solid var(--primary);
margin: 0 0 10px;
padding: 8px 16px;
color: var(--muted);
font-style: italic;
}
/* ── Section wrapper ──────────────────────────── */
.section { margin-bottom: 40px; }
.section-title {
font-size: .68rem;
font-weight: 700;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--muted);
border-bottom: 1px solid var(--border);
padding-bottom: 6px;
margin: 0 0 18px;
}
/* ── Buttons ──────────────────────────────────── */
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 18px;
border-radius: var(--radius);
border: none;
font-size: .88rem;
font-weight: 600;
cursor: pointer;
font-family: inherit;
transition: opacity .15s, transform .1s;
}
.btn:hover { opacity: .85; transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn-primary { background: var(--primary); color: #fff; }
.btn-secondary { background: var(--secondary); color: #fff; }
.btn-success { background: var(--success); color: #fff; }
.btn-danger { background: var(--danger); color: #fff; }
.btn-outline {
background: transparent;
border: 2px solid var(--primary);
color: var(--primary);
}
.btn-ghost { background: transparent; color: var(--primary); }
.btn-sm { padding: 4px 12px; font-size: .78rem; }
.btn-lg { padding: 12px 28px; font-size: 1.05rem; }
.btn-group { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* ── Form ─────────────────────────────────────── */
.form-row { margin-bottom: 14px; }
label.field-label {
display: block;
font-size: .84rem;
font-weight: 600;
margin-bottom: 4px;
color: var(--text);
}
input[type="text"],
input[type="email"],
input[type="number"],
select {
width: 100%;
max-width: 300px;
padding: 8px 12px;
border: 1.5px solid var(--border);
border-radius: var(--radius);
font-size: .88rem;
font-family: inherit;
background: var(--surface);
color: var(--text);
outline: none;
transition: border-color .15s, box-shadow .15s;
}
input[type="text"]:focus,
input[type="email"]:focus,
input[type="number"]:focus,
select:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent);
}
.radio-group,
.check-group { display: flex; gap: 16px; flex-wrap: wrap; }
.radio-group label,
.check-group label {
display: flex;
align-items: center;
gap: 6px;
font-size: .88rem;
cursor: pointer;
}
/* ── Cards ────────────────────────────────────── */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
}
.card-header {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
font-weight: 700;
font-size: .9rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.card-body { padding: 16px; }
.card-footer {
padding: 10px 16px;
border-top: 1px solid var(--border);
background: var(--bg);
font-size: .82rem;
color: var(--muted);
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 14px;
}
/* ── Badges ───────────────────────────────────── */
.badge {
display: inline-flex;
align-items: center;
padding: 2px 10px;
border-radius: 999px;
font-size: .72rem;
font-weight: 700;
letter-spacing: .04em;
}
.badge-primary { background: color-mix(in srgb, var(--primary) 15%, transparent); color: var(--primary); }
.badge-success { background: color-mix(in srgb, var(--success) 15%, transparent); color: var(--success); }
.badge-danger { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
.badge-warning { background: color-mix(in srgb, var(--warning) 15%, transparent); color: var(--warning); }
.badge-muted { background: color-mix(in srgb, var(--muted) 15%, transparent); color: var(--muted); }
/* ── Avatars ──────────────────────────────────── */
.avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
font-weight: 700;
font-size: .85rem;
color: #fff;
flex-shrink: 0;
}
.avatar-sm { width: 28px; height: 28px; font-size: .68rem; }
.avatar-lg { width: 56px; height: 56px; font-size: 1.2rem; }
/* ── Alerts ───────────────────────────────────── */
.alert {
padding: 11px 14px;
border-radius: var(--radius);
border-left: 4px solid;
margin-bottom: 8px;
font-size: .88rem;
}
.alert-info { background: color-mix(in srgb, var(--primary) 10%, transparent); border-color: var(--primary); color: var(--primary); }
.alert-success { background: color-mix(in srgb, var(--success) 10%, transparent); border-color: var(--success); color: var(--success); }
.alert-warning { background: color-mix(in srgb, var(--warning) 10%, transparent); border-color: var(--warning); color: var(--warning); }
.alert-danger { background: color-mix(in srgb, var(--danger) 10%, transparent); border-color: var(--danger); color: var(--danger); }
/* ── Progress bars ────────────────────────────── */
.progress {
background: var(--border);
border-radius: 999px;
height: 8px;
overflow: hidden;
margin-bottom: 8px;
}
.progress-bar {
height: 100%;
border-radius: 999px;
background: var(--primary);
transition: width .4s ease;
}
/* ── Table ────────────────────────────────────── */
.table-wrap { border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
table { width: 100%; border-collapse: collapse; font-size: .88rem; }
th {
background: var(--bg);
text-align: left;
padding: 10px 14px;
font-size: .7rem;
font-weight: 700;
letter-spacing: .07em;
text-transform: uppercase;
color: var(--muted);
border-bottom: 2px solid var(--border);
}
td {
padding: 10px 14px;
border-bottom: 1px solid var(--border);
background: var(--surface);
color: var(--text);
}
tr:last-child td { border-bottom: none; }
tr:hover td { background: color-mix(in srgb, var(--primary) 4%, var(--surface)); }
/* ── Tooltips (pure CSS) ──────────────────────── */
[data-tip] { position: relative; cursor: default; }
[data-tip]::after {
content: attr(data-tip);
position: absolute;
bottom: calc(100% + 7px);
left: 50%;
transform: translateX(-50%);
background: #1e293b;
color: #fff;
font-size: .72rem;
padding: 4px 10px;
border-radius: 6px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity .15s;
z-index: 100;
}
[data-tip]:hover::after { opacity: 1; }
</style>
</head>
<body>
<div class="app">
<header class="app-header">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
</svg>
<h1>CSS Playground</h1>
<span>Edit the CSS on the left — changes apply instantly</span>
</header>
<!-- CSS editor -->
<aside class="editor-pane">
<div class="editor-label">styles.css</div>
<textarea id="css-editor" spellcheck="false" autocomplete="off"></textarea>
<div class="editor-status" id="editor-status"></div>
</aside>
<!-- Element showcase -->
<main class="demo-pane">
<section class="section">
<p class="section-title">Typography</p>
<h1>Heading H1 — page title</h1>
<h2>Heading H2 — section title</h2>
<h3>Heading H3 — subsection</h3>
<p>A regular paragraph with <strong>bold text</strong>, <em>italic text</em>, and <a href="#">a link</a>. The quick brown fox jumps over the lazy dog.</p>
<p><small>Secondary note or caption in a more subtle style.</small></p>
<blockquote>"Design is the art of solving problems with elegance."</blockquote>
</section>
<section class="section">
<p class="section-title">Buttons</p>
<div class="btn-group" style="margin-bottom:10px">
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-success">Success</button>
<button class="btn btn-danger">Danger</button>
</div>
<div class="btn-group">
<button class="btn btn-outline">Outline</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary btn-lg">Large</button>
<button class="btn btn-primary" style="opacity:.38;cursor:not-allowed" disabled>Disabled</button>
</div>
</section>
<section class="section">
<p class="section-title">Form</p>
<div class="form-row">
<label class="field-label" for="f-name">Full name</label>
<input type="text" id="f-name" placeholder="Jane Smith">
</div>
<div class="form-row">
<label class="field-label" for="f-email">Email address</label>
<input type="email" id="f-email" placeholder="jane@example.com">
</div>
<div class="form-row">
<label class="field-label" for="f-select">Category</label>
<select id="f-select">
<option>Design</option><option>Development</option><option>Marketing</option>
</select>
</div>
<div class="form-row">
<label class="field-label">Preference</label>
<div class="radio-group">
<label><input type="radio" name="pref" checked> Option A</label>
<label><input type="radio" name="pref"> Option B</label>
<label><input type="radio" name="pref"> Option C</label>
</div>
</div>
<div class="form-row">
<div class="check-group">
<label><input type="checkbox" checked> Notifications</label>
<label><input type="checkbox"> Newsletter</label>
<label><input type="checkbox"> Share data</label>
</div>
</div>
<div class="form-row">
<label class="field-label">
Intensity — <span id="range-val">50</span>%
</label>
<input type="range" id="f-range" value="50" min="0" max="100"
style="width:280px;accent-color:var(--primary)">
</div>
</section>
<section class="section">
<p class="section-title">Cards</p>
<div class="card-grid">
<div class="card">
<div class="card-header">Basic card</div>
<div class="card-body">
<p style="margin:0;font-size:.88rem;color:var(--muted)">Card content with some useful information for the user.</p>
</div>
<div class="card-footer">Updated 2 hours ago</div>
</div>
<div class="card">
<div class="card-header">
Statistics
<span class="badge badge-success">Online</span>
</div>
<div class="card-body">
<div style="font-size:2rem;font-weight:800;color:var(--primary)">1,284</div>
<div style="font-size:.8rem;color:var(--muted);margin-top:2px">visitors this month</div>
</div>
</div>
<div class="card">
<div class="card-header">Profile</div>
<div class="card-body" style="display:flex;align-items:center;gap:12px">
<div class="avatar avatar-lg" style="background:var(--secondary)">AL</div>
<div>
<div style="font-weight:700">Alice Lee</div>
<div style="font-size:.82rem;color:var(--muted)">UX Designer</div>
</div>
</div>
</div>
</div>
</section>
<section class="section">
<p class="section-title">Badges & Avatars</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px">
<span class="badge badge-primary">Primary</span>
<span class="badge badge-success">Active</span>
<span class="badge badge-danger">Error</span>
<span class="badge badge-warning">Warning</span>
<span class="badge badge-muted">Archived</span>
</div>
<div style="display:flex;gap:10px;align-items:center">
<div class="avatar avatar-sm" style="background:var(--primary)">PT</div>
<div class="avatar" style="background:var(--secondary)">AL</div>
<div class="avatar avatar-lg" style="background:var(--success)">JD</div>
<div class="avatar" style="background:var(--warning)">MC</div>
<div class="avatar" style="background:var(--danger)">XZ</div>
</div>
</section>
<section class="section">
<p class="section-title">Alerts</p>
<div class="alert alert-info">Info: your account has been successfully updated.</div>
<div class="alert alert-success">Success: your changes have been saved.</div>
<div class="alert alert-warning">Warning: your subscription expires in 3 days.</div>
<div class="alert alert-danger">Error: unable to reach the remote server.</div>
</section>
<section class="section">
<p class="section-title">Progress bars</p>
<div style="max-width:360px">
<div style="display:flex;justify-content:space-between;font-size:.8rem;margin-bottom:3px"><span>Loading</span><span>72%</span></div>
<div class="progress"><div class="progress-bar" style="width:72%"></div></div>
<div style="display:flex;justify-content:space-between;font-size:.8rem;margin-bottom:3px"><span>Success</span><span>90%</span></div>
<div class="progress"><div class="progress-bar" style="width:90%;background:var(--success)"></div></div>
<div style="display:flex;justify-content:space-between;font-size:.8rem;margin-bottom:3px"><span>Danger</span><span>35%</span></div>
<div class="progress"><div class="progress-bar" style="width:35%;background:var(--danger)"></div></div>
</div>
</section>
<section class="section">
<p class="section-title">Table</p>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Name</th><th>Role</th><th>Status</th><th>Score</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Alice Lee</strong></td>
<td>Designer</td>
<td><span class="badge badge-success">Active</span></td>
<td>98</td>
</tr>
<tr>
<td><strong>Bob Martin</strong></td>
<td>Developer</td>
<td><span class="badge badge-success">Active</span></td>
<td>87</td>
</tr>
<tr>
<td><strong>Clara Vidal</strong></td>
<td>Project Manager</td>
<td><span class="badge badge-warning">On leave</span></td>
<td>74</td>
</tr>
<tr>
<td><strong>David Sorel</strong></td>
<td>Marketing</td>
<td><span class="badge badge-muted">Archived</span></td>
<td>61</td>
</tr>
</tbody>
</table>
</div>
</section>
<section class="section">
<p class="section-title">Tooltips (hover)</p>
<div style="display:flex;gap:16px;flex-wrap:wrap;padding-top:8px">
<button class="btn btn-primary" data-tip="Save the file">Save</button>
<button class="btn btn-outline" data-tip="Open settings">Settings</button>
<span data-tip="More information here" style="text-decoration:underline dotted;cursor:help;font-size:.9rem">Text with tooltip</span>
</div>
</section>
</main>
</div>
<script>
const styleEl = document.getElementById('live-styles');
const editor = document.getElementById('css-editor');
const status = document.getElementById('editor-status');
editor.value = styleEl.textContent;
updateStatus();
editor.addEventListener('input', () => {
styleEl.textContent = editor.value;
updateStatus();
});
editor.addEventListener('keydown', e => {
if (e.key !== 'Tab') return;
e.preventDefault();
const s = editor.selectionStart, end = editor.selectionEnd;
editor.value = editor.value.slice(0, s) + ' ' + editor.value.slice(end);
editor.selectionStart = editor.selectionEnd = s + 2;
styleEl.textContent = editor.value;
});
document.getElementById('f-range').addEventListener('input', e => {
document.getElementById('range-val').textContent = e.target.value;
});
function updateStatus() {
const lines = editor.value.split('\n').length;
const chars = editor.value.length;
status.textContent = `${lines} lines · ${chars} chars`;
}
</script>
</body>
</html>
@Pierre-Thibault

Copy link
Copy Markdown
Author

Here is a simple HTML file that let you edit its CSS for you to play with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment