Guides

Styling

In meno-astro, a node's static styling is a literal utility class="…" string — Meno's own Tailwind-looking engine (named scales bind to your theme.css tokens, brackets like p-[13px] for off-scale values). This class string is the canonical form: it parses straight back to the node's class attribute and round-trips losslessly through every visual save. style({…}) is reserved for values a static class can't express — prop-bound and {{template}} values, and component-root styling that merges an instance class.

<div class="flex gap-3 p-6">
  <slot />
</div>

This is the rule that makes the format work both ways: the class string is itself the structured, canonical data, so the editor parses it straight back into the visual model. A utility class string round-trips losslessly — it is exactly what a visual save writes back to disk. Reach for style({…}) only for the dynamic values a static class can't express.

The class string

A class string is a space-separated list of utilities. Layout, spacing, color, and type all read as short tokens, and responsive overrides ride desktop-first max-lg: (tablet) and max-sm: (mobile) prefixes:

<section class="flex flex-row gap-8 py-[92px] bg-(--bg) max-w-[1280px] mx-auto max-lg:flex-col max-lg:gap-6 max-lg:py-12 max-sm:py-8 max-sm:gap-4">
  <slot />
</section>

Unprefixed utilities are your desktop base and apply at every width. Add a max-lg: (tablet) or max-sm: (mobile) variant only for the utilities you want to change at that size — leave the rest unprefixed, and they keep inheriting the base value.

Utilities and values

Each utility is a short token — a property abbreviation plus a value from your scale: flex, gap-4, p-6, grid-cols-3, font-medium. A named scale (like rounded-lg) resolves to the matching theme.css token; for anything off-scale, drop the raw value in brackets (p-[13px]).

Bracket values take any raw CSS — length, weight, or a var(--token):

<div class="text-[18px] font-[500] leading-[1.5] rounded-lg border border-(--border) text-center">

For the full catalog of supported properties and their values, see Style Properties.

Colors: always var(--name)

Colors are referenced as CSS variables — var(--text), var(--bg), var(--primary) — never raw hex. The names come from your project's src/styles/theme.css, which defines the default palette on :root and each extra theme in a [theme="dark"] block, so the same var(--name) resolves correctly under light or dark mode. From this project's theme.css:

/* Colors: dark */
:root {
  --text: #f5f5f5;
  --bg: #000000;
  --border: #404040;
  --primary: #7c84f8;
}

/* Colors: light */
[theme="light"] {
  --text: #0a0a0a;
  --bg: #ffffff;
  --border: #bfbfbf;
  --primary: #7c84f8;
}

Use them by name in any color utility:

<p class="text-(--text) bg-(--bg)">

Because the value is a variable, a text-colored element automatically flips between #f5f5f5 and #0a0a0a when the active theme changes. A raw hex value (#7c84f8) would lock to one theme and break that behavior, so always go through var(--name).

Design tokens in theme.css

Beyond colors, reusable design tokens — fonts, spacing scales, and similar — live in the same theme.css as plain custom properties on :root. A token's --name is its whole identity — there is no separate label, type, or group field. This project defines a font-family token:

/* Font Family */
:root {
  --ff: Inter;
}

Reference a token as var(--name) inside a bracket value anywhere a value is expected:

<h1 class="[font-family:var(--ff)] text-[67px]">

Defining typography and spacing as tokens keeps them consistent across the project and lets you retune the whole site by editing theme.css once.

Responsive breakpoints

Meno is desktop-first. Unprefixed utilities apply at every width; max-lg: and max-sm: variants override them at and below their breakpoint. This project's breakpoints (from project.config.json) are:

Breakpoint

Width

(no prefix)

applies everywhere

max-lg:

1024px and below

max-sm:

540px and below

A utility you do not override keeps its base value. In the example below, the grid collapses to one column on tablet and the gap tightens on mobile, while bg-(--bg-light) and rounded-xl stay constant because they carry no max-lg: or max-sm: variant:

<div class="grid grid-cols-3 gap-8 bg-(--bg-light) rounded-xl max-lg:grid-cols-1 max-sm:gap-4">

Only add a max-lg: or max-sm: variant for the specific utilities you want to change at that size. Breakpoint widths are configurable per project in project.config.json under breakpoints. For previewing and editing breakpoints visually, see the Styling in the Editor guide.

Prop-bound styles (_mapping)

Inside a component, a style value can be driven by one of the component's props instead of being a fixed literal. Replace the value with a mapping object{ _mapping: true, prop, values } — directly inside the style() object. When a mapping is losslessly convertible, Meno emits it instead as a variants(__props, table) class on the element; the two forms are equivalent. Each instance of the component then resolves the value from the prop it was given.

From the real Button.astro, a margin toggled by a boolean prop:

<Link class={style({
  base: {
    display: "flex",
    gap: "12px",
    alignItems: "center",
    marginTop: {
      _mapping: true,
      prop: "isMarginTop",
      values: { true: "40px", false: "0" }
    }
  }
})}>

And from Heading.astro, several mappings keyed off size and variant — including a default fallback used when the prop's value has no explicit entry:

<Tag_0 class={style({
  base: {
    fontSize: {
      _mapping: true,
      prop: "size",
      values: { "1": "67px", "2": "56px", "3": "40px", default: "95px" }
    },
    color: {
      _mapping: true,
      prop: "variant",
      values: { default: "var(--text)", light: "var(--text-light)" }
    }
  }
})}>{text}</Tag_0>

The mapping object has three keys:

Key

Meaning

_mapping

Always true — marks the value as a prop binding

prop

Name of the component prop to read (declared in resolveProps)

values

Map of prop value to CSS value (with an optional default)

The prop must be one of the names declared in the component's resolveProps(Astro, {…}) argument. See Building Components for how props are defined and Component Props for the full prop reference.

The style() second argument

style() takes an optional second argument carrying editor metadata — it does not add CSS to the element directly. Its keys:

Key

Purpose

interactive

Hover, focus, and JS-state styles (pseudo-selectors and ancestor/child rules)

label

A human-readable name for the node in the editor's structure tree

genClass

Whether the element gets a generated class

From Heading.astro, a hover style plus a node label:

<Tag_0 class={style({
  base: { fontWeight: "500" },
  tablet: {},
  mobile: {}
}, {
  interactive: [
    { name: "onHover", postfix: ":hover", style: { base: { fontSize: "100px" } } }
  ],
  label: "text"
})}>{text}</Tag_0>

The interactive array carries the state rules a class variant can't express — ancestor/context and compound selectors. Simple hover, focus, and active states are hover:/focus:/active: class variants instead. That is a topic of its own; see Interactive Styles for the full pattern.


Everything above is what the visual Style panel reads and writes. Edit styles on the canvas or hand-edit the class string in the .astro file — both resolve to the same utilities, and the changes stay in sync. For doing this work visually, continue to Styling in the Editor.

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

Product

Resources

Comparisons

© 2026 Meno. All rights reserved.