# Set up Moonarc

> Install Moonarc with one command, add a first component, match the setup to your project (Tailwind, ClientRouter, static or server, colours), connect your coding agent, and check the build.

Works with Astro 5, 6 and 7, static and server output, with or without `<ClientRouter />`, with or without Tailwind. Page: https://moonarc.dev/setup/

## 1. Install

`astro add` installs `moonarc` and `@moonarc/core` with your package manager, then adds the integration to your Astro config. It asks before installing and before editing the config; `--yes` answers both.

- npm: `npx astro add moonarc`
- pnpm: `pnpm astro add moonarc`
- yarn: `yarn astro add moonarc`
- bun: `bunx astro add moonarc`

What changes in `astro.config.mjs`:

```diff
  import { defineConfig } from 'astro/config';
+ import moonarc from 'moonarc';

  export default defineConfig({
+   integrations: [moonarc()],
  });
```

In `package.json`, `moonarc` and `@moonarc/core` join `dependencies`.

What the integration adds to every page:

1. The tokens: `base.css`. The easing, duration and colour custom properties every component reads, in `@layer theme` and inside `:where()`, so any rule of yours overrides them.
2. The gate: an inline script of 92 B raw. It sets `data-ma-js` on `<html>` before first paint and again after every ClientRouter swap. Entrance effects hide content only behind it, so a page whose JavaScript never runs still shows everything.
3. No components and no runtime. JavaScript arrives only with a component you import, and each component's page prints what it costs.

Without the integration: `npm install @moonarc/core`, then import the tokens, declare the layer order first in `<head>` and add the gate yourself:

```astro
---
// src/layouts/Layout.astro
import '@moonarc/core/styles/base.css';
import { gateScript } from '@moonarc/core';
---
<html lang="en">
  <head>
    <style is:inline>@layer theme, base, components, utilities;</style>
    <script is:inline set:html={gateScript} />
  </head>
  <body><slot /></body>
</html>
```

## 2. Add a component

Each component has its own import path, so one import never pulls another component's CSS or script.

### Headline: Reveal + RotatingText, 3.0 kB raw JS on the page

```astro
---
import Reveal from '@moonarc/core/Reveal';
import RotatingText from '@moonarc/core/RotatingText';
---
<Reveal>
  <h1>
    Build
    <RotatingText words={['faster', 'lighter', 'calmer']} fluid />
    Astro sites.
  </h1>
</Reveal>
```

Reveal is 1.3 kB raw plus the shared runtime, once per site. RotatingText is CSS: 0 B.

### Stats: CountUp, 3.0 kB raw JS on the page

```astro
---
import CountUp from '@moonarc/core/CountUp';
---
<p><CountUp to={102} /> primitives</p>
<p><CountUp to={67} /> of them at 0 B of JavaScript</p>
```

CountUp counts in CSS and has no script of its own. It starts when Reveal sees it, so the page ships Reveal and the runtime.

### Marquee: Marquee, 0 B JS on the page

```astro
---
import Marquee from '@moonarc/core/Marquee';
---
<Marquee label="Works with" duration={24}>
  <span>Astro 5</span> <span>Astro 6</span> <span>Astro 7</span>
  <span>static</span> <span>server</span> <span>ClientRouter</span>
  <span>Tailwind v4</span> <span>plain CSS</span>
</Marquee>
```

Two copies of the row and one CSS animation. It pauses on hover and stops under reduced motion.

## 3. Match your project

Every combination works without a required change. Optional steps are marked.

### Styling: How do you write CSS?

- **Plain CSS** (no change): Components ship their own CSS in cascade layers and read the tokens from `base.css`. Any rule of yours wins without `!important`.
- **Tailwind v4** (optional): Import the token layer after Tailwind, and the library's curves and durations become utilities: `ease-out-quint`, `duration-fast`, `ease-snap`.

  ```css
  @import 'tailwindcss';
  @import '@moonarc/core/styles';
  ```
  See [Motion tokens](https://moonarc.dev/docs/motion-tokens/).
- **Tailwind v3** (no change): Skip the token layer: it is written for v4's `@theme`. Read the tokens as custom properties instead.

  ```css
  .card {
    transition: translate var(--ma-duration) var(--ma-ease-out);
  }
  ```

### Navigation: Do you use Astro's client router?

- **Full page loads** (no change): Every component starts on page load and needs nothing else.
- **`<ClientRouter />`** (no change): Components rebind after every navigation and clean up before each swap. For a script of your own, use the runtime's `onMount` instead of an `astro:page-load` listener.

  ```ts
  import { onMount } from '@moonarc/core/runtime';
  
  onMount('[data-menu]', (menu, { signal }) => {
    menu.addEventListener('click', () => menu.toggleAttribute('data-open'), { signal });
  });
  ```
  See [GSAP and Lenis adapters](https://moonarc.dev/docs/view-transitions/).

### Output: What does astro build produce?

- **static** (no change): Checked in CI on Astro 5, 6 and 7 by building a real project and opening it in a browser.
- **server** (no change): Checked the same way with the Node adapter. `vite.ssr.noExternal` is not needed. If your setup externalises `.astro` files anyway, turn on the escape hatch.

  ```js
  integrations: [moonarc({ noExternal: true })],
  ```
  See [The integration](https://moonarc.dev/docs/integration/).

### Colours: Where do your colours come from?

- **Defaults** (no change): The primitives read `--ma-*` tokens with light and dark values. Override any of them in your own `:root`.
- **A shadcn theme** (no change): Pro sections read shadcn's variables (`--background`, `--primary`, `--card`, `--radius`) as they are, so a theme from `shadcn init` or tweakcn applies without mapping.
  See [Theming](https://moonarc.dev/docs/theming/).
- **One brand colour** (optional): `theme.css` derives a brand scale, a tinted neutral and an accent from one colour, in CSS at runtime.

  ```css
  @import '@moonarc/core/theme.css';
  
  :root {
    --ma-brand: oklch(0.543 0.097 235);
  }
  ```
  See [Theme builder](https://moonarc.dev/theme/).

## 4. Connect your coding agent

The MCP server runs on your machine over stdio and needs no account.

Claude Code:

```sh
# in your project
claude mcp add moonarc -- npx -y @moonarc/mcp
```

Cursor:

```jsonc
// .cursor/mcp.json
{
  "mcpServers": {
    "moonarc": { "command": "npx", "args": ["-y", "@moonarc/mcp"] }
  }
}
```

VS Code:

```jsonc
// .vscode/mcp.json
{
  "servers": {
    "moonarc": { "type": "stdio", "command": "npx", "args": ["-y", "@moonarc/mcp"] }
  }
}
```

Codex:

```toml
# ~/.codex/config.toml
[mcp_servers.moonarc]
command = "npx"
args = ["-y", "@moonarc/mcp"]
```

Tools:

- `search_motion(intent, max_js_bytes?, tier?, limit?)`: Components for an intent under a JavaScript budget in raw bytes. max_js_bytes: 0 returns only the ones with no script of their own. limit: how many, best first (10 unless set, at most 50).
- `get_component(name)`: Source, props and craft notes: easing, duration, the reduced-motion branch, ClientRouter behaviour.
- `get_install_command(names, method?)`: The exact command. The agent runs it; the server never writes files.
- `get_usage()`: Plan and quota. The free tier needs no account and has no limit.

A prompt to start with: "Add a strip of our customer logos under the hero. Use Moonarc through its MCP server, and only components that ship 0 B of JavaScript."

### Copy the source with the shadcn CLI

The CLI reads `components.json`, and an Astro project has none. `shadcn init` would create one and also add React and seven packages these components never import. Write the file yourself instead: `package.json` stays as it is.

`components.json`:

```json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "src/styles/global.css",
    "baseColor": "neutral",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "lib": "@/lib"
  },
  "registries": {
    "@moonarc": "https://moonarc.dev/r/{name}.json"
  }
}
```

`tsconfig.json`:

```json
{
  "extends": "astro/tsconfigs/strict",
  "include": [".astro/types.d.ts", "**/*"],
  "exclude": ["dist"],
  "compilerOptions": {
    "paths": { "@/*": ["./src/*"] }
  }
}
```

Then `npx shadcn@latest add @moonarc/reveal`.

Files land in `src/components/moonarc/` and `src/lib/moonarc/`, Pro files in a `pro/` folder inside each, and the stylesheets in `src/styles/`. They import one another through the `@/*` alias, so `tsconfig.json` needs the `paths` entry above. `tailwind.css` names the stylesheet the theme item writes its variables into.

## 5. Check the build

Build the headline example from step 2 with `npx astro build`, then `npx astro preview`. `dist/index.html`, shortened:

```html
<head>
  <link rel="stylesheet" href="/_astro/index.[hash].css">
  <script>(f=>addEventListener('astro:after-swap',f,f()))(()=>document.documentElement.dataset.maJs=1)</script>
</head>
<body>
  <div data-ma-reveal="true" class="ma-reveal">
    <h1>Build <span class="ma-rotate">…</span> Astro sites.</h1>
  </div>
  <script type="module">/* Reveal and the runtime */</script>
</body>
```

1. **The gate is in `<head>`.** One inline script of 92 B raw that sets `data-ma-js`. If it is missing, reveals show at once and never animate in.
2. **The only JavaScript is Reveal and the runtime.** 3.0 kB raw as measured in CI, gate included. RotatingText adds nothing. Astro inlines a script under 4 kB raw into the page (Vite's `build.assetsInlineLimit`), so a small site can have no `.js` file in `dist/_astro` at all.
3. **It moves, and respects reduced motion.** In the preview, the heading rises in and the word cycles. Turn on reduce motion in your operating system and reload: the heading fades in place and the words crossfade.

## 6. Troubleshooting

### Cannot resolve "@moonarc/core/…"

The components live in `@moonarc/core`. If only `moonarc` is in your `package.json`, pnpm and Yarn Plug'n'Play will not let your code import the other package. `astro add` installs both; after a manual install, add it:

`pnpm add @moonarc/core`

### Reveals are visible at once and never animate in

The `html[data-ma-js]` gate is missing. The integration adds it. After a manual install, put `<script is:inline set:html={gateScript} />` in your `<head>`, with `gateScript` imported from `@moonarc/core`. Without it every page still shows all its content; nothing hides, so nothing animates in.

### Things fade but never move

Reduced motion is on. Every component follows `prefers-reduced-motion: reduce`: Reveal keeps a short fade and drops the travel, RotatingText crossfades in place, Marquee stops. Check your operating system's reduce motion setting. On this site, the motion switch in the header does the same.

[Accessibility](https://moonarc.dev/docs/accessibility/)

### Animations stop after clicking a link

A script outside the library listens to `astro:page-load` once, or a GSAP or Lenis setup is not wrapped. Bind your own code with [the runtime](https://moonarc.dev/docs/runtime/) and wrap GSAP and Lenis with [the adapters](https://moonarc.dev/docs/view-transitions/).

### Styles are missing inside a partial

Astro drops `<head>` content, styles included, from pages with `export const partial = true`. The page that receives the partial has to import the component, or its stylesheet, itself.

### Tailwind v3: ease-out-quint does nothing

The token layer uses v4's `@theme`, which v3 does not read, so the utilities never exist. On v3, use the custom properties directly: `transition-timing-function: var(--ma-ease-out)`.

### Colours ignore my dark mode toggle

The tokens follow the system setting unless `<html>` says otherwise: `data-theme="dark"` or `class="dark"` forces dark, `data-theme="light"` or `class="light"` forces light. If your toggle writes a different attribute, set one of these as well. The library's [ThemeToggle](https://moonarc.dev/components/theme-toggle/) writes `data-theme`.

### ERR_UNKNOWN_FILE_EXTENSION ".astro"

Not expected on Astro 5, 6 or 7: CI builds a server project on each without it. Pass `moonarc({ noExternal: true })` and open an issue with your config.

Before you open an issue, run `npx astro info` and include its output: it lists your Astro, Vite and Node versions, package manager, output, adapter and integrations. Issues: https://github.com/azriPtr/moonarc/issues
