Skip to main content

Dynamic-height Feed — In a Container

mates-virtual virtualList inside a parent div with overflow:auto. Items have variable heights (1–6 sentences each) — ResizeObserver measures every rendered row automatically, no itemHeight needed. The parent div is the scroll container; no scroller: true.

Quick start
import { virtualList } from "mates-virtual";

// Variable-height items — no itemHeight required.
// Parent div with overflow:auto is the scroll container.
html`
  <div style="height:500px;overflow:auto;border:1px solid …">
    ${virtualList(posts, (p) => p.id, (p) => html`
      <article style="padding:1rem;border-bottom:1px solid …">
        <p>${p.body}</p>   <!-- height varies: 1–6 sentences -->
      </article>
    `)}
  </div>
`

2 000 variable-height posts inside a bordered panel

import { html, atom, renderApp } from 'mates';
import { virtualList } from 'mates-virtual';
import { Avatar } from 'mates-ui';
import { ChatBubbleIcon, FavoriteIcon } from 'mates-icons';

const WORDS = "lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt labore dolore magna".split(" ");
const NAMES = ["Alice Johnson", "Bob Smith", "Carol Davis", "David Brown", "Eve Wilson", "Frank Moore"];
const TAGS = ["design", "engineering", "product", "ux", "infra"];

function randInt(seed, max) { return ((seed * 16807) % 2147483647) % max; }

const POSTS = Array.from({ length: 200 }, (_, i) => {
const nameIdx = randInt(i * 3 + 1, NAMES.length);
const sentLen = 1 + randInt(i * 7 + 2, 5);
const body = Array.from({ length: sentLen }, (_, s) => {
const wc = 8 + randInt(i * 13 + s, 10);
return Array.from({ length: wc }, (_, w) => WORDS[randInt(i + s + w, WORDS.length)]).join(" ") + ".";
}).join(" ");
const initials = NAMES[nameIdx].split(" ").map((n) => n[0]).join("");
return { id: i, author: { name: NAMES[nameIdx], initials, role: "Engineer" }, body, likes: randInt(i * 5, 120), comments: randInt(i * 11, 40), ts: ["2m ago", "1h ago", "yesterday"][i % 3], tags: randInt(i, 3) > 0 ? [TAGS[randInt(i * 2, TAGS.length)]] : [] };
});

const App = () => {
const posts = atom(POSTS);
return () => html`
<div style="border:0.09rem solid var(--md-color-border);border-radius:var(--md-radius-sm);overflow:hidden;max-width:40rem;width:100%;">
<div style="padding:var(--md-space-3) var(--md-space-4);border-bottom:0.09rem solid var(--md-color-border);background:var(--md-color-surface-raised);font-size:var(--md-text-sm);font-weight:600;">
Feed (${posts().length} posts)
</div>
<div style="height:480px;overflow:auto;">
${virtualList(posts(), (p) => p.id, (p) => html`
<article style="padding:var(--md-space-4);border-bottom:0.06rem solid var(--md-color-border);display:flex;gap:var(--md-space-3);">
${Avatar({ initials: p.author.initials, size: "sm" })}
<div style="flex:1;min-width:0;">
<div style="display:flex;gap:var(--md-space-2);margin-bottom:var(--md-space-1);flex-wrap:wrap;">
<span style="font-size:var(--md-text-sm);font-weight:600;">${p.author.name}</span>