24 CSS Tags & Chips19 / 24

CSS onlyMIT licensed

Dismissible Chip with Undo Toast Notification

Forgiving deletion, the pattern Gmail made mandatory — remove a chip and a toast slides up offering Undo — built from checkbox state alone. The dismissed chip's checkbox drives everything: chip collapses, its personal toast rises (bottom-center, stacked by DOM order), and the toast's Undo button is just a <label> pointed back at the same checkbox — click it and the chip re-inflates through the same transition it left by. State restoration as a hyperlink between two controls.

Published Updated

Live Demo
Try it

The code

<section class="ctc-19" aria-label="Dismissible chip with undo toast demo">
  <form class="ctc-19__stage">
    <div class="ctc-19__wrap">
      <header class="ctc-19__head">
        <p class="ctc-19__kicker">Undo toasts · label[for] as time travel</p>
        <h1>Delete without fear</h1>
        <p class="ctc-19__sub">Dismiss a connected app — a toast offers the way back. Undo is a <code>&lt;label&gt;</code> for the same checkbox that deleted it.</p>
      </header>
      <div class="ctc-19__panel">
        <p class="ctc-19__lab">Connected apps</p>
        <div class="ctc-19__row">
          <span class="ctc-19__chip" style="--h:255"><i>F</i><span>Figma</span><input type="checkbox" id="ctc19-figma" aria-label="Disconnect Figma"></span>
          <span class="ctc-19__chip" style="--h:150"><i>S</i><span>Slack</span><input type="checkbox" id="ctc19-slack" aria-label="Disconnect Slack"></span>
          <span class="ctc-19__chip" style="--h:40"><i>L</i><span>Linear</span><input type="checkbox" id="ctc19-linear" aria-label="Disconnect Linear"></span>
          <span class="ctc-19__chip" style="--h:320"><i>N</i><span>Notion</span><input type="checkbox" id="ctc19-notion" aria-label="Disconnect Notion"></span>
          <span class="ctc-19__chip" style="--h:200"><i>G</i><span>GitHub</span><input type="checkbox" id="ctc19-github" aria-label="Disconnect GitHub"></span>
        </div>
        <button class="ctc-19__reset" type="reset">↺ Restore everything</button>
      </div>
      <div class="ctc-19__toasts" aria-label="Undo notifications">
        <div class="ctc-19__toast" data-for="figma" role="status"><b>Figma disconnected</b><label for="ctc19-figma">Undo</label><i></i></div>
        <div class="ctc-19__toast" data-for="slack" role="status"><b>Slack disconnected</b><label for="ctc19-slack">Undo</label><i></i></div>
        <div class="ctc-19__toast" data-for="linear" role="status"><b>Linear disconnected</b><label for="ctc19-linear">Undo</label><i></i></div>
        <div class="ctc-19__toast" data-for="notion" role="status"><b>Notion disconnected</b><label for="ctc19-notion">Undo</label><i></i></div>
        <div class="ctc-19__toast" data-for="github" role="status"><b>GitHub disconnected</b><label for="ctc19-github">Undo</label><i></i></div>
      </div>
      <p class="ctc-19__note">The drain bar signals urgency but CSS can't run timers — the toast politely waits. Auto-commit needs three lines of JS: on check, <code>setTimeout</code> the real deletion; Undo clears it.</p>
    </div>
  </form>
</section>
.ctc-19,
.ctc-19 *,
.ctc-19 *::before,
.ctc-19 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.ctc-19 {
  --bg: oklch(0.965 0.008 230);
  --panel: oklch(0.997 0.002 230);
  --ink: oklch(0.24 0.025 240);
  --mut: oklch(0.5 0.02 240);
  --line: oklch(0.88 0.012 230);
  --acc: oklch(0.55 0.16 230);
  --del: oklch(0.6 0.21 25);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--ink);
  container-type: inline-size;
}

.ctc-19__stage {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100svh;
  overflow-y: auto;
  display: grid;
  place-items: center;
  padding: clamp(16px,4cqi,48px);
  background: radial-gradient(70cqi 55cqi at 88% -10%,color-mix(in oklch,var(--acc) 13%,transparent),transparent 62%),var(--bg);
  container: ctc19/inline-size;
  border: 0;
}

.ctc-19__wrap {
  width: min(620px,100%);
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.ctc-19__kicker {
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--acc);
}

.ctc-19__head h1 {
  font-size: clamp(24px,3.8cqi,38px);
  font-weight: 800;
  letter-spacing: -.03em;
  margin: 8px 0 6px;
}

.ctc-19__sub {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--mut);
}

.ctc-19__sub code,
.ctc-19__note code {
  font-family: ui-monospace,Menlo,monospace;
  font-size: .9em;
  background: oklch(0.92 0.01 230);
  padding: 1px 5px;
  border-radius: 4px;
  color: var(--ink);
}

.ctc-19__panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.ctc-19__lab {
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--mut);
}

.ctc-19__row {
  display: flex;
  flex-wrap: wrap;
}

.ctc-19__chip {
  --c: oklch(0.6 0.14 var(--h,230));
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 44px;
  max-width: 300px;
  padding: 0 9px 0 7px;
  margin: 0 9px 9px 0;
  border-radius: 99px;
  border: 1.5px solid var(--line);
  background: oklch(0.98 0.004 230);
  font-size: 14px;
  font-weight: 650;
  white-space: nowrap;
  overflow: hidden;
  transition: max-width .4s,padding .4s,margin .4s,opacity .3s,scale .3s,border-color .2s;
}

.ctc-19__chip i {
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-style: normal;
  font-size: 12px;
  font-weight: 800;
  color: oklch(0.99 0 0);
  background: linear-gradient(140deg,oklch(0.66 0.13 var(--h)),oklch(0.48 0.14 calc(var(--h) + 45)));
}

.ctc-19__chip:hover {
  border-color: color-mix(in oklch,var(--c) 45%,var(--line));
}

.ctc-19__chip input {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 0;
  background: color-mix(in oklch,var(--ink) 8%,transparent);
  cursor: pointer;
  transition: background .2s,rotate .25s;
  outline-offset: 2px;
}

.ctc-19__chip input::before,
.ctc-19__chip input::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 10px;
  height: 1.8px;
  border-radius: 2px;
  background: var(--mut);
  translate: -50% -50%;
}

.ctc-19__chip input::before {
  rotate: 45deg;
}

.ctc-19__chip input::after {
  rotate: -45deg;
}

.ctc-19__chip input:hover,
.ctc-19__chip input:focus-visible {
  background: color-mix(in oklch,var(--del) 16%,transparent);
  rotate: 90deg;
}

.ctc-19__chip input:focus-visible {
  outline: 2px solid var(--del);
}

.ctc-19__chip:has(:checked) {
  max-width: 0;
  padding-inline: 0;
  margin: 0;
  opacity: 0;
  scale: .7;
  pointer-events: none;
  border-color: transparent;
}

.ctc-19__reset {
  align-self: flex-start;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 12px;
  border: 1.5px solid var(--line);
  background: var(--panel);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  transition: border-color .2s,color .2s,translate .2s;
}

.ctc-19__reset:hover,
.ctc-19__reset:focus-visible {
  border-color: var(--acc);
  color: var(--acc);
  translate: 0 -1px;
}

.ctc-19__reset:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}

.ctc-19__toasts {
  position: absolute;
  left: 50%;
  bottom: 22px;
  translate: -50% 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(400px,calc(100% - 32px));
  pointer-events: none;
  z-index: 5;
}

.ctc-19__toast {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  overflow: hidden;
  padding: 13px 16px;
  border-radius: 14px;
  background: oklch(0.23 0.02 240);
  color: oklch(0.96 0.005 230);
  box-shadow: 0 16px 40px -14px oklch(0.15 0.02 240/.6);
  opacity: 0;
  translate: 0 16px;
  scale: .96;
  pointer-events: none;
  max-height: 0;
  padding-block: 0;
  margin: 0;
  transition: opacity .35s,translate .35s cubic-bezier(.34,1.3,.64,1),scale .35s,max-height .35s,padding .35s;
}

.ctc-19__toast b {
  font-size: 13px;
  font-weight: 650;
}

.ctc-19__toast label {
  min-height: 32px;
  display: inline-flex;
  align-items: center;
  padding: 0 12px;
  border-radius: 9px;
  background: oklch(1 0 0/.12);
  color: oklch(0.85 0.1 230);
  font-size: 12.5px;
  font-weight: 800;
  letter-spacing: .02em;
  cursor: pointer;
  transition: background .2s;
}

.ctc-19__toast label:hover {
  background: oklch(1 0 0/.22);
}

.ctc-19__toast i {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2.5px;
  width: 100%;
  background: var(--acc);
  transform-origin: left;
}

.ctc-19__stage:has(#ctc19-figma:checked) .ctc-19__toast[data-for="figma"],
.ctc-19__stage:has(#ctc19-slack:checked) .ctc-19__toast[data-for="slack"],
.ctc-19__stage:has(#ctc19-linear:checked) .ctc-19__toast[data-for="linear"],
.ctc-19__stage:has(#ctc19-notion:checked) .ctc-19__toast[data-for="notion"],
.ctc-19__stage:has(#ctc19-github:checked) .ctc-19__toast[data-for="github"] {
  opacity: 1;
  translate: 0 0;
  scale: 1;
  pointer-events: auto;
  max-height: 70px;
  padding-block: 13px;
}

.ctc-19__stage:has(#ctc19-figma:checked) .ctc-19__toast[data-for="figma"] i,
.ctc-19__stage:has(#ctc19-slack:checked) .ctc-19__toast[data-for="slack"] i,
.ctc-19__stage:has(#ctc19-linear:checked) .ctc-19__toast[data-for="linear"] i,
.ctc-19__stage:has(#ctc19-notion:checked) .ctc-19__toast[data-for="notion"] i,
.ctc-19__stage:has(#ctc19-github:checked) .ctc-19__toast[data-for="github"] i {
  animation: ctc19-drain 5s linear forwards;
}

@keyframes ctc19-drain {
  from {
    scale: 1 1;
  }

  to {
    scale: 0 1;
  }
}

.ctc-19__note {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--mut);
  border-left: 3px solid var(--acc);
  padding-left: 12px;
}

@media (prefers-reduced-motion: reduce) {
  .ctc-19 * {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Tags & Chip from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Dismissible Chip with Undo Toast Notification
Source: https://codefronts.com/snippets/css-tags-chips/remove-with-undo/

Forgiving deletion, the pattern Gmail made mandatory — remove a chip and a toast slides up offering Undo — built from checkbox state alone. The dismissed chip's checkbox drives everything: chip collapses, its personal toast rises (bottom-center, stacked by DOM order), and the toast's Undo button is just a <label> pointed back at the same checkbox — click it and the chip re-inflates through the same transition it left by. State restoration as a hyperlink between two controls.
## HTML
```html
<section class="ctc-19" aria-label="Dismissible chip with undo toast demo">
  <form class="ctc-19__stage">
    <div class="ctc-19__wrap">
      <header class="ctc-19__head">
        <p class="ctc-19__kicker">Undo toasts · label[for] as time travel</p>
        <h1>Delete without fear</h1>
        <p class="ctc-19__sub">Dismiss a connected app — a toast offers the way back. Undo is a <code>&lt;label&gt;</code> for the same checkbox that deleted it.</p>
      </header>
      <div class="ctc-19__panel">
        <p class="ctc-19__lab">Connected apps</p>
        <div class="ctc-19__row">
          <span class="ctc-19__chip" style="--h:255"><i>F</i><span>Figma</span><input type="checkbox" id="ctc19-figma" aria-label="Disconnect Figma"></span>
          <span class="ctc-19__chip" style="--h:150"><i>S</i><span>Slack</span><input type="checkbox" id="ctc19-slack" aria-label="Disconnect Slack"></span>
          <span class="ctc-19__chip" style="--h:40"><i>L</i><span>Linear</span><input type="checkbox" id="ctc19-linear" aria-label="Disconnect Linear"></span>
          <span class="ctc-19__chip" style="--h:320"><i>N</i><span>Notion</span><input type="checkbox" id="ctc19-notion" aria-label="Disconnect Notion"></span>
          <span class="ctc-19__chip" style="--h:200"><i>G</i><span>GitHub</span><input type="checkbox" id="ctc19-github" aria-label="Disconnect GitHub"></span>
        </div>
        <button class="ctc-19__reset" type="reset">↺ Restore everything</button>
      </div>
      <div class="ctc-19__toasts" aria-label="Undo notifications">
        <div class="ctc-19__toast" data-for="figma" role="status"><b>Figma disconnected</b><label for="ctc19-figma">Undo</label><i></i></div>
        <div class="ctc-19__toast" data-for="slack" role="status"><b>Slack disconnected</b><label for="ctc19-slack">Undo</label><i></i></div>
        <div class="ctc-19__toast" data-for="linear" role="status"><b>Linear disconnected</b><label for="ctc19-linear">Undo</label><i></i></div>
        <div class="ctc-19__toast" data-for="notion" role="status"><b>Notion disconnected</b><label for="ctc19-notion">Undo</label><i></i></div>
        <div class="ctc-19__toast" data-for="github" role="status"><b>GitHub disconnected</b><label for="ctc19-github">Undo</label><i></i></div>
      </div>
      <p class="ctc-19__note">The drain bar signals urgency but CSS can't run timers — the toast politely waits. Auto-commit needs three lines of JS: on check, <code>setTimeout</code> the real deletion; Undo clears it.</p>
    </div>
  </form>
</section>
```
## CSS
```css
.ctc-19,
.ctc-19 *,
.ctc-19 *::before,
.ctc-19 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.ctc-19 {
  --bg: oklch(0.965 0.008 230);
  --panel: oklch(0.997 0.002 230);
  --ink: oklch(0.24 0.025 240);
  --mut: oklch(0.5 0.02 240);
  --line: oklch(0.88 0.012 230);
  --acc: oklch(0.55 0.16 230);
  --del: oklch(0.6 0.21 25);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--ink);
  container-type: inline-size;
}

.ctc-19__stage {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100svh;
  overflow-y: auto;
  display: grid;
  place-items: center;
  padding: clamp(16px,4cqi,48px);
  background: radial-gradient(70cqi 55cqi at 88% -10%,color-mix(in oklch,var(--acc) 13%,transparent),transparent 62%),var(--bg);
  container: ctc19/inline-size;
  border: 0;
}

.ctc-19__wrap {
  width: min(620px,100%);
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.ctc-19__kicker {
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--acc);
}

.ctc-19__head h1 {
  font-size: clamp(24px,3.8cqi,38px);
  font-weight: 800;
  letter-spacing: -.03em;
  margin: 8px 0 6px;
}

.ctc-19__sub {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--mut);
}

.ctc-19__sub code,
.ctc-19__note code {
  font-family: ui-monospace,Menlo,monospace;
  font-size: .9em;
  background: oklch(0.92 0.01 230);
  padding: 1px 5px;
  border-radius: 4px;
  color: var(--ink);
}

.ctc-19__panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.ctc-19__lab {
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--mut);
}

.ctc-19__row {
  display: flex;
  flex-wrap: wrap;
}

.ctc-19__chip {
  --c: oklch(0.6 0.14 var(--h,230));
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 44px;
  max-width: 300px;
  padding: 0 9px 0 7px;
  margin: 0 9px 9px 0;
  border-radius: 99px;
  border: 1.5px solid var(--line);
  background: oklch(0.98 0.004 230);
  font-size: 14px;
  font-weight: 650;
  white-space: nowrap;
  overflow: hidden;
  transition: max-width .4s,padding .4s,margin .4s,opacity .3s,scale .3s,border-color .2s;
}

.ctc-19__chip i {
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-style: normal;
  font-size: 12px;
  font-weight: 800;
  color: oklch(0.99 0 0);
  background: linear-gradient(140deg,oklch(0.66 0.13 var(--h)),oklch(0.48 0.14 calc(var(--h) + 45)));
}

.ctc-19__chip:hover {
  border-color: color-mix(in oklch,var(--c) 45%,var(--line));
}

.ctc-19__chip input {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 0;
  background: color-mix(in oklch,var(--ink) 8%,transparent);
  cursor: pointer;
  transition: background .2s,rotate .25s;
  outline-offset: 2px;
}

.ctc-19__chip input::before,
.ctc-19__chip input::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 10px;
  height: 1.8px;
  border-radius: 2px;
  background: var(--mut);
  translate: -50% -50%;
}

.ctc-19__chip input::before {
  rotate: 45deg;
}

.ctc-19__chip input::after {
  rotate: -45deg;
}

.ctc-19__chip input:hover,
.ctc-19__chip input:focus-visible {
  background: color-mix(in oklch,var(--del) 16%,transparent);
  rotate: 90deg;
}

.ctc-19__chip input:focus-visible {
  outline: 2px solid var(--del);
}

.ctc-19__chip:has(:checked) {
  max-width: 0;
  padding-inline: 0;
  margin: 0;
  opacity: 0;
  scale: .7;
  pointer-events: none;
  border-color: transparent;
}

.ctc-19__reset {
  align-self: flex-start;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 12px;
  border: 1.5px solid var(--line);
  background: var(--panel);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  transition: border-color .2s,color .2s,translate .2s;
}

.ctc-19__reset:hover,
.ctc-19__reset:focus-visible {
  border-color: var(--acc);
  color: var(--acc);
  translate: 0 -1px;
}

.ctc-19__reset:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}

.ctc-19__toasts {
  position: absolute;
  left: 50%;
  bottom: 22px;
  translate: -50% 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(400px,calc(100% - 32px));
  pointer-events: none;
  z-index: 5;
}

.ctc-19__toast {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  overflow: hidden;
  padding: 13px 16px;
  border-radius: 14px;
  background: oklch(0.23 0.02 240);
  color: oklch(0.96 0.005 230);
  box-shadow: 0 16px 40px -14px oklch(0.15 0.02 240/.6);
  opacity: 0;
  translate: 0 16px;
  scale: .96;
  pointer-events: none;
  max-height: 0;
  padding-block: 0;
  margin: 0;
  transition: opacity .35s,translate .35s cubic-bezier(.34,1.3,.64,1),scale .35s,max-height .35s,padding .35s;
}

.ctc-19__toast b {
  font-size: 13px;
  font-weight: 650;
}

.ctc-19__toast label {
  min-height: 32px;
  display: inline-flex;
  align-items: center;
  padding: 0 12px;
  border-radius: 9px;
  background: oklch(1 0 0/.12);
  color: oklch(0.85 0.1 230);
  font-size: 12.5px;
  font-weight: 800;
  letter-spacing: .02em;
  cursor: pointer;
  transition: background .2s;
}

.ctc-19__toast label:hover {
  background: oklch(1 0 0/.22);
}

.ctc-19__toast i {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2.5px;
  width: 100%;
  background: var(--acc);
  transform-origin: left;
}

.ctc-19__stage:has(#ctc19-figma:checked) .ctc-19__toast[data-for="figma"],
.ctc-19__stage:has(#ctc19-slack:checked) .ctc-19__toast[data-for="slack"],
.ctc-19__stage:has(#ctc19-linear:checked) .ctc-19__toast[data-for="linear"],
.ctc-19__stage:has(#ctc19-notion:checked) .ctc-19__toast[data-for="notion"],
.ctc-19__stage:has(#ctc19-github:checked) .ctc-19__toast[data-for="github"] {
  opacity: 1;
  translate: 0 0;
  scale: 1;
  pointer-events: auto;
  max-height: 70px;
  padding-block: 13px;
}

.ctc-19__stage:has(#ctc19-figma:checked) .ctc-19__toast[data-for="figma"] i,
.ctc-19__stage:has(#ctc19-slack:checked) .ctc-19__toast[data-for="slack"] i,
.ctc-19__stage:has(#ctc19-linear:checked) .ctc-19__toast[data-for="linear"] i,
.ctc-19__stage:has(#ctc19-notion:checked) .ctc-19__toast[data-for="notion"] i,
.ctc-19__stage:has(#ctc19-github:checked) .ctc-19__toast[data-for="github"] i {
  animation: ctc19-drain 5s linear forwards;
}

@keyframes ctc19-drain {
  from {
    scale: 1 1;
  }

  to {
    scale: 0 1;
  }
}

.ctc-19__note {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--mut);
  border-left: 3px solid var(--acc);
  padding-left: 12px;
}

@media (prefers-reduced-motion: reduce) {
  .ctc-19 * {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
  }
}
```

How this works

One checkbox, two views. The chip and its toast both key off the same input — the chip inverts the state (visible when unchecked), the toast reads it straight (visible when checked):

.chip:has(:checked){ max-width:0; opacity:0; /* leaves */ }
.stage:has(#kill-figma:checked) .toast[data-for="figma"]{
  translate:0 0; opacity:1; pointer-events:auto; /* arrives */ }
/* the undo button is just a label for the same checkbox */
<label for="kill-figma" class="toast__undo">Undo</label>

That <label for> is the whole undo mechanism: activating it unchecks the box, the chip's collapse transition plays backward, the toast sinks away. No state store, no event bus — the platform's label-input association IS the wiring, and it works from the keyboard because the label is focusable via its control.

Toasts stack in a fixed-position column (absolute to the stage here, so the demo stays iframe-safe); each slides up with a springy translate and carries a thin progress bar that drains over 5 seconds. The bar is honest about being CSS: it communicates urgency but cannot uncheck a box, so the toast stays until answered — the note shows the 3-line JS timer to add if you want auto-commit. 'Clear all' is the form-reset trick from demo 05, resurrecting every chip and sinking every toast at once.

Make it yours

  • Toast position: the .ctc-19__toasts column is bottom-center; pin it bottom-right with one inset change. Keep width ≤ 420px so Undo stays within thumb reach on mobile.
  • Timing: the 5s drain on the progress bar is convention (Gmail uses ~5s, iOS ~4s). Match your real auto-commit timer if you add one.
  • Auto-commit: setTimeout(() => input.closest('form').submit-or-remove, 5000) per checked box — the note in the demo shows it; keep Undo working during the countdown.
  • Multiple deletions stack chronologically; cap the visible column with max-height + overflow if your users rage-delete.

Gotchas — read before shipping

  • The toast must remain in the accessibility tree while visible — this demo toggles opacity/translate/pointer-events, never display:none, so the Undo label stays reachable mid-transition.
  • Add role='status' to each toast: screen readers then announce 'Figma removed — Undo' without focus theft. Never use role='alert' for undoable actions; it interrupts.
  • Pure CSS cannot run a timer: the drain bar is signaling, not enforcement. If the toast implies auto-delete, wire the 3-line JS or users will (rightly) distrust your UI.
  • Fixed-position toasts inside transformed ancestors position against the ancestor, not the viewport — the classic 'my toast is stuck in the card' bug; this demo uses absolute-to-stage on purpose.

Browser support

ChromeSafariFirefoxEdge
111+16.4+121+111+

Chip collapse and toast reveal both need :has() (Firefox 121+). Without it, the × still checks but nothing hides — inert, not broken. label[for] wiring and the transitions are universal.

Search CodeFronts

Loading…