Reference

Node Types

Every page and component in a Meno project is a tree of nodes, and each node has a type that determines how it renders as .astro markup. Whether you build visually in the Meno editor or hand-edit the files, the same node types map to the same meno-astro dialect. This page is the catalog: for each type, what it looks like in markup and the rules that keep it round-tripping.

There are seven node types — node, component, slot, link, embed, list, and locale-list — plus dynamic tags and conditionals that wrap any node. Nothing is ever silently dropped: a type the codec does not recognize emits as a visible {/* meno:unknown */} comment rather than disappearing.

For prop values and templates that appear inside these nodes, see Component Props and Template Expressions.

node — HTML element

The most common node. It renders as a standard HTML element whose styles live in a class={style({…})} attribute, followed by any HTML attributes, then children.

<div class={style({ base: { display: "flex", gap: "12px" } })}>
  <span class={style({ base: { color: "var(--text)" } })}>{text}</span>
</div>

Styles always go inside style({...}), never a raw class="..." string. The argument is a Meno StyleObject — a flat object, or { base, tablet, mobile } for responsive breakpoints. Prop-bound values stay inline as { _mapping: true, prop, values }.

HTML attributes follow the class. A plain string uses the bare form; numbers and booleans use braces; templated strings become expressions:

<img class={style({ base: { objectFit: "cover" } })} src="/images/hero.webp" alt="" width={1200} height={600} />

Void elements (img, br, input, …) self-close and ignore children. Text children are emitted raw when they stand alone (<h1>Hello</h1>) and as {"…"} expressions when they sit beside sibling nodes, so adjacent text stays distinct.

Editor metadata — interactiveStyles, label, generateElementClass — rides along in an optional second style() argument, renamed to interactive, label, and genClass:

<button class={style({ base: { color: "var(--text)" } }, {
    interactive: [{ name: "onHover", postfix: ":hover", style: { base: { color: "var(--accent)" } } }]
  })}>{text}</button>

component — component instance

An instance of a reusable component renders as a Capitalized JSX tag with its props as attributes. The component name needs a matching local import in the frontmatter (import Heading from '../components/Heading.astro' on a page, or './Heading.astro' from another component).

<Heading size={1} text={i18n({ _i18n: true, en: "About", pl: "O nas" })} align="center" />
<Button isMarginTop={true} text="Services" link={{ href: "/services" }} />

Each value type has a fixed encoding:

  • string → text="Hi"

  • number → size={1}

  • boolean → isMarginTop={true}

  • object / link → link={{ href: "/services", target: "_blank" }}

  • i18n value → text={i18n({ _i18n: true, en: "…", pl: "…" })}

The component's own props are declared once in its resolveProps(Astro, {…}) block — that argument is the single source of truth, not a separate interface Props. See Component Props for the full list of prop types.

slot — child insertion point

A slot marks where a component instance's children are placed. It renders as a self-closing <slot />, or with fallback content when the node carries default children.

<slot />
<slot><p>Default content</p></slot>

A component with no structure of its own emits a bare <slot /> as its entire body.

link — object link

A link renders through the runtime Link component, which localizes internal hrefs and applies the link reset. Imported from meno-astro/components.

<Link href="/pricing">Pricing</Link>

The href resolves like an attribute. A plain string stays a string; an i18n href wraps in i18n({…}); a prop-bound href (a LinkMapping) wraps in href({…}):

<Link href={i18n({ _i18n: true, en: "/about", pl: "/o-nas" })}>About</Link>
<Link href={href({ _mapping: true, prop: "link" })} class={style({ base: { display: "flex" } })}>{text}</Link>

embed — raw HTML / SVG

An embed passes raw HTML or SVG through untouched, via the runtime Embed component. A single-line payload inlines as a backtick template literal:

<Embed html={`<svg viewBox="0 0 24 24"><path d="M5 12h14" /></svg>`} />

A multi-line payload is hoisted to a frontmatter const __embedN so the verbatim HTML is never re-indented, then referenced by name:

---
const __embed0 = `<svg width="515" height="84">
<path d="…" fill="#74B0FF" />
</svg>`;
---
<Embed html={__embed0} />

When html is bound to a CMS field, it renders as <Embed html={i18n(cms.field)} />.

list — repeating items

A list repeats its children once per item. The source is either a component prop or a CMS collection. See Lists for the full picture.

A prop list maps over a prop with the list() helper. The loop variable defaults to item, and the index variable is always <itemAs>Index. limit and offset become the options object:

{ list(items, { limit: 6 }).map((item, itemIndex) => (
  <div class={style({ base: { padding: "20px" } })}>
    <h3>{item.title}</h3>
    <p>{item.description}</p>
  </div>
)) }

A collection list hoists its query to a frontmatter const with getCollectionList(...), then maps over the result. The loop variable defaults to the singularized collection name ("products"product); set itemAs to choose another. Make the templates in the body match the loop variable.

---
const productsList = await getCollectionList("products", { limit: 10 }, Astro);
---
{productsList.map((product, productIndex) => (
  <div class={style({ base: { border: "1px solid var(--border)" } })}>
    <h3>{i18n(product.title)}</h3>
    <span>{`$${product.price}`}</span>
  </div>
))}

Collection items are raw entry data, so bare {{item.*}} chains emit wrapped in i18n(…) (identity for non-i18n values). Query options hoisted into getCollectionList include filter, sort, limit, offset, items, and excludeCurrentItem.

locale-list — language switcher

A locale-list renders the runtime LocaleList component, which generates a link for each configured locale. Boolean and display props are JSX attributes; each style sub-object is wrapped in style(…). See Internationalization.

<LocaleList displayType="nativeName" showFlag={true} style={style({ base: { display: "flex", gap: "8px" } })} itemStyle={style({ base: { textDecoration: "none" } })} activeItemStyle={style({ base: { fontWeight: "600" } })} />

displayType accepts "code", "name", or "nativeName". The style sub-props are style, itemStyle, activeItemStyle, separatorStyle, and flagStyle; any interactiveStyles, label, or generateElementClass collect into a single meta={{…}} attribute.

Dynamic tags

When a node's tag itself contains a template — for example a heading whose level comes from a size prop (h{{size}}) — it cannot be a literal JSX tag. The codec hoists it to a frontmatter const Tag_N and references that:

---
const Tag_0 = `h${size}`;
---
<Tag_0 class={style({ base: { fontWeight: "500" } })}>{text}</Tag_0>

The original tag string is restored on parse, so this round-trips like any other node.

Conditionals

Any node can be conditionally rendered. The condition wraps the node as {cond && ( … )}:

{visible && (
  <div class={style({ base: { padding: "16px" } })}>A</div>
)}

The condition takes three forms:

  • a template ({{visible}}) → {visible && ( … )}

  • a literal false{false && ( … )}

  • a prop-bound BooleanMapping{when({…}) && ( … )}

Summary

Node type

Markup

Notes

node

<tag class={style({…})}>…</tag>

Styles in style(); void tags self-close

component

<Name prop=… />

Capitalized tag, needs a local import

slot

<slot /> / <slot>fallback</slot>

default children become fallback

link

<Link href="/x">…</Link>

href={i18n({…})} / href={href({…})} for i18n / mapping

embed

<Embed html={} />

Multi-line hoists to const __embedN

list

{ list(src, {…}).map(…) } or getCollectionList(…) + {xList.map(…)}

Prop list vs collection list

locale-list

<LocaleList … />

Style sub-props wrapped in style(…)

Dynamic tag

const Tag_0 = \h${size}\` + <Tag_0>…</Tag_0>`

For templated tag names

Conditional

{cond && ( … )}

Wraps any node


Nothing is dropped on the way through the codec. A node type it does not recognize emits as {/* meno:unknown "type" */}, so unsupported content stays visible in the source instead of vanishing on the next save.

See also Template Expressions for how {{…}} templates and i18n() wraps resolve, and Component Props for the prop types these nodes carry.

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

Product

Resources

Comparisons

© 2026 Meno. All rights reserved.