# Astro

> How midcode runs astro dev with one plugin added, what it edits in an .astro file, and how components, islands, pages and styles work.

- Page: https://midcode.app/docs/frameworks/astro
- From the midcode docs. Every page as Markdown: https://midcode.app/llms.txt
- Status: This ships with the next release of midcode. The version you can download today (1.1.2) doesn't have it yet.

midcode edits an Astro site in the markup of its `.astro` files, the part below the frontmatter: text, classes, attributes, new elements, their order, and the props a component is given where it's used. The frontmatter and whatever sits inside `{ }` stay exactly as you wrote them, except for what midcode adds there when you ask for it: the import of a component you drag in from the Assets tab, a stylesheet import in a site without Tailwind, and a `variant` prop when you give a component [variants](#variants-and-states). An island (React, Preact, Solid, Svelte, Vue) is edited in its own file. midcode 1.1.2 and earlier edit only an Astro site's JSX islands, and leave `.astro` files to view and comment.

## At a glance

| | Astro |
| --- | --- |
| Detected by | `astro` in the dependencies of `package.json` |
| Runs with | Your project's own `astro dev`, with your config and one Vite plugin added |
| Elements are marked by | That plugin, in memory, as each `.astro` file is loaded |
| Editing | Text, classes, attributes, tag; insert, move, duplicate, delete, wrap in a stack |
| Components | `.astro` components are recognised where they're used, with their props as typed controls, and open on their own canvas with variants, states and variables |
| Pages | The files of `src/pages`. New pages and removing them |
| Styles | Your Tailwind 4 classes, or `mid:` utilities with `midcode.css` imported in each layout |
| Tried with | Astro 7 |

## How midcode runs it

A project is Astro when `astro` is in its dependencies. midcode starts the Astro that's in your `node_modules`:

```bash
astro dev --root <your project> --config <midcode's config> --port 4310 --host localhost --ignore-lock
```

The port is the first free one from 4310, and the process runs with the Node your shell has, or with midcode's own when there is none. Astro's telemetry is turned off for that process.

The config is a small file in midcode's own data folder (`~/Library/Application Support/midcode/injected/astro.config.mjs`). It imports your `astro.config` and adds one plugin to its `vite.plugins`. Your config, your `package.json` and your lockfile aren't changed, and the plugin only applies to the dev server, never to a build.

Astro compiles an `.astro` file before any plugin gets to transform it, so midcode hands the file over already marked, as it's loaded. Take this component:

```astro title="src/components/Hero.astro"
---
import Card from './Card.astro'
const { title } = Astro.props
const woods = ['Oak', 'Walnut']
---
<section class="hero">
  <h1 class="title">{title}</h1>
  <p class="lead">Furniture made to order.</p>
  <ul>
    {woods.map((wood) => <li class="wood">{wood}</li>)}
  </ul>
  <Card title="Oak" price={9} />
</section>
```

What Astro compiles while midcode runs it has a mark on every element: `data-mc="src/components/Hero.astro:6:1"` on the `<section>` (the file, line and column where it's written), and `data-mci="Card|src/components/Hero.astro:12:3"` on the `<Card>`. The elements at the top of each `.astro` file also get a `data-mcu`, read from `Astro.props`, which says where that component is used. Nothing is written to disk.

`<html>`, `<head>` and what's inside it, `<script>` and `<style>` are never marked.

If the server doesn't come up this way (the process stops, or nothing answers in 45 seconds), midcode runs your own `dev` script instead and says so: the site shows, without marks, and you can only view and comment.

## What you can edit

The tools are the editor's own: [select, move and resize](https://midcode.app/docs/editor/select-move-resize.md), [text and media](https://midcode.app/docs/editor/text-and-media.md), [the style panel](https://midcode.app/docs/editor/styles.md), [Insert](https://midcode.app/docs/editor/insert.md). In the component above:

| Element | On the canvas |
| --- | --- |
| `<p class="lead">` | Everything: its text, its classes, its place |
| `<h1 class="title">` | Classes and attributes. Its text is `{title}`, which isn't written there |
| `<li class="wood">` | Classes and attributes. An expression draws it, so it can't be moved or removed |
| `<Card … />` | Its props, where it's used |

Several elements can be removed, moved or wrapped in a stack (`⇧A`) at once, and `⌥`-drag leaves the element where it is and puts a copy where you drop it. All of it stays within one file.

### Text, classes and attributes

A text is replaced in the markup when everything inside its element is written text:

```diff title="src/components/Hero.astro"
-  <p class="lead">Furniture made to order.</p>
+  <p class="lead">Furniture made to last.</p>
```

A `{` or `}` you type is written as an entity, so Astro doesn't read it as an expression. Text that comes from an expression is looked for as an exact string in the project's `.ts`, `.js`, `.json` and `.md` files and edited there when midcode can tell which one it is; otherwise it's edited in the code.

The style panel writes into a written `class="…"` attribute and creates it when there's none. A `class:list` directive is an expression and is left alone. An attribute's value is written as text (`href="/about"`), as a bare name when it's a switch (`disabled`), or in braces when it's a number or other code (`width={640}`).

### Elements an expression draws

An element written inside `{ }` (the `<li>` in the `.map` above, or what a condition returns) is one piece of that expression. You can restyle it, and every copy it draws changes with it. Moving or removing it would break the expression, so midcode refuses: "An expression draws this element ({…}). Move or remove it in the code."

### Components and props

An `.astro` component used in a page shows in Layers with its name and is selected as an instance. Its **Props** are typed controls, read from the `interface Props` (or `type Props`) in its frontmatter, with the defaults its `Astro.props` destructuring gives:

```astro title="src/components/Card.astro"
---
interface Props {
  title: string
  price?: number
  featured?: boolean
}
const { title, price = 0, featured = false } = Astro.props
---
```

A change is written where the component is used:

```diff title="src/components/Hero.astro"
-  <Card title="Oak" price={9} />
+  <Card title="Walnut" price={12} featured />
```

A value equal to the declared default removes the attribute. Astro's own pass-through components get no instance mark: `Fragment`, `ViewTransitions`, `ClientRouter`, `Content`, `Debug`, `Code`, `Prism`.

Drag a component from the Assets tab onto the page and midcode writes the instance with its import, in the frontmatter (one is added at the top of the file when it has none), as a relative path with its extension: `import Card from '../components/Card.astro'`.

### Variants and states

An `.astro` file outside your `pages` and `layouts` folders is a component. The Assets tab lists it, and it opens on its own canvas, where you add variants and states: see [Components](https://midcode.app/docs/editor/components.md).

A state is written as classes (`hover:`, `active:`, `focus-visible:`). A variant is a `variant` prop: a `variant?: 'primary' | 'ghost'` member in `interface Props`, its default in the frontmatter's destructuring (`const { variant = 'primary' } = Astro.props`), and `data-variant={variant}` with a `group/<name>` class on each element at the top of the markup. A component with no props yet gets the interface and the destructuring. Renaming or removing a variant follows the instances that set it (`variant="ghost"`).

### Islands

The same plugin marks the JSX of `.tsx` and `.jsx` files, `.svelte` files (read with your project's Svelte compiler) and `.vue` templates. An island is edited in its own file, like in a project of that framework: see [React with Vite](https://midcode.app/docs/frameworks/react-vite.md), [Svelte](https://midcode.app/docs/frameworks/svelte.md) and [Vue](https://midcode.app/docs/frameworks/vue.md).

## Pages

Pages are the files of `src/pages`: `.astro`, `.md`, `.mdx`, `.html`, `.tsx` and `.jsx`.

| File | Page |
| --- | --- |
| `src/pages/index.astro` | The home page |
| `src/pages/about.astro` | `/about` |
| `src/pages/blog/[slug].astro` | `/blog/[slug]`, a dynamic route |

For a dynamic route, the page menu in the top bar lists its real pages, found in the running site's sitemap and links ([Pages and navigation](https://midcode.app/docs/editor/pages.md)). Folders whose name starts with `_` or a dot aren't listed.

"New page" in the page menu writes `src/pages/<path>.astro` the way your home page is written, and "Remove" moves a page's file to the Trash. When the home page imports a layout from a `layouts` folder and wraps its content in it, the new page does the same:

```astro title="src/pages/about.astro"
---
import Layout from '../layouts/Layout.astro'
---

<Layout>
	<main class="mx-auto max-w-3xl px-6 py-24">
	  <h1 class="text-4xl font-semibold">About</h1>
	  <p class="mt-4">A new page, made in midcode.</p>
	</main>
</Layout>
```

If the home page gives its layout a `title`, the new page gets `title="About"`. Without a layout, the new page is a whole document that imports the stylesheets the home page imports in its frontmatter. The class names are written when Tailwind is in your `package.json`.

A page written in Markdown shows on the canvas, but Markdown carries no marks. Edit that content in the [CMS](https://midcode.app/docs/data/cms.md): a folder of `.md` or `.mdx` files with front matter is a collection there.

## Site settings

The globe in the top bar opens [Site settings](https://midcode.app/docs/editor/site-settings.md). In an Astro site the title, the description, the language, search engines, the favicon and the social image are tags in the `<head>` of a layout: the first file of `src/layouts`, by name, that closes a `<head>`, or `src/pages/index.astro` when no layout does. Images are copied into `public/`.

What the layout takes from its props (`<title>{title}</title>`) shows as code and is left to your code. There's one form for the whole site: pages have none of their own.

## Styles

With Tailwind 4 (a stylesheet with `@import "tailwindcss"` or an `@theme` block), the panel writes your own utilities: see [Tailwind CSS](https://midcode.app/docs/styling/tailwind.md).

Without it, midcode writes its own prefixed utilities (`mid:p-6`, `mid:md:flex`) and keeps their plain CSS in `src/midcode.css`. Astro has no single entry file, so the first style edit imports the stylesheet in the frontmatter of every layout in `src/layouts` (or of every page in `src/pages`, when the site has no layouts), as one undoable step:

```diff title="src/layouts/Layout.astro"
 ---
+import '../midcode.css'
 import Header from '../components/Header.astro'
 ---
```

A layout with no frontmatter gets one with that single line. [Without Tailwind](https://midcode.app/docs/styling/without-tailwind.md) explains what's in the file.

## Limits

- Insert's interactive components are React ("Needs a React page"), and icons go in as inline SVG.
- An element whose whole class is an expression (`class={classes}`) is styled in the code: the panel works on a written `class="…"`.
- A tag with a dash in its name (a custom element like `<my-widget>`) is read as a component and gets no mark.
- Elements move within one file.
- Pages are looked for in `src/pages`. With another `srcDir`, the list has the home page alone, and any path can be typed in the page menu.
- [variables](https://midcode.app/docs/editor/variables.md) are written as `{title}`, `src={image}` and a `style={{ … }}` object. A prop the frontmatter or the markup also reads in its own code isn't renamed or removed from the canvas.
- Variants aren't written for a component whose root is another component ("Its root is another component that doesn't pass its props on ({...props}), so data-variant wouldn't reach the page"), nor for one whose markup or frontmatter already reads `variant` ("This component's variants are written in code: edit them there or ask your agent").
- In the released version there's no free canvas. The next release brings [the free canvas](https://midcode.app/docs/editor/free-canvas.md), for plain elements: a component or an element with an expression in it can't float.
- Tried with Astro 7: editing `.astro` files, and a component used three times, once inside a `.map`. An Astro 7 site without Tailwind was tried too (a layout with no frontmatter gets one), and so were creating and removing a page, Site settings, and component mode (variants read, added, renamed and removed).
