npx astro add moonarcpnpm astro add moonarcbunx astro add moonarcA code block with tabs (for example npm, pnpm, bun) and a copy button per panel.
A quickstart as numbered steps down a rail that fills as the reader moves through them, each number lighting as its step reaches the middle of the screen. A step has its words, its code in tabs (one per language or package manager, a copy button on each) and what the reader should see when it worked, as a line or as output, so a step can be checked before the next. A last line can point to what to read next. Scroll-driven where the browser has view timelines; the rail full and every number lit where it does not. Composes TracingBeam and CodeTabs; zero JavaScript of its own.
Quickstart
npm install sitenamepnpm add sitenamebun add sitenameCreate a key in the dashboard under Keys. It is shown once.
SITENAME_KEY=sk_test_••••You should see
The SDK reads SITENAME_KEY by itself; nothing else to configure.
import { Sitename } from 'sitename';
const mail = new Sitename();
await mail.emails.send({
from: 'hello@your-domain.com',
to: 'you@example.com',
subject: 'It works',
text: 'Sent from the quickstart.',
});curl https://api.sitename.example/v1/emails \
-H "Authorization: Bearer $SITENAME_KEY" \
-d to=you@example.com -d subject="It works"You should see
{
"id": "em_7Hq2kD9x",
"status": "queued"
}Next, hear back when it lands. Set up webhooks
Live, from the library itself, at full size: scroll, hover, navigate away and back. The demo is never gated.
Variants · each row writes the attribute the prop writes; the section's own CSS answers
JavaScript of its own
0 B
Uses CopyButton and the shared runtime (1.9 kB raw, once per site). With those included: 2.5 kB raw.
CSS 21.6 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.
Browser support
Chrome 115 · not Firefox · Safari 26; needs scroll-timeline. Elsewhere: the rail is drawn full and every number lit (Firefox).
The demo above is never gated; the source is. With a key in components.json (see account), the registry serves it like any other item:
npx shadcn@latest add @moonarc-pro/quickstart-steps---
import QuickstartSteps from '@/components/moonarc/pro/QuickstartSteps.astro';
---
<QuickstartSteps
id="qs"
eyebrow="Quickstart"
next={{ text: 'Next, send it from your app.', href: '/docs/sdks/', label: 'Pick an SDK' }}
steps={[
{
title: 'Install the SDK',
code: [
{ label: 'npm', code: 'npm install sitename' },
{ label: 'pnpm', code: 'pnpm add sitename' },
],
},
{
title: 'Send a request',
text: 'Your key is in the dashboard under Keys.',
code: [{ label: 'cURL', code: 'curl https://api.example.com/v1/ping -H "Authorization: Bearer $KEY"' }],
result: { code: '{ "ok": true }' },
},
]}
>
<span slot="headline">Send your first request in <em>two minutes</em></span>
</QuickstartSteps>Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: tracing-beam, code-tabs.
| Prop | Type | Default | Description |
|---|---|---|---|
steps | { title; text?; code?; result? }[] | none | The steps, in order. text is a paragraph or two (a blank line splits them), with `code` in backticks; code is CodeTabs' tabs ({ label, code, lang }); result is what the reader should see: { label?, text?, code?, lang? }, the code in JSON unless lang says otherwise. |
id | string | 'ma-qs' | Prefix of the code tabs' radio names; unique on the page. |
eyebrow | string | none | A short line above the heading. Slot "eyebrow" wins. |
headline | string | none | The heading. Slot "headline" wins and may hold an <em>. |
copy | string | none | A line under the heading. |
resultLabel | string | 'You should see' | The words over a step's result. |
next | { text: string; href: string; label: string } | none | A last line on the rail with a link: what to read next. |
level | 2 | 3 | 2 | Heading level of the heading; each step's title is one below. With no heading (steps inside a docs page), each step's title takes this level. |
tone | 'surface' | 'muted' | 'ink' | 'brand' | unset (paints nothing) | The section's ground; on ink and brand the rail takes the tone's emphasis colour. |
density | 'compact' | 'cozy' | 'roomy' | 'cozy' | Block padding ×0.8 / ×1 / ×1.25 through --ma-density. |
The section reads the theme contract (shadcn's variable names with the base tokens as fallback), so a pasted theme or one --ma-brand recolours it. Its class names are the override surface and stay stable; a named slot replaces the matching prop with your own markup.
.ma-qs.ma-qs__code.ma-qs__copy.ma-qs__eyebrow.ma-qs__head.ma-qs__headline.ma-qs__inner.ma-qs__next.ma-qs__num.ma-qs__result.ma-qs__rlabel.ma-qs__rtext.ma-qs__step.ma-qs__steps.ma-qs__text.ma-qs__title.ma-qs__trace.ma-trace__line
slot="eyebrow"slot="headline"
The rail is drawn full and every number lit.
ClientRouterCSS only; the copy buttons are CodeTabs', bound through the shared runtime.
Replaces: Quickstart pages in developer docs · Step lists with code blocks (Mintlify Steps, Starlight Steps). See the migration table.
npx astro add moonarcpnpm astro add moonarcbunx astro add moonarcA code block with tabs (for example npm, pnpm, bun) and a copy button per panel.
A line beside an article fills as it is read, with a dot at its head.
Every request takes your key as a bearer token. Sample API, made up for this demo.
Base URLhttps://api.sitename.example/v1
Send an email, then follow it until it lands.
POST/emails
Queues one email. It is sent within seconds; follow it with GET /emails/{id} or a webhook.
Body
fromstringFormat: email.required
An address on a domain you have verified.
tostringFormat: email.required
subjectstringAt most 998 characters.required
textstringoptional
The plain-text body.
tagsarray of objectsoptional
Labels you can filter the logs by.
namestringoptional
valuestringoptional
Responses
202422Request
curl -X POST "https://api.sitename.example/v1/emails" \
-H "Authorization: Bearer $SITENAME_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "hello@sitename.example",
"to": "ada@example.com",
"subject": "Your receipt",
"text": "string",
"tags": [
{
"name": "kind",
"value": "receipt"
}
]
}'const res = await fetch('https://api.sitename.example/v1/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SITENAME_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'hello@sitename.example',
to: 'ada@example.com',
subject: 'Your receipt',
text: 'string',
tags: [
{
name: 'kind',
value: 'receipt',
},
],
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://api.sitename.example/v1/emails",
headers={"Authorization": f"Bearer {os.environ['SITENAME_KEY']}"},
json={
"from": "hello@sitename.example",
"to": "ada@example.com",
"subject": "Your receipt",
"text": "string",
"tags": [
{
"name": "kind",
"value": "receipt",
},
],
},
)
print(res.json()){
"id": "em_7Hq2kD9x",
"status": "queued",
"created_at": "2026-10-09T08:14:22Z"
}{
"error": "domain_not_verified",
"message": "Verify sitename.example before sending from it."
}GET/emails/{id}
Path parameters
idstringrequired
The id the send returned.
Responses
200Request
curl "https://api.sitename.example/v1/emails/em_7Hq2kD9x" \
-H "Authorization: Bearer $SITENAME_KEY"const res = await fetch('https://api.sitename.example/v1/emails/em_7Hq2kD9x', {
headers: {
Authorization: `Bearer ${process.env.SITENAME_KEY}`,
},
});
const data = await res.json();import os
import requests
res = requests.get(
"https://api.sitename.example/v1/emails/em_7Hq2kD9x",
headers={"Authorization": f"Bearer {os.environ['SITENAME_KEY']}"},
)
print(res.json()){
"id": "em_7Hq2kD9x",
"status": "queued",
"created_at": "2026-01-01T00:00:00Z"
}The domains you send from, and whether their records check out.
GET/domains
Query parameters
limitinteger1 to 100.optional
How many to return.
Default20
Responses
200Request
curl "https://api.sitename.example/v1/domains" \
-H "Authorization: Bearer $SITENAME_KEY"const res = await fetch('https://api.sitename.example/v1/domains', {
headers: {
Authorization: `Bearer ${process.env.SITENAME_KEY}`,
},
});
const data = await res.json();import os
import requests
res = requests.get(
"https://api.sitename.example/v1/domains",
headers={"Authorization": f"Bearer {os.environ['SITENAME_KEY']}"},
)
print(res.json()){
"data": [
{
"name": "sitename.example",
"status": "verified"
}
],
"has_more": false
}An API reference in one section: a sidebar of groups and their operations; for each operation its method and path, what it does, its path, query and header parameters, the fields of its body (nested fields under a disclosure, as deep as they go) and its responses; and beside it, sticky on a wide screen, the request in every language with a copy button and the response examples by status.
Add the MCP server once and the agent searches components by intent and byte budget, reads their craft notes and gets the command that installs them.
# 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 feature section with code beside the copy: a headline, copy and a short list on one side, and CodeTabs on the other, one tab per client or language, each panel with its own copy button.
A vertical timeline: the line draws itself down the page in step with the scroll, each step revealing from the line's side as it is reached, markers numbered or dated.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown