Remove with Undo
Click × to remove a chip — but a small Undo toast appears for 4 seconds before deletion is final. The pattern Gmail and Linear made standard.
Remove with Undo the 15th of 20 designs in the 20 CSS Tags & Chips Designs 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="ctc-undo">
<div class="ctc-undo-list">
<span class="ctc-undo-chip" data-ctc-undo
>Frontend<button type="button" aria-label="Remove Frontend">×</button></span
>
<span class="ctc-undo-chip" data-ctc-undo
>Backend<button type="button" aria-label="Remove Backend">×</button></span
>
<span class="ctc-undo-chip" data-ctc-undo
>DevOps<button type="button" aria-label="Remove DevOps">×</button></span
>
</div>
<div class="ctc-undo-toast" role="status" aria-live="polite" hidden>
<span class="ctc-undo-msg"></span>
<button type="button" class="ctc-undo-btn">Undo</button>
</div>
</div> .ctc-undo {
position: relative;
display: flex;
flex-direction: column;
gap: 12px;
min-height: 70px;
}
.ctc-undo-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.ctc-undo-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 8px 6px 12px;
background: #1f1f2e;
color: #f0eeff;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 999px;
font:
600 12px/1 system-ui,
sans-serif;
transition:
opacity 0.25s,
transform 0.25s;
}
.ctc-undo-chip.is-removing {
opacity: 0;
transform: scale(0.7);
pointer-events: none;
}
.ctc-undo-chip button {
width: 18px;
height: 18px;
background: transparent;
border: 0;
padding: 0;
color: #b8b6d4;
cursor: pointer;
font-size: 16px;
line-height: 1;
border-radius: 50%;
}
.ctc-undo-chip button:hover {
background: rgba(255, 255, 255, 0.1);
color: #fff;
}
.ctc-undo-toast {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
background: #15151d;
border: 1px solid rgba(46, 204, 138, 0.4);
border-radius: 8px;
font:
500 12px/1.2 system-ui,
sans-serif;
color: #c4b5fd;
align-self: flex-start;
animation: ctc-undo-in 0.2s ease;
}
.ctc-undo-toast[hidden] {
display: none;
}
.ctc-undo-btn {
background: transparent;
border: 0;
color: #2ecc8a;
font: inherit;
font-weight: 700;
cursor: pointer;
padding: 0;
}
.ctc-undo-btn:hover {
text-decoration: underline;
}
@keyframes ctc-undo-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) {
.ctc-undo-toast {
animation: none !important;
}
}
(function () {
document.querySelectorAll(".ctc-undo").forEach(function (root) {
var toast = root.querySelector(".ctc-undo-toast");
var msg = root.querySelector(".ctc-undo-msg");
var btn = root.querySelector(".ctc-undo-btn");
var pendingChip = null;
var timer = null;
function commit() {
if (pendingChip && pendingChip.parentNode) pendingChip.parentNode.removeChild(pendingChip);
pendingChip = null;
toast.setAttribute("hidden", "");
timer = null;
}
btn.addEventListener("click", function () {
if (!pendingChip) return;
pendingChip.classList.remove("is-removing");
clearTimeout(timer);
timer = null;
pendingChip = null;
toast.setAttribute("hidden", "");
});
root.querySelectorAll("[data-ctc-undo] button").forEach(function (x) {
x.addEventListener("click", function () {
if (timer) commit();
var chip = x.closest("[data-ctc-undo]");
chip.classList.add("is-removing");
pendingChip = chip;
msg.textContent = 'Removed "' + chip.firstChild.textContent.trim() + '"';
toast.removeAttribute("hidden");
timer = setTimeout(commit, 4000);
});
});
});
})();