Reference

Component Props

A component's props are declared once, in the argument to resolveProps(Astro, {…}) in the component's frontmatter. That object is the single source of truth: it lists every prop the component accepts, each prop's type, and its default value. There is no separate interface Props block and no __meno_props — the one resolveProps argument is what the editor reads, what the Meno codec round-trips, and what the component destructures to use in its markup.

This is the same definition the old Meno JSON interface carried, expressed as a JavaScript object literal instead.

The resolveProps argument

Each entry in the argument is name: { type, default, … }. resolveProps merges the incoming Astro.props over each prop's default at runtime, then returns the resolved values — which you destructure on the next line.

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

const { text, size, class: className } = resolveProps(Astro, {
  text: { type: "string", default: "Heading" },
  size: { type: "number", default: 1 }
});

const __meno = { category: "ui" };
---
<h2 class={style({ base: { fontWeight: "500" } })}>{text}</h2>

Two rules always hold for the destructure:

  • Always keep class: className in the destructure. Every component instance can carry wrapper styles from its parent, and className is how those reach the component.

  • Always emit the resolveProps call, even when the component has no props of its own — write const { class: className } = resolveProps(Astro, {});.

The TypeScript types of the destructured locals are inferred from the definition, so you never hand-write them. A number def types its local as number, a boolean as boolean, a link as { href; target? }, and a select as a union of its options (a string fallback otherwise). Change a prop by editing its entry in the resolveProps argument; the names and types regenerate from it on save.

When a component applies styles, it captures the resolved props in a const __props = resolveProps(Astro, {…}) first, then destructures from __props, because style() needs __props to resolve prop-bound style values:

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

const __props = resolveProps(Astro, {
  text: { type: "string", default: "Resources" },
  isOpen: { type: "boolean", default: false }
});
const { text, isOpen, class: className } = __props;
---
<li class={style({ base: { listStyle: "none" } }, __props, { root: true })}>{text}</li>

See Building Components for the full anatomy of a component file.

Prop types

These are the valid prop types. Each entry is { type: "…", default: …, … }.

Type

Definition

Example

string

Single-line text

{ type: "string", default: "Hello" }

number

Numeric value

{ type: "number", default: 1 }

boolean

True/false toggle

{ type: "boolean", default: false }

select

Dropdown; needs options: [...]

{ type: "select", options: ["a", "b"], default: "a" }

link

URL with optional target

{ type: "link", default: { href: "/" } }

file

File path; restrict with accept

{ type: "file", accept: "image/*" }

rich-text

HTML content

{ type: "rich-text", default: "" }

**There is no image type.** For an image prop, use file with accept: "image/*".

string

A single line of text. Renders into markup as a {text} interpolation (or a {{template}} inside a string).

const { name, class: className } = resolveProps(Astro, {
  name: { type: "string", default: "select" }
});

number

A numeric value. The default and resolved local are real numbers, not strings.

const { size, class: className } = resolveProps(Astro, {
  size: { type: "number", default: 1 }
});

boolean

A true/false toggle. Useful for variants you switch with a conditional or a style _mapping.

const { isMarginTop, class: className } = resolveProps(Astro, {
  isMarginTop: { type: "boolean", default: false }
});

select

A dropdown limited to a fixed set of values. List the allowed values in options; the local's type is the union of those options. Most _mapping style bindings read a select prop.

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

link

A URL with an optional target. The value is an object { href, target? }. Bind it to a <Link>'s href to make the destination editable per instance.

const { link, class: className } = resolveProps(Astro, {
  link: { type: "link", default: { href: "#" } }
});

file

A path to an uploaded asset. The accept property restricts the file type with a MIME pattern — accept: "image/*" for images, accept: "application/pdf" for PDFs.

const { image, alt, class: className } = resolveProps(Astro, {
  image: { type: "file", accept: "image/*" },
  alt: { type: "string", default: "" }
});

rich-text

HTML content edited in the rich-text editor. Bind it into markup with <Fragment set:html={value} />, never a plain text interpolation — a rich-text value is structured, not a string.

const { question, answer, class: className } = resolveProps(Astro, {
  question: { type: "rich-text", default: "Question?" },
  answer: { type: "rich-text", default: "Answer goes here." }
});
<dd><Fragment set:html={answer} /></dd>

Rich-text props also back CMS rich-text fields — see CMS Fields for how richTextWithComponents renders embedded components.

Defaults

Give every prop a default: so the component renders standalone and previews sensibly in the editor. resolveProps layers the incoming Astro.props over the defaults, so any prop a parent doesn't pass falls back to its default. A string/number/boolean/select default is a literal of that type; a link default is { href, target? }; a file default is a path string (often ""); a rich-text default is an HTML string.

A prop may omit default (for example a file or boolean with no sensible fallback). The resolved local is then undefined until an instance supplies a value — guard it where you use it (src={image || undefined}).

Passing props to a component

When you place a component, its props are JSX attributes on the capitalized tag. The component needs a matching local import in the frontmatter (import Button from '../components/ui/Button.astro').

  • string → text="Hi" (use text={"a \"quoted\" value"} if it contains quotes or newlines)

  • number → size={1}

  • boolean → isMarginTop={true}

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

  • internationalized → text={i18n({ _i18n: true, en: "About", pl: "O nas" })}

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

Any localized value goes through i18n({ _i18n: true, … }) so the right locale resolves at render time. See Template Expressions for how {{expr}} model values map to {expr} markup.

Reserved name: children

Never declare a prop named children. It's reserved for the child nodes passed into the component — the content that fills the component's <slot />. If you need a text prop, name it text, content, or anything else. (resolveProps skips children in the destructure for this reason.)

A component with a <slot /> receives nested content as children; a component without one takes only its declared props. See Node Types for slots and the other node forms.


Props are the contract between a component and the pages that use it. Keep the resolveProps argument tidy and well-defaulted, and both the visual editor and hand-edits stay in sync.

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

Product

Resources

Comparisons

© 2026 Meno. All rights reserved.