Sticky group headers

Letter headers in a contact list stick to the top and push each other away. Pure CSS.

How it works

Each group is its own block and its header is position: sticky; top: 0. Because a sticky element can’t leave its parent, the next group’s header naturally pushes the previous one off screen.

Full file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sticky group headers</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-font-smoothing: antialiased; }

body { background: #fff; color: #1a1a1a; font-family: system-ui, sans-serif; }
.list { max-width: 520px; margin: 0 auto; }
.group h3 {
  position: sticky; top: 0; margin: 0; padding: 10px 20px;
  background: #ffe66d; font-size: 1.4rem; border-bottom: 2px solid #1a1a1a;
}
.group div { padding: 16px 20px; border-bottom: 1px solid #eee; display: flex; align-items: center; gap: 14px; }
.group div::before { content: ''; width: 36px; height: 36px; border-radius: 50%; background: var(--c); }
</style>
</head>
<body>
<main class="list" id="list"></main>
<script>
/* Only used to generate the sample list. The sticky effect is pure CSS. */
const names = { A: ['Ada', 'Amir', 'Ana', 'Arjun'], B: ['Bea', 'Bruno', 'Bo'], C: ['Chen', 'Clara', 'Cyrus', 'Coco'], D: ['Dara', 'Dmitri', 'Dee'], E: ['Elif', 'Emeka', 'Eva'], F: ['Farah', 'Felix', 'Fumi'], G: ['Gus', 'Greta', 'Gael'], H: ['Hana', 'Hugo', 'Hiro'] };
const colors = ['#ff6b6b', '#4ecdc4', '#556270', '#c7f464', '#ffa36c'];
const list = document.getElementById('list');
let k = 0;
for (const L in names) {
  list.insertAdjacentHTML('beforeend', '<section class="group"><h3>' + L + '</h3>' + names[L].map(n => '<div style="--c:' + colors[k++ % 5] + '">' + n + '</div>').join('') + '</section>');
}
</script>
</body>
</html>

More sticky effects