# Vue

> How midcode runs a Vite + Vue app, marks the elements of every template, and what you can edit in a .vue file, from text and classes to the props of each component.

- Page: https://midcode.app/docs/frameworks/vue
- 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 a Vue app built with Vite in the `<template>` of its single-file components: text, classes, attributes, new elements, their order, and the props a component is given where it's used. Your `<script>`, your `<style>` and everything the template computes (`{{ }}`, `:bound` values, `v-if`) stay exactly as you wrote them. midcode adds to a `<script setup>` in two cases only: the import of a component you drag in from the Assets tab, and the `variant` prop of a component you give [variants](#variants-and-states). midcode 1.1.2 and earlier open a Vue project to check every breakpoint and leave comments, without editing.

A Vue app built with Vue CLI runs another way: see [Create React App, Vue CLI, Gatsby, Docusaurus](https://midcode.app/docs/frameworks/webpack.md). Nuxt has [its own page](https://midcode.app/docs/frameworks/nuxt.md), and so does Vue inside [Laravel](https://midcode.app/docs/frameworks/laravel.md).

## At a glance

| | Vue with Vite |
| --- | --- |
| Detected by | `vite` and `vue` in the dependencies of `package.json` |
| Runs with | Your project's own Vite, with your config and one plugin added |
| Elements are marked by | That plugin, in memory, while the dev server runs |
| Editing | Text, classes, attributes, tag; insert, move, duplicate, delete, wrap in a stack |
| Components | Instances and typed props. A component opens on its own canvas, with variants, states and variables |
| Pages | The home page is listed; any other route opens by typing its path |
| Styles | Your Tailwind 4 classes, or `mid:` utilities compiled to `midcode.css` |
| Tried with | Vite + Vue + Tailwind 4 |

## How midcode runs it

midcode takes a project for Vue with Vite when its `package.json` lists `vite` and `vue` and none of the frameworks it checks first (Next.js, React Router, Remix, Astro, SvelteKit, Nuxt). It then starts the Vite that's in your `node_modules`:

```bash
vite dev <your project> --config <midcode's config> --port 4310 --strictPort --host localhost
```

The port is the first free one from 4310. It runs with the Node your shell has, or with midcode's own when there is none. If your `dev` script gives Vite other options (`vite --mode staging`), midcode passes them along; the port, the host and the config are midcode's to say.

That config is a small file in midcode's own data folder (`~/Library/Application Support/midcode/injected/vite.config.mjs`), not in your project. It loads your `vite.config` with your project's Vite and puts one plugin first in the list. Your `vite.config`, your `package.json` and your lockfile aren't changed, and the plugin only applies to the dev server, never to a build.

As Vite loads each `.vue` file, the plugin adds a mark to every element of its `<template>` before Vue compiles it. This file:

```vue title="src/components/Hero.vue"
<template>
  <section class="hero">
    <h1 class="title">Build calmly</h1>
    <Card title="Oak" :count="3" />
  </section>
</template>
```

reaches Vue's compiler like this (the root's `data-mcu` left out):

```vue
<template>
  <section data-mc="src/components/Hero.vue:2:3" class="hero">
    <h1 data-mc="src/components/Hero.vue:3:5" class="title">Build calmly</h1>
    <Card data-mci="Card|src/components/Hero.vue:4:5" title="Oak" :count="3" />
  </section>
</template>
```

- `data-mc` is the file, line and column where the element is written. It's what the canvas edits by.
- `data-mci` goes on a component used in a template. Vue hands it to the component's root element, like any attribute the component doesn't declare.
- `data-mcu` goes on the elements at the top of each template, read from `$attrs`. It says where this component is used, also when the component has several roots.

Tags that draw nothing of their own get no instance mark: `<template>`, `<slot>`, `<component>`, `<transition>`, `<transition-group>`, `<keep-alive>`, `<teleport>`, `<suspense>`, `<router-view>`.

None of this is written to disk. Hot reload keeps working: when a file changes, Vue's plugin receives the marked version again.

If midcode's way of starting Vite doesn't come up (the process stops, or nothing answers in 45 seconds), midcode runs your own `dev` script instead and says so. The site shows, but without marks, so you can only view and comment. [Open a project](https://midcode.app/docs/start/open-a-project.md) covers that case.

## What you can edit

Click an element and the right panel shows where it's written, as `file:line`. The editor's own pages describe each tool: [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), [Layers](https://midcode.app/docs/editor/layers.md). What follows is how each one lands in a `.vue` file.

### Text

Double-click a text and type. The new words replace the old ones in the template:

```diff title="src/components/Hero.vue"
-    <h1 class="title">Build calmly</h1>
+    <h1 class="title">Build slowly</h1>
```

A text is edited there when everything inside its element is written text. If you type `{{`, it's written as an entity (`&#123;&#123;`), so Vue doesn't read it as an expression.

Text that comes from `{{ title }}` isn't in the template. midcode then looks for the exact string in the project's `.ts`, `.js`, `.json` and `.md` files (a data file, a locale) and edits it there when it can tell which one it is. `.vue` files aren't searched: a value written in the component's own `<script>` is edited in the code, and one given as a prop where the component is used is changed there, in the code or from the instance's **Props**. [Text, images and video](https://midcode.app/docs/editor/text-and-media.md) explains the search.

### Classes and attributes

The style panel writes into the static `class` attribute, and creates it when the element has none. A `:class` binding is an expression and is left alone:

```diff title="src/components/Hero.vue"
-    <h1 class="title" :class="{ dark }">Build calmly</h1>
+    <h1 class="title text-6xl" :class="{ dark }">Build calmly</h1>
```

If a class you're replacing isn't written in the template, midcode says it comes from a prop or a condition and writes nothing.

Attributes (a link's `href`, an image's `src` and `alt`, an input's settings, a `<select>` and its options) are read as written, including the ones Vue binds to code. A value is written the way Vue writes it:

| Value | Written as |
| --- | --- |
| A text | `href="/pricing"` |
| On | `disabled` |
| A number or other code | `:maxlength="40"` |

### Structure

Insert adds plain markup (what the panel holds is converted from JSX: `class`, not `className`). A component dragged from the Assets tab is written with its import, in the file's `<script setup>` (one is added at the top of the file when it has none), as a relative path with its extension: `import Card from './Card.vue'`. Dragging moves an element before, after or inside another one in the same file, and the comment right above it travels with it. Several elements can be deleted, moved or wrapped in a stack (`⇧A`) at once. `⌥`-drag leaves the element where it is and puts a copy beside or inside the one you drop it on, in the same file.

### Components and props

A component used in a template shows in Layers with its name and is selected as an instance. Its **Props** in the right panel are typed controls, read from what the component declares:

- `defineProps<Props>()`, with the defaults from `withDefaults(…, { … })` or from a destructuring.
- `defineProps({ title: String, count: { type: Number, default: 0 } })`. `String`, `Number` and `Boolean` choose the control, and `required: true` is respected.
- `defineProps(['title', 'image'])`: the names alone.
- The `props` of the object a plain `<script>` exports.

```vue title="src/components/Card.vue"
<script setup lang="ts">
interface Props {
  title: string
  count?: number
  featured?: boolean
}
const props = withDefaults(defineProps<Props>(), { count: 0, featured: false })
</script>
```

Changing a prop writes it where the component is used:

```diff title="src/components/Hero.vue"
-    <Card title="Oak" :count="3" />
+    <Card title="Walnut" :count="5" featured />
```

A value equal to the declared default removes the attribute. A new one goes after the last attribute, or before a `v-bind="…"` when there is one. `<card-item>` is read as `CardItem`, and `my-title` as the prop `myTitle`. A component used without an import (unplugin-vue-components) is looked for by name under `components/`, `app/components/`, `src/components/` and `layers/`.

### Variants and states

A `.vue` file is a component. The Assets tab lists it (pages, layouts and `App.vue` aside), 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:`), as in any component. A variant is a `variant` prop, declared the way the component already declares its props, with `:data-variant="variant"` and a `group/<name>` class on each root of the template:

| The component has | The prop is written as |
| --- | --- |
| `defineProps<Props>()` | A `variant?: 'primary' \| 'ghost'` member in the type, and its default in `withDefaults(…)` (put around `defineProps` when it isn't there) or in the destructuring |
| `defineProps({ … })` | `variant: { type: String, default: 'primary' }`, with the list of variants in a JSDoc `@type` above it |
| No props yet | One line: `withDefaults(defineProps<{ variant?: 'primary' \| 'ghost' }>(), { variant: 'primary' })` in TypeScript, the `defineProps({ … })` form in JavaScript. A `<script setup>` is added when the file has none |

Renaming or removing a variant follows the instances that set it (`variant="ghost"`, `:variant="'ghost'"`).

## Pages

midcode doesn't read Vue Router's routes, so the page menu in the top bar lists only the home page. To open another one, type its path there ("Search, or type a path and press Enter"). [Pages and navigation](https://midcode.app/docs/editor/pages.md) has the rest. midcode doesn't add pages to a Vue app: its routes are code, yours or your agent's to write.

## Site settings

The globe in the top bar opens [Site settings](https://midcode.app/docs/editor/site-settings.md). The title, the description, the language, search engines, the favicon and the social image are written as tags in the `<head>` of your `index.html`:

```diff title="index.html"
-    <title>Vite + Vue</title>
+    <title>Oak Studio</title>
+    <meta name="description" content="Furniture made to last." />
   </head>
```

Images are copied into `public/`. A value Vite fills in when it builds (`%VITE_APP_TITLE%`) shows as code and isn't written. There's one form for the whole app: a page has no `<head>` of its 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 (`class="title mid:text-[56px]"`) and keeps their plain CSS in `midcode.css`, next to the module your `index.html` loads. The first style edit creates the file and imports it there, as one undoable step:

```diff title="src/main.ts"
 import { createApp } from 'vue'
 import App from './App.vue'
+import './midcode.css'
```

Your `<style scoped>` blocks keep working and aren't edited. [Without Tailwind](https://midcode.app/docs/styling/without-tailwind.md) has the details.

## Limits

- A `<template lang="pug">` isn't read: its elements get no marks.
- Elements move within one file. midcode doesn't follow what a `v-for` gives its contents: an element that reads the loop's item can be dragged out of the loop, where that name no longer exists. `⌘Z` puts it back.
- An insert that needs an import is refused in a component written with the Options API (a plain `<script>` and no `<script setup>`), where an import alone wouldn't be enough: "This needs an import, which midcode can't add to Hero.vue yet. Add it in the code." Insert's interactive components are React and answer "Needs a React page"; icons go in as inline SVG.
- [variables](https://midcode.app/docs/editor/variables.md) are written as `{{ title }}`, `:src="image"` and a `:style="{ … }"` object. A prop the template or the script also reads in its own code isn't renamed or removed from the canvas.
- Variants aren't written for a component on the Options API or one that lists its props as `defineProps([...])` ("Variants for a component that declares its props this way are coming"), nor for one whose template or script 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) to a Vite + Vue app, for plain elements: a component or an element with a binding in it can't float.
- Tried: text, classes, attributes and structure in a Vite + Vue + Tailwind 4 app; props typed by an interface and by `defineProps({ … })`, read and written; a component with two roots. Removing, wrapping and moving several elements, and Site settings, were tried on Vite + Vue projects too. So was component mode, in TypeScript and in JavaScript: variants read, added, renamed and removed, and a style written in one variant's cell of the grid. A Vue app without Tailwind follows the same path as any Vite project, but wasn't tried with Vue itself.
