Setup · about five minutes
Set up Moonarc in five minutes.
One command installs the packages and registers the integration. Then you import a component, build, and check what it costs. The steps also cover the settings that differ per project, your coding agent, and the fixes for common problems.
npx astro add moonarc✔ Resolved packages.Astro will run the following command:npm i moonarc @moonarc/core✔ Dependencies installed.Astro will make the following changes to your config file:+ import moonarc from 'moonarc';+ integrations: [moonarc()],success Added the following integration to your project:- moonarc
astro add log, shortened. It asks before installing and before editing your config; --yes answers both.- Astro 5, 6, 7
- static and server
- with or without ClientRouter
- with or without Tailwind
Install with one command.
astro add installs Moonarc's two packages with your package manager, then adds the integration to your Astro config.
npx astro add moonarcpnpm astro add moonarcyarn astro add moonarcbunx astro add moonarcWhat changes in your project
import { defineConfig } from 'astro/config';
+ import moonarc from 'moonarc';
export default defineConfig({
+ integrations: [moonarc()],
});In package.json, moonarc and @moonarc/core join dependencies. Nothing else in your project is touched.
What the integration adds to every page
- 1
The tokens:
base.cssThe easing, duration and colour custom properties every component reads, in
@layer themeand inside:where(), so any rule of yours overrides them. - 2
The gate: an inline script of 92 B raw
It sets
data-ma-json<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.
Install without the integration
Install the components package, then do by hand what the integration does: import the tokens once and add the gate to your layout's <head>.
npm install @moonarc/core---
// 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>Import a component and use it.
Each component has its own import path, so one import never pulls another component's CSS or script. The code on the left renders what is on the right.
---
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>Resultlive
Build faster, lighter, calmer Astro sites.
<Reveal> + <RotatingText> · 3.0 kB raw JS on the page
Reveal is 1.3 kB raw plus the shared runtime, once per site. RotatingText is CSS: 0 B.
---
import CountUp from '@moonarc/core/CountUp';
---
<p><CountUp to={102} /> primitives</p>
<p><CountUp to={67} /> of them at 0 B of JavaScript</p>Resultlive
102primitives
67of them at 0 B of JavaScript
<CountUp> · 3.0 kB raw JS on the page
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.
---
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>Resultlive
<Marquee> · 0 B JS on the page
Two copies of the row and one CSS animation. It pauses on hover and stops under reduced motion.
Four settings that differ per project.
Pick what describes your project. Every combination works without a required change; the panel shows the optional ones.
For your project
Required changes: none
- 01
Styling: Plain CSSno change
Components ship their own CSS in cascade layers and read the tokens from
base.css. Any rule of yours wins without!important.Styling: Tailwind v4optional
Import the token layer after Tailwind, and the library's curves and durations become utilities:
ease-out-quint,duration-fast,ease-snap.Motion tokens →src/styles/global.css @import 'tailwindcss'; @import '@moonarc/core/styles';Styling: Tailwind v3no change
Skip the token layer: it is written for v4's
@theme. Read the tokens as custom properties instead.any stylesheet .card { transition: translate var(--ma-duration) var(--ma-ease-out); } - 02
Navigation: Full page loadsno change
Every component starts on page load and needs nothing else.
Navigation: <ClientRouter />no change
Components rebind after every navigation and clean up before each swap. For a script of your own, use the runtime's
onMountinstead of anastro:page-loadlistener.GSAP and Lenis adapters →your script import { onMount } from '@moonarc/core/runtime'; onMount('[data-menu]', (menu, { signal }) => { menu.addEventListener('click', () => menu.toggleAttribute('data-open'), { signal }); }); - 03
Output: staticno change
Checked in CI on Astro 5, 6 and 7 by building a real project and opening it in a browser.
Output: serverno change
Checked the same way with the Node adapter.
vite.ssr.noExternalis not needed. If your setup externalises.astrofiles anyway, turn on the escape hatch.The integration →astro.config.mjs integrations: [moonarc({ noExternal: true })], - 04
Colours: Defaultsno change
The primitives read
--ma-*tokens with light and dark values. Override any of them in your own:root.Colours: A shadcn themeno change
Pro sections read shadcn's variables (
Theming →--background,--primary,--card,--radius) as they are, so a theme fromshadcn initor tweakcn applies without mapping.Colours: One brand colouroptional
theme.cssderives a brand scale, a tinted neutral and an accent from one colour, in CSS at runtime.Theme builder →src/styles/global.css @import '@moonarc/core/theme.css'; :root { --ma-brand: oklch(0.543 0.097 235); }
Connect your coding agent.
The MCP server runs on your machine over stdio and needs no account. Your agent can search the catalogue by intent and JavaScript budget, read how a component is built, and get the exact install command.
# in your project
claude mcp add moonarc -- npx -y @moonarc/mcp// .cursor/mcp.json
{
"mcpServers": {
"moonarc": { "command": "npx", "args": ["-y", "@moonarc/mcp"] }
}
}// .vscode/mcp.json
{
"servers": {
"moonarc": { "type": "stdio", "command": "npx", "args": ["-y", "@moonarc/mcp"] }
}
}# ~/.codex/config.toml
[mcp_servers.moonarc]
command = "npx"
args = ["-y", "@moonarc/mcp"]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.The agent calls search_motion with max_js_bytes: 0, gets Marquee and the other components with no script of their own, then asks for the install command.
What your agent can ask
- 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.
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.
{
"$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"
}
}{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"paths": { "@/*": ["./src/*"] }
}
}npx shadcn@latest add @moonarc/revealFiles 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.
Without MCP: /llms.txt is the whole catalogue in one file, and every component and docs page has a Markdown twin at its URL plus .md.
Check that it worked.
Build the headline example from step 02 and look at what Astro wrote. Three things show that the setup is right.
npx astro build
npx astro preview<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>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.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.jsfile indist/_astroat all.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.
Using the client router? Click to another page and back: the heading animates again. How that works
If something is off.
Each entry is a symptom, its cause and the fix.
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.
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 and wrap GSAP and Lenis with the adapters.
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 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.
Where to go from here.
- CatalogueAll 102 components 67 of them ship 0 B of JavaScript. Each page has the live component, its props and its measured cost.
- DocsThe runtime onMount and onPage: how your own scripts survive navigation the way the components do.
- ThemeTheme builder Pick a brand colour, preview real sections in light and dark, copy the CSS block.
- ProSections and effects Page sections composed from the primitives, and GPU effects, each with its JavaScript cost.