Lists
A list repeats a piece of markup once for every item in a data source. It is how you build blog listings, team grids, product catalogs, navigation menus, and any other layout where the same structure recurs with different data.
Meno has two kinds of lists, and both are plain JavaScript .map() in the markup:
A prop list iterates over an array passed in as a component prop, using the
list()helper.A collection list iterates over items from a CMS collection, fetched in the frontmatter with
getCollectionList().
The difference is only where the data comes from. A prop list takes data the caller hands the component; a collection list queries the CMS directly. Once you have the array, the body markup is the same shape in both cases.
Prop lists
A prop list wraps an array prop in list() and maps over the result. Use it inside a reusable component that receives its data through props rather than querying the CMS itself.
---
import { list, style } from 'meno-astro';
const { title, items, class: className } = resolveProps(Astro, {
title: { type: "string", default: "Features" },
items: { type: "list", default: [] }
});
---
<section class={style({ base: { display: "flex", flexDirection: "column", gap: "16px" } })}>
<h2>{title}</h2>
{ list(items, { limit: 6 }).map((item, itemIndex) => (
<div class={style({ base: { padding: "20px" } })}>
<strong>{item.title}</strong>
<span>{item.description}</span>
</div>
)) }
</section>A few rules govern the loop:
The source is the array prop (
items). You pass the bare identifier tolist().The loop variable defaults to
item, and the index variable is always<loopVar>Index— hereitemIndex. If you name the loop variablefeature, the index becomesfeatureIndex.limitandoffsetgo in the options object:list(items, { limit: 6, offset: 2 }). Omit the object entirely when you do not need them:list(items).Inside the loop, reference each element's fields with
{item.title}— a plain template expression. Prop-list values are not CMS data, so they are not wrapped ini18n().
To match templates that already use a specific name, name the loop variable to suit:
{ list(items).map((feature, featureIndex) => (
<li>{feature.title}</li>
)) }The caller passes the array as a normal JSX prop:
<ListSection title="Our Features" items={[
{ title: "Fast", description: "Built for speed." },
{ title: "Simple", description: "Easy to understand." }
]} />Collection lists
A collection list pulls items from a CMS collection. You fetch the items once in the frontmatter with getCollectionList(), assign the result to a const, then .map() over it in the body.
---
import { getCollection } from 'astro:content';
import { getCollectionList, i18n } from 'meno-astro';
import { BaseLayout } from 'meno-astro/components';
import BlogCard from '../components/ui/BlogCard.astro';
const meta = { title: "Blog", description: "Latest articles and updates" };
const postsList = await getCollectionList("posts", { sort: { field: "publishedAt", order: "desc" }, limit: 10 }, Astro, getCollection);
---
<BaseLayout meta={meta}>
<main>
{postsList.map((post, postIndex) => (
<BlogCard href={`/blog/${i18n(post.slug)}`} title={i18n(post.title)} excerpt={i18n(post.excerpt)} />
))}
</main>
</BaseLayout>The pieces:
getCollectionList("posts", { … }, Astro, getCollection)is hoisted to a frontmatterconst. The conventional binding name is<collection>List(herepostsList). The first argument is the collection name, the second is the query options, and the last two —AstroandgetCollection(imported fromastro:content) — let the helper resolve the current route and locale and read the collection at build time. Pass them exactly as shown.The loop variable defaults to the singular of the source —
postsgivespost— and the index ispostIndex. Write your body templates to match that name:{i18n(post.title)}, not{i18n(item.title)}.Each item is raw CMS entry data, so an i18n field interpolated bare would render as
[object Object]. Wrap every field binding in thei18n()resolver —{i18n(post.title)}.i18n()returns plain values unchanged, so the wrap is always safe.
Referencing fields in the loop
Inside a collection loop, read fields off the loop variable through i18n(). From this site's own docs template:
{queryList(docsList, { filter: { field: "category", operator: "eq", value: category._id }, sort: { field: "order", order: "asc" } }).map((doc, docIndex) => (
<Link href={i18n(doc._url)}>{i18n(doc.title)}</Link>
))}Every item carries the schema fields you defined in the collection (doc.title, doc.slug, …) plus a few generated ones — _id, _url, _createdAt. See CMS for how collections and their fields are defined, and Template expressions for the full rules on bindings and i18n() wrapping.
Query options
The options object on getCollectionList() shapes the result set. Pass any combination:
Option | Type | Description |
|---|---|---|
| object or array | Keep only items matching a condition (or all of several) |
| object or array | Order items by a field |
| number | Cap the number of items returned |
| number | Skip the first N items |
| string or array | Restrict to specific item IDs (or a reference field) |
A filter condition is { field, operator, value }, where operator is one of eq, neq, gt, gte, lt, lte, contains, or in (defaulting to eq). A sort is { field, order } with order being "asc" or "desc".
const featuredList = await getCollectionList("posts", {
filter: { field: "featured", operator: "eq", value: true },
sort: { field: "publishedAt", order: "desc" },
limit: 3
}, Astro, getCollection);You can also restrict to specific items by ID — useful for resolving a reference field, as this site's docs template does when it looks up a doc's category:
const categoryList = await getCollectionList("docs-categories", { items: cms.category }, Astro, getCollection);For interactive, client-side filtering and search over a rendered list (facets, fuzzy search, URL-synced filter state), see Filtering and search and the Meno filter API.
Nested and filtered sublists
You often need a list inside a list — categories on the outside, the items belonging to each category on the inside. Fetch the parent and the full child set once in the frontmatter, then narrow the child set per parent with queryList(). queryList() takes an already-fetched array and the same filter/sort/limit/offset options as getCollectionList(), so you re-filter in memory without a second query.
This site's documentation sidebar nests docs under their categories exactly this way:
---
import { getCollection } from 'astro:content';
import { getCollectionList, i18n, queryList } from 'meno-astro';
const docsList = await getCollectionList("docs", {}, Astro, getCollection);
const categoriesList = await getCollectionList("docs-categories", { sort: { field: "order", order: "asc" } }, Astro, getCollection);
---
{categoriesList.map((category, categoryIndex) => (
<div>
<span>{i18n(category.name)}</span>
{queryList(docsList, { filter: { field: "category", operator: "eq", value: category._id }, sort: { field: "order", order: "asc" } }).map((doc, docIndex) => (
<Link href={i18n(doc._url)}>{i18n(doc.title)}</Link>
))}
</div>
))}The outer loop binds category; the inner queryList() filters docsList down to the docs whose category field equals the current category._id. Both loop variables are in scope inside the inner loop, so you can read {i18n(category.name)} and {i18n(doc.title)} side by side.
Loop helpers and conditionals
Each .map() callback receives the index as its second argument — (item, itemIndex), (post, postIndex). Use it for numbering, alternating styles, or first/last checks:
{postsList.map((post, postIndex) => (
<li>{`${postIndex + 1}. `}{i18n(post.title)}</li>
))}To render an item only when a condition holds, wrap its markup in a {cond && ( … )} guard — the same conditional form used everywhere in the dialect:
{postsList.map((post, postIndex) => (
post.featured && (
<article>{i18n(post.title)}</article>
)
))}You can also condition on the index — for example, to show a divider before every item except the first:
{postsList.map((post, postIndex) => (
<div>
{postIndex > 0 && (
<hr />
)}
{i18n(post.title)}
</div>
))}See Template expressions for the complete conditional grammar, and Node types for the elements, components, and links you place inside a loop's body.
Reach for a prop list when a reusable component should render whatever array its caller passes, and a collection list when a page or template should query the CMS directly. Both are just .map() over an array — list() for props, getCollectionList() for collections, queryList() to re-slice an already-fetched set — so once you have the array, the markup inside the loop is the same.