DEV Community

Cover image for CSS Only-Interactive Pure CSS Drawing Canvas Using Radio Buttons and Modern Grid Layouts
Prahalad S
Prahalad S

Posted on

CSS Only-Interactive Pure CSS Drawing Canvas Using Radio Buttons and Modern Grid Layouts

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 &amp; 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>
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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.

Here is codepen Demo

Top comments (0)