Interactive Vote Card
Poll card with clickable options that fill in with a progress bar and show percentage. Selected option persists state via JS class toggle.
Interactive Vote Card the 20th of 20 designs in the 20 CSS Cards with Animations collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
The code
<div class="stage-20">
<div class="card-vote">
<div class="vote-header">
<h4>Best CSS feature?</h4>
<span class="vote-count">1,284 votes</span>
</div>
<div class="vote-desc">Pick your favourite modern CSS feature.</div>
<div class="vote-options">
<div
class="vote-opt selected"
onclick="
this.closest('.vote-options')
.querySelectorAll('.vote-opt')
.forEach((o) => o.classList.remove('selected'));
this.classList.add('selected');
"
>
<div class="vote-fill"></div>
<span class="vote-opt-label">Container Queries</span>
<span class="vote-opt-pct">64%</span>
</div>
<div
class="vote-opt"
onclick="
this.closest('.vote-options')
.querySelectorAll('.vote-opt')
.forEach((o) => o.classList.remove('selected'));
this.classList.add('selected');
"
>
<div class="vote-fill" style="width: 22%"></div>
<span class="vote-opt-label">:has() selector</span>
<span class="vote-opt-pct">22%</span>
</div>
<div
class="vote-opt"
onclick="
this.closest('.vote-options')
.querySelectorAll('.vote-opt')
.forEach((o) => o.classList.remove('selected'));
this.classList.add('selected');
"
>
<div class="vote-fill" style="width: 14%"></div>
<span class="vote-opt-label">CSS Layers</span>
<span class="vote-opt-pct">14%</span>
</div>
</div>
</div>
</div> .card-vote {
width: 200px;
padding: 18px 20px;
border-radius: 14px;
background: var(--ccg-surface2);
border: 1px solid var(--ccg-border2);
}
.vote-header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
}
.vote-header h4 {
margin: 0;
font-size: 13px;
font-weight: 700;
color: #fff;
line-height: 1.3;
flex: 1;
min-width: 0;
}
.vote-count {
font-family: var(--ccg-mono);
font-size: 11px;
color: var(--ccg-muted);
}
.vote-desc {
font-size: 11px;
color: var(--ccg-muted);
line-height: 1.5;
margin-bottom: 14px;
}
.vote-options {
display: flex;
flex-direction: column;
gap: 6px;
}
.vote-opt {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 10px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--ccg-border);
cursor: pointer;
transition: all 0.2s;
position: relative;
overflow: hidden;
}
.vote-opt:hover {
border-color: rgba(124, 108, 255, 0.4);
background: rgba(124, 108, 255, 0.07);
}
.vote-opt.selected {
border-color: var(--ccg-accent);
}
.vote-fill {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background: rgba(124, 108, 255, 0.12);
transition: width 0.4s ease;
width: 0;
}
.vote-opt.selected .vote-fill {
width: 64%;
}
.vote-opt-label {
font-size: 11px;
color: var(--ccg-label);
position: relative;
z-index: 1;
}
.vote-opt-pct {
font-family: var(--ccg-mono);
font-size: 10px;
color: var(--ccg-muted);
margin-left: auto;
position: relative;
z-index: 1;
}
.vote-opt.selected .vote-opt-pct {
color: var(--ccg-accent);
}
.vote-opt.selected .vote-opt-label {
color: #fff;
}
/* parent stage backdrop (so the demo renders standalone) */
[class^="stage-"] {
width: 100%;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
padding: 2.5rem 1.5rem;
box-sizing: border-box;
}
.stage-20 {
background: #0a0a0f;
}
/* gallery vars + keyframes (so the demo renders standalone) */
:root {
--ccg-bg: #0a0a0f;
--ccg-surface: #111118;
--ccg-surface2: #17171f;
--ccg-surface3: #1e1e28;
--ccg-border: rgba(255, 255, 255, 0.15);
--ccg-border2: rgba(255, 255, 255, 0.13);
--ccg-accent: #7c6cff;
--ccg-pink: #ff6c8a;
--ccg-green: #1ed98a;
--ccg-amber: #f5a84a;
--ccg-cyan: #3de8f5;
--ccg-text: #f0eeff;
--ccg-muted: #6b6987;
--ccg-label: #9896b8;
--ccg-mono: "DM Mono", "Fira Code", monospace;
--ccg-sans: "Syne", sans-serif;
}
/* missing class rules (merged from gallery) */
.vote-desc {
font-size: 11px;
color: var(--ccg-muted);
line-height: 1.5;
margin-bottom: 14px;
} document.querySelectorAll(".vote-opt").forEach((opt) => {
opt.addEventListener("click", () => {
opt
.closest(".vote-options")
.querySelectorAll(".vote-opt")
.forEach((o) => o.classList.remove("selected"));
opt.classList.add("selected");
});
});