Guides

Styling

In meno-astro, every styled element carries its CSS in a style({…}) call on its class attribute — never a raw class="…" string. The argument is a Meno StyleObject: a plain object of camelCase CSS properties, optionally split across responsive breakpoints. The visual Style panel writes exactly this call, and a hand-edit round-trips through it unchanged.

<div class={style({ base: { display: "flex", gap: "12px", padding: "24px" } })}>
  <slot />
</div>

This is the single rule that makes the format work both ways: because styles are structured data inside style(), the editor can parse them back into the visual model. A raw Tailwind or CSS class string would not round-trip — it is dropped on the next visual save. Keep all styling inside style({…}).

The StyleObject

The argument to style() is either a flat object of properties or — more commonly — a responsive object with base, tablet, and mobile keys:

<section class={style({
  base: {
    display: "flex",
    flexDirection: "row",
    gap: "32px",
    padding: "92px 0",
    backgroundColor: "var(--bg)",
    maxWidth: "1280px",
    margin: "0 auto"
  },
  tablet: { flexDirection: "column", gap: "24px", padding: "48px 0" },
  mobile: { padding: "32px 0", gap: "16px" }
})}>
  <slot />
</section>

Only base is required. Add tablet or mobile only when you need to override something at that size — leave them out (or empty) otherwise. The editor commonly emits tablet: {} and mobile: {} as placeholders; both forms are equivalent and round-trip the same.

CSS property names and values

Property names are camelCase, exactly as you would write them in a JavaScript style object — backgroundColor, fontSize, borderRadius, flexDirection, gridTemplateColumns. Do not use kebab-case (background-color); it will not be recognized.

Every value is a string, including numeric lengths:

<div class={style({
  base: {
    fontSize: "18px",
    fontWeight: "500",
    lineHeight: "1.5",
    borderRadius: "8px",
    border: "1px solid var(--border)",
    textAlign: "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 colors.json, which defines a palette per theme so the same var(--name) resolves correctly under light or dark mode. From this project's colors.json:

{
  "default": "dark",
  "themes": {
    "dark": {
      "colors": {
        "text": "#f5f5f5",
        "bg": "#000000",
        "border": "#404040",
        "primary": "#7c84f8"
      }
    },
    "light": {
      "colors": {
        "text": "#0a0a0a",
        "bg": "#ffffff",
        "border": "#bfbfbf",
        "primary": "#7c84f8"
      }
    }
  }
}

Use them by name in any color property:

<p class={style({ base: { color: "var(--text)", backgroundColor: "var(--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 from variables.json

Beyond colors, reusable design tokens — fonts, spacing scales, and similar — live in variables.json and are exposed as CSS variables too. This project defines a font-family token:

{
  "variables": [
    {
      "name": "Layout",
      "prop_name": "fontFamily",
      "cssVar": "--ff",
      "value": "Inter",
      "type": "none",
      "group": "font-family"
    }
  ]
}

Reference a token by its cssVar anywhere a value is expected:

<h1 class={style({ base: { fontFamily: "var(--ff)", fontSize: "67px" } })}>

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

Responsive breakpoints

Meno is desktop-first. base styles apply at every width; tablet and mobile override base at and below their breakpoint. This project's breakpoints (from project.config.json) are:

Breakpoint

Width

base

applies everywhere

tablet

1024px and below

mobile

540px and below

A property you do not override is inherited from base. In the example below, the grid collapses to one column on tablet and the gap tightens on mobile, while backgroundColor and borderRadius stay constant because they are only set on base:

<div class={style({
  base: {
    display: "grid",
    gridTemplateColumns: "1fr 1fr 1fr",
    gap: "32px",
    backgroundColor: "var(--bg-light)",
    borderRadius: "12px"
  },
  tablet: { gridTemplateColumns: "1fr" },
  mobile: { gap: "16px" }
})}>

Only add a tablet or mobile entry for the specific properties 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 StyleObject. 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 is the home for every pseudo-selector and state-driven style — hover, focus, active, and JS-toggled classes. 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 style({…}) call in the .astro file — both produce the same StyleObject, 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.