Collapse out of the layout
Items shrink to zero height so the list closes the gap smoothly. Uses grid rows 1fr to 0fr, no height guessing.
CSSJavaScriptIntermediate
<div class="list" id="list"></div>
.list{width:min(360px,90vw)}
.row{display:grid;grid-template-rows:1fr;transition:grid-template-rows .45s cubic-bezier(.4,0,.2,1),opacity .35s}
.row.gone{grid-template-rows:0fr;opacity:0}
.inner{overflow:hidden;min-height:0}
.item{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;margin-bottom:8px;border-radius:10px;background:#2a1513;border:1px solid #4a2e2a}
button{font:inherit;color:#ff8f8f;background:none;border:1px solid #5a3030;border-radius:8px;padding:5px 12px;cursor:pointer}
.reset{display:block;margin:14px auto 0;color:#cfc3c1;border-color:#4a2e2a}
const list = document.getElementById('list');
function fill() {
list.innerHTML = '';
['Milk', 'Eggs', 'Bread', 'Coffee'].forEach(name => {
const row = document.createElement('div');
row.className = 'row';
row.innerHTML = `<div class="inner"><div class="item"><span>${name}</span><button>Remove</button></div></div>`;
row.querySelector('button').onclick = () => {
row.classList.add('gone');
setTimeout(() => row.remove(), 500);
};
list.append(row);
});
const reset = document.createElement('button');
reset.className = 'reset';
reset.textContent = 'Reset';
reset.onclick = fill;
list.append(reset);
}
fill();
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Collapse</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{display:grid;place-items:center;background:#140807;color:#fffffc;font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
.list{width:min(360px,90vw)}
.row{display:grid;grid-template-rows:1fr;transition:grid-template-rows .45s cubic-bezier(.4,0,.2,1),opacity .35s}
.row.gone{grid-template-rows:0fr;opacity:0}
.inner{overflow:hidden;min-height:0}
.item{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;margin-bottom:8px;border-radius:10px;background:#2a1513;border:1px solid #4a2e2a}
button{font:inherit;color:#ff8f8f;background:none;border:1px solid #5a3030;border-radius:8px;padding:5px 12px;cursor:pointer}
.reset{display:block;margin:14px auto 0;color:#cfc3c1;border-color:#4a2e2a}
</style></head>
<body>
<div class="list" id="list"></div>
<script>
const list = document.getElementById('list');
function fill() {
list.innerHTML = '';
['Milk', 'Eggs', 'Bread', 'Coffee'].forEach(name => {
const row = document.createElement('div');
row.className = 'row';
row.innerHTML = `<div class="inner"><div class="item"><span>${name}</span><button>Remove</button></div></div>`;
row.querySelector('button').onclick = () => {
row.classList.add('gone');
setTimeout(() => row.remove(), 500);
};
list.append(row);
});
const reset = document.createElement('button');
reset.className = 'reset';
reset.textContent = 'Reset';
reset.onclick = fill;
list.append(reset);
}
fill();
</script>
</body></html>
Good for
Removing rows from lists, tables, carts and to-do items without the rest jumping up.
Watch out for
You cannot animate height auto. The grid-template-rows 1fr to 0fr trick works for any content height.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.