Yes — here’s a 100% pure CSS / HTML version with zero JavaScript.
Here’s a complete, self-contained Interactive Pure CSS Drawing Canvas that uses:
- Radio buttons for color selection
- Modern CSS Grid for the pixel canvas
- No JavaScript — drawing is powered by :checked, :active, :hover, CSS custom properties, and animation-play-state
How it works
- Select a color with the radio buttons.
- Click and drag (hold mouse down) over the grid cells.
- Each cell permanently “paints” itself with the currently selected color using a one-shot animation that stays in the final state (forwards + paused/running).
HTML:
<div class="app">
<h1>Pure CSS Drawing Canvas</h1>
<p class="hint">
Select a color → click & drag to draw<br />
Click <strong>Clear Canvas</strong> to erase · Click it again (<strong>Unlock Drawing</strong>) to
resume
</p>
<!-- Color radios -->
<input type="radio" name="color" id="c0" checked />
<input type="radio" name="color" id="c1" />
<input type="radio" name="color" id="c2" />
<input type="radio" name="color" id="c3" />
<input type="radio" name="color" id="c4" />
<input type="radio" name="color" id="c5" />
<!-- Clear checkbox -->
<input type="checkbox" id="clear" />
<div class="controls">
<label class="swatch" for="c0" title="Red"></label>
<label class="swatch" for="c1" title="Green"></label>
<label class="swatch" for="c2" title="Blue"></label>
<label class="swatch" for="c3" title="Yellow"></label>
<label class="swatch" for="c4" title="Purple"></label>
<label class="swatch" for="c5" title="Black"></label>
<label class="clear-btn" for="clear">
<span class="clear">Clear Canvas</span>
<span class="unlock">Unlock Drawing</span>
</label>
</div>
<p class="status">Canvas cleared — click “Unlock Drawing” to continue</p>
<div class="canvas-wrapper">
<div class="canvas">
<!-- 20 × 15 = 300 hard-coded pixels (no JavaScript) -->
<div class="pixel"></div>
<div class="pixel"></div>
<div class="pixel"></div>
<div class="pixel"></div>
<div class="pixel"></div>
<div class="pixel"></div>
<!-- copy(<div class="pixel"></div>) till you get 300 elements -->
</div>
</div>
<footer>100% Pure CSS · No JavaScript</footer>
</div>
CSS
:root {
--pixel-size: 20px;
--cols: 20;
--rows: 15;
--color-0: #ef4444;
--color-1: #22c55e;
--color-2: #3b82f6;
--color-3: #eab308;
--color-4: #a855f7;
--color-5: #000000;
--empty: #1e293b;
--bg: #0f172a;
--panel: #1e293b;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100dvh;
background: var(--bg);
color: #e2e8f0;
font-family:
system-ui,
-apple-system,
sans-serif;
display: grid;
place-items: center;
padding: 1.5rem;
}
.app {
display: grid;
gap: 1.25rem;
max-width: 100%;
}
h1 {
font-size: 2rem;
font-weight: 700;
text-align: center;
letter-spacing: -0.02em;
}
.hint {
text-align: center;
font-size: 0.875rem;
color: #94a3b8;
line-height: 1.45;
}
/* Hide native inputs */
input[type="radio"],
input[type="checkbox"] {
position: absolute;
opacity: 0;
pointer-events: none;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: center;
align-items: center;
}
.swatch {
width: 2.75rem;
height: 2.75rem;
border-radius: 50%;
cursor: pointer;
border: 3px solid transparent;
transition:
transform 0.15s,
box-shadow 0.15s,
border-color 0.15s;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}
.swatch:hover {
transform: scale(1.12);
}
/* Active color highlight */
#c0:checked ~ .controls label[for="c0"],
#c1:checked ~ .controls label[for="c1"],
#c2:checked ~ .controls label[for="c2"],
#c3:checked ~ .controls label[for="c3"],
#c4:checked ~ .controls label[for="c4"],
#c5:checked ~ .controls label[for="c5"] {
border-color: #fff;
box-shadow:
0 0 0 1px #fff,
0 4px 12px rgba(0, 0, 0, 0.4);
transform: scale(1.15);
}
label[for="c0"] {
background: var(--color-0);
color: var(--color-0);
}
label[for="c1"] {
background: var(--color-1);
color: var(--color-1);
}
label[for="c2"] {
background: var(--color-2);
color: var(--color-2);
}
label[for="c3"] {
background: var(--color-3);
color: var(--color-3);
}
label[for="c4"] {
background: var(--color-4);
color: var(--color-4);
}
label[for="c5"] {
background: var(--color-5);
color: var(--color-5);
border: 2px solid white;
}
/* Clear / Unlock button */
.clear-btn {
padding: 0.55rem 1.15rem;
border-radius: 9999px;
background: #334155;
color: #e2e8f0;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
border: 2px solid #475569;
transition:
background 0.15s,
border-color 0.15s,
transform 0.15s;
user-select: none;
min-width: 9.5rem;
text-align: center;
}
.clear-btn:hover {
background: #475569;
transform: translateY(-1px);
}
/* When cleared (checked) → red + different text */
#clear:checked ~ .controls .clear-btn {
background: #ef4444;
border-color: #f87171;
color: white;
}
.clear-btn .unlock {
display: none;
}
#clear:checked ~ .controls .clear-btn .clear {
display: none;
}
#clear:checked ~ .controls .clear-btn .unlock {
display: inline;
}
/* Status message that appears only when cleared */
.status {
text-align: center;
font-size: 0.85rem;
color: #f87171;
font-weight: 500;
min-height: 1.3em;
opacity: 0;
transition: opacity 0.2s;
}
#clear:checked ~ .status {
opacity: 1;
}
/* Canvas */
.canvas-wrapper {
background: var(--panel);
padding: 1rem;
border-radius: 1rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
overflow: auto;
max-width: 100%;
}
.canvas {
display: grid;
grid-template-columns: repeat(var(--cols), var(--pixel-size));
grid-template-rows: repeat(var(--rows), var(--pixel-size));
gap: 1px;
background: #334155;
width: fit-content;
margin: 0 auto;
user-select: none;
touch-action: none;
}
.pixel {
width: var(--pixel-size);
height: var(--pixel-size);
background: var(--empty);
--play-0: paused;
--play-1: paused;
--play-2: paused;
--play-3: paused;
--play-4: paused;
--play-5: paused;
animation:
0ms linear 1ms forwards var(--play-0) paint-0,
0ms linear 1ms forwards var(--play-1) paint-1,
0ms linear 1ms forwards var(--play-2) paint-2,
0ms linear 1ms forwards var(--play-3) paint-3,
0ms linear 1ms forwards var(--play-4) paint-4,
0ms linear 1ms forwards var(--play-5) paint-5;
}
@keyframes paint-0 {
to {
background: var(--color-0);
}
}
@keyframes paint-1 {
to {
background: var(--color-1);
}
}
@keyframes paint-2 {
to {
background: var(--color-2);
}
}
@keyframes paint-3 {
to {
background: var(--color-3);
}
}
@keyframes paint-4 {
to {
background: var(--color-4);
}
}
@keyframes paint-5 {
to {
background: var(--color-5);
}
}
/* Paint while a color is selected + mouse held + hover */
#c0:checked ~ .canvas-wrapper .canvas:active .pixel:hover {
--play-0: running;
}
#c1:checked ~ .canvas-wrapper .canvas:active .pixel:hover {
--play-1: running;
}
#c2:checked ~ .canvas-wrapper .canvas:active .pixel:hover {
--play-2: running;
}
#c3:checked ~ .canvas-wrapper .canvas:active .pixel:hover {
--play-3: running;
}
#c4:checked ~ .canvas-wrapper .canvas:active .pixel:hover {
--play-4: running;
}
#c5:checked ~ .canvas-wrapper .canvas:active .pixel:hover {
--play-5: running;
}
/* Clear / Reset rule */
#clear:checked ~ .canvas-wrapper .pixel {
animation: none !important;
background: var(--empty) !important;
}
footer {
text-align: center;
font-size: 0.8rem;
color: #64748b;
}
Tips
- Change --pixel-size, --cols, and --rows in :root to resize the canvas.
- Add more colors by duplicating a radio + label + keyframes block.
- For a completely zero-JS version, replace the small generation script with 768 hardcoded elements (or generate them with a build step / Pug / etc.).
Enjoy drawing with pure CSS!
Yes — here’s a 100% pure CSS / HTML version with zero JavaScript.


Top comments (0)