Reference

meno-astro Runtime API

Every Meno project is a real Astro project, and the .astro files Meno emits import a small runtime from the meno-astro package. These helpers turn the editor's data — style objects, i18n values, prop definitions, collection lists — into HTML at build time. The meno() Astro integration (registered in astro.config.mjs) wires the runtime up: it generates the build-time CSS, maps your locales into Astro's i18n routing, and injects the locale middleware.

You rarely call these by hand — the editor emits them whenever you save, and they round-trip back into the visual model on the next parse. But when you [hand-edit .astro files](/docs/hand-editing) it helps to know what each symbol does and where it shows up. This page is the reference for that runtime surface.

A typical page header shows the shape of it:

---
import { getCollectionList, i18n, queryList, style } from 'meno-astro';
import { BaseLayout, Embed, Link } from 'meno-astro/components';
import Heading from '../components/ui/Heading.astro';
---
<BaseLayout meta={meta}>
  <Heading text={i18n(cms.title)} />
</BaseLayout>

Helpers come from the package root (meno-astro); the runtime components come from meno-astro/components. Your own components are normal local imports.

meno-astro helpers

These are imported from meno-astro. Each appears in emitted markup wherever its concept is used — style() on every styled element, i18n() on every localized or CMS-bound value, the list helpers wherever you loop, and so on.

Helper

Signature

What it does

style

style(styleObject, props?, meta?)

Resolves a Meno StyleObject (responsive base/tablet/mobile, prop mappings, interactive states) to a deterministic CSS class name. The matching CSS is generated at build time by the meno() integration.

i18n

i18n(value)

Resolves an { _i18n: true, … } value to the active locale's string; passes non-i18n values through unchanged. Also wraps CMS-data bindings like i18n(cms.title).

resolveProps

resolveProps(Astro, defs)

The single authoritative prop block for a component. Merges Astro.props over each definition's default and always supplies class.

list

list(source, { offset?, limit? })

Tolerant prop-list slicing. Returns [] for null/undefined, then applies offset and limit.

getCollectionList

getCollectionList(source, query?, Astro, getCollection)

Resolves a CMS collection list at build time into an array of items, ready to .map().

queryList

queryList(items, query)

In-memory filter / sort / limit over an already-fetched list.

href

href(linkValue, props?)

Resolves a structured or prop-mapped href to a URL string.

when

when(mapping, props?)

Resolves a BooleanMapping to a boolean, for use in an if condition.

embedHtml

embedHtml(value, props?)

Resolves a structured embed payload to an HTML string.

richTextWithComponents

richTextWithComponents(value, cmsComponents)

Renders a CMS rich-text field (a TipTap document) to HTML, including any components embedded in the text.

inlineStyle

inlineStyle(decls, props?)

Renders a component root's prop-bound styles as an inline style="…" attribute.

style

style() is how every styled element gets its class. The argument is a Meno StyleObject — a flat object, or a responsive { base, tablet, mobile } — and style() returns a class name. The actual CSS (utility rules, @media breakpoints, :hover and other interactive states) is generated at build time by the meno() integration, so the function only ever returns a string at render.

<div class={style({ base: { display: "flex", gap: "12px" }, mobile: { flexDirection: "column" } })}>

The optional second argument is the host component's resolved props — pass __props so prop-bound { _mapping: … } values resolve to a concrete value before CSS is generated. The third argument carries emit-time metadata: interactive rules (which become :hover / state CSS), a label, and root: true on a component's structure root so the instance class merges in. See Style Properties and Interactive Styles for the style object shape.

<button class={style({ base: { padding: "12px 22px" } }, __props, { root: true, label: "button" })}>

i18n

i18n() resolves a value for the page's active locale. Hand-written i18n values use the { _i18n: true, en, pl, … } shape; i18n() returns the right string for the current locale. Crucially, it is the identity function for non-i18n values — so it is safe to wrap any CMS-data binding, whether or not that field is localized.

<Heading text={i18n({ _i18n: true, en: "About", pl: "O nas" })} />
<span>{i18n(category.name)}</span>

The locale context is opened once per render by the injected locale middleware, so every i18n() call on the page (and in its components) sees the same locale. See Internationalization.

resolveProps

resolveProps(Astro, defs) is the one authoritative prop block in a component — there is no separate interface Props. The defs argument is exactly what the dialect parser reads to know a component's props; at runtime it merges the incoming Astro.props over each definition's default and always returns a class (defaulting to ""). The destructured locals' TypeScript types are inferred from defs.

---
import { resolveProps, style } from 'meno-astro';

const __props = resolveProps(Astro, {
  text: { type: "string", default: "Link" },
  variant: { type: "select", default: "primary", options: ["primary", "secondary"] }
});
const { text, variant, class: className } = __props;
---

Keep class: className in the destructure, and emit the call even for an empty prop set: const { class: className } = resolveProps(Astro, {});. See Component Props.

list

list() is the tolerant slicer behind a prop-bound list. It accepts a possibly-missing array and returns a safe array to map over, applying offset then limit.

{list(items, { limit: 6 }).map((item, itemIndex) => (
  <li>{item.title}</li>
))}

See Lists for the full looping grammar.

getCollectionList and queryList

getCollectionList() resolves a CMS collection into an array at build time. It is called in the frontmatter; the editor passes astro:content's getCollection as the last argument (the package never imports it itself). The result carries a synthesized _url and _id on each item.

const docsList = await getCollectionList("docs", {}, Astro, getCollection);

queryList() runs an in-memory filter / sort / limit over a list you have already fetched — useful for a nested loop that narrows an outer collection by a loop variable:

{queryList(docsList, { filter: { field: "category", operator: "eq", value: category._id } }).map((doc) => (
  <Link href={i18n(doc._url)}>{i18n(doc.title)}</Link>
))}

See Filtering and Search for query shapes.

href and when

href() resolves a structured href (a { href, target? } object or a prop mapping) to a URL string, against the host component's props. when() resolves a BooleanMapping to a boolean — this is what powers a conditional bound to a prop.

{when({ _mapping: true, prop: "icon", values: { arrow: true, none: false } }, __props) && (
  <Embed html={`<svg>…</svg>`} />
)}

embedHtml

embedHtml() resolves a structured embed payload to an HTML string against the host props. It backs an embed node whose HTML varies by prop. (A static embed renders its HTML directly through the <Embed> component instead.)

richTextWithComponents

richTextWithComponents(value, cmsComponents) renders a CMS rich-text field — stored as a TipTap document — to HTML, including any project components embedded in that rich text. It is the emit target for a rich-text field bound as a text child, and is always used through set:html (it returns a promise, which set:html awaits natively):

<Fragment set:html={richTextWithComponents(cms.content, cmsComponents)} />

The cmsComponents argument is the generated registry src/cmsComponents.ts (see below). Never interpolate a rich-text field as plain text — {i18n(cms.content)} would print [object Object] because the value is a structured document, not a string. See CMS.

inlineStyle

inlineStyle(decls, props?) renders a component root's prop-bound style declarations as an inline style="…" attribute — used for style values that depend on a prop at render time and so cannot become a build-time class. It drops any declaration the instance's own class already overrides (so the instance utility class wins), and returns undefined when nothing remains, letting Astro omit the attribute.

<img style={inlineStyle({ "height": `${height}`, "object-fit": `${objectFit}` }, __props)} />

meno-astro/components

These are the runtime Astro components emitted markup imports from meno-astro/components. They are real components — Capitalized tags with a matching import — that you compose like any node type.

Component

Role

BaseLayout

The page shell. Renders <html lang>, the head meta (title / description resolved through i18n()), the <body> slot, and drains the page's collected CSS.

Link

An <a> whose internal hrefs are localized to the active render locale.

Embed

A raw HTML / SVG injector that also normalizes rich-text values to HTML.

LocaleList

A locale switcher whose links point at the current page in each locale, slug-translated.

BaseLayout

BaseLayout wraps every page. It takes the frontmatter meta object and renders the document shell: the <html lang> for the active locale, the <head> (with meta.title and meta.description resolved through i18n(), since they may be i18n values), the canonical link and hreflang alternates, and <body> with your page tree slotted in. It also pulls in the build-time stylesheet so the classes style() returned have CSS.

<BaseLayout meta={meta}>
  <main>…</main>
</BaseLayout>

See Creating Pages for the page skeleton.

Link

Link renders an <a> and localizes internal hrefs to the active locale (so /about becomes /pl/o-nas on a Polish page). The href can be a plain string, a link object, or an i18n / mapped href.

<Link href="/about">About</Link>
<Link href={i18n(doc._url)}>{i18n(doc.title)}</Link>

Embed

Embed injects raw HTML or SVG via set:html. When bound to a CMS rich-text field, it normalizes the stored value (a TipTap document or { __richtext__, … } object) to HTML before injecting, and localizes any internal links inside it.

<Embed html={`<svg viewBox="0 0 24 24">…</svg>`} />
<Embed html={i18n(cms.body)} />

LocaleList

LocaleList renders one link per configured locale, each pointing at the current page in that locale with the slug translated through the project's slug map; the active locale is marked. Its display and style sub-props (displayType, showFlag, itemStyle, activeItemStyle, …) are passed as a single meta={{…}} object, with each style wrapped in style(…). See Internationalization.

Supporting files

A few generated files complete the runtime. You normally do not hand-edit these — they are scaffolded by the converter and kept in sync.

  • src/content.config.ts — the Astro content-collection config. One defineCollection per CMS collection, each using menoCmsLoader (reads src/content/<collection>/*.json, merging .draft.json sidecars in dev) and resolveCmsEntrySlug (resolves an i18n slug to its default-locale string for routing). The collection's real schema lives in its template page's meta.cms, not here — this file uses a permissive schema so astro build can resolve the collection.

  • src/cmsComponents.ts — the generated component registry passed to richTextWithComponents(). It is a constant import.meta.glob('./components/**/*.astro') keyed by component name, so components you create later join it automatically. Do not hand-edit it.

  • The meno() integration in astro.config.mjs — the default export of meno-astro/integration, added as integrations: [meno()]. At build time it generates the utility/interactive CSS, maps your project.config.json i18n into Astro's native i18n routing, and injects the locale middleware that opens the per-render locale context.

import { defineConfig } from 'astro/config';
import meno from 'meno-astro/integration';

export default defineConfig({
  integrations: [meno()],
});

One more constant worth knowing: meno-astro exports dialectVersion, the on-disk format version written into generated projects. It lets a project be migrated forward if the dialect format ever evolves — you will not reference it by hand, but you may see it in tooling.

Building the future of digital experiences, one website at a time

Product

Resources

Comparisons

© 2026 Meno. All rights reserved.