Skip to content

Components / Sections

Request Hero

A hero for an HTTP API: the headline beside a console where a request is written line by line, a line runs while it is sent, the status lands with the time it took and the region that answered, and the JSON response streams in under it. Two to four requests take their turn once and the last one rests; a tab above the console picks any of them with radios and :has(), and the console holds still while it is pointed at or holds focus. The code is highlighted at build by Shiki, each token colour moved until it reads on the console, which is dark on every page and every tone: the theme's deepest neutral. Zero JavaScript.

Live demo

Email API

Email that arrives

One request sends it. Webhooks tell you when it lands, and every message keeps its own log.

Sample figures, made up for this demo

POST /v1/emails HTTP/1.1
Host: api.sitename.example
Authorization: Bearer sk_live_••••
Content-Type: application/json

{
  "from": "hello@sitename.example",
  "to": "ada@example.com",
  "subject": "Your receipt",
  "template": "receipt"
}

202 Accepted84 msfra1

{
  "id": "em_7Hq2kD9x",
  "status": "queued",
  "created_at": "2026-10-09T08:14:22Z"
}
GET /v1/emails/em_7Hq2kD9x HTTP/1.1
Host: api.sitename.example
Authorization: Bearer sk_live_••••

200 OK31 msfra1

{
  "id": "em_7Hq2kD9x",
  "status": "delivered",
  "opened": false,
  "events": [
    {
      "type": "delivered",
      "at": "2026-10-09T08:14:25Z"
    }
  ]
}
GET /v1/domains HTTP/1.1
Host: api.sitename.example
Authorization: Bearer sk_live_••••

200 OK27 msiad1

{
  "data": [
    {
      "name": "sitename.example",
      "status": "verified",
      "region": "eu-west"
    }
  ],
  "has_more": false
}

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

tone
density
media

Measured

JavaScript of its own

0 B

Uses Reveal and the shared runtime (1.9 kB raw, once per site). With those included: 3.0 kB raw.

CSS 18.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

Baseline · newly available

Chrome 105 · Firefox 121 · Safari 15.4; needs has. Elsewhere: without :has() the requests play their turns and the tabs do nothing.

Install Pro

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:

terminal
npx shadcn@latest add @moonarc-pro/hero-request
src/pages/index.astro
---
import HeroRequest from '@/components/moonarc/pro/HeroRequest.astro';
---
<HeroRequest
  eyebrow="Email API"
  copy="One request sends it. Webhooks tell you when it lands."
  primary={{ href: '/docs/quickstart/', label: 'Get an API key' }}
  secondary={{ href: '/reference/', label: 'API reference' }}
  host="api.example.com"
  requests={[
    {
      label: 'Send',
      method: 'POST',
      path: '/v1/emails',
      headers: { Authorization: 'Bearer sk_live_••••' },
      body: { to: 'ada@example.com', subject: 'Your receipt' },
      status: 202,
      time: 84,
      region: 'fra1',
      response: { id: 'em_7Hq2', status: 'queued' },
    },
    {
      label: 'Check',
      method: 'GET',
      path: '/v1/emails/em_7Hq2',
      status: 200,
      time: 31,
      response: { id: 'em_7Hq2', status: 'delivered' },
    },
  ]}
>
  <span slot="headline">Email that <em>arrives</em></span>
</HeroRequest>

Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: split-text, reveal.

Props

PropTypeDefaultDescription
hoststringnoneThe host every request goes to, printed in the console's bar and as the Host header.
requests{ label; method; path; headers?; body?; status; statusText?; time; region?; response }[]noneOne to four requests, in the order they play. label is the tab's word; method is GET, POST, PUT, PATCH or DELETE; headers come after Host in order (mask a secret yourself); body and response are written as JSON when they are objects; time is in ms; statusText defaults to the usual reason phrase.
headlinestringnoneThe headline, split by word. Slot "headline" wins and may hold an <em>, set in the emphasis face and colour.
eyebrowstringnoneA short line above the headline. Slot "eyebrow" wins.
copystringnoneA paragraph under the headline.
primary{ href: string; label: string }noneThe main action.
secondary{ href: string; label: string }noneThe second action. Slot "actions" replaces both.
notestringnoneA short line under the actions, in the mono face.
labelstring'Example requests'The name of the console and of its tabs, read out before them.
level1 | 21Heading level of the headline: 1 on a page's own hero, 2 under another h1.
media'right' | 'left''right'Side of the console on a wide screen.
tone'surface' | 'muted' | 'ink' | 'brand'unset (paints nothing)The hero's ground. The console keeps its deep neutral on every tone, edged by a hairline.
density'compact' | 'cozy' | 'roomy''cozy'Block padding ×0.8 / ×1 / ×1.25 through --ma-density.

Theming and overrides

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.

Classes

.ma-hreq.ma-hreq__actions.ma-hreq__bar.ma-hreq__code.ma-hreq__console.ma-hreq__copy.ma-hreq__eyebrow.ma-hreq__headline.ma-hreq__host.ma-hreq__inner.ma-hreq__line.ma-hreq__lock.ma-hreq__note.ma-hreq__panel.ma-hreq__panels.ma-hreq__primary.ma-hreq__radio.ma-hreq__req.ma-hreq__res.ma-hreq__secondary.ma-hreq__send.ma-hreq__status.ma-hreq__tab.ma-hreq__tabs.ma-hreq__text

Slots

slot="eyebrow"slot="headline"slot="actions"

Reduced motion

The first request is shown whole, with no turns and nothing streaming; the tabs still pick another.

With ClientRouter

CSS only; a fresh page plays the turns from the start.

Why it is built this way

Replaces: API playground heroes on developer landing pages · Animated request/response mockups. See the migration table.

npx astro add moonarc
pnpm astro add moonarc
bunx astro add moonarc

A code block with tabs (for example npm, pnpm, bun) and a copy button per panel.

every browserclick

API reference

Every request takes your key as a bearer token. Sample API, made up for this demo.

Base URLhttps://api.sitename.example/v1

Language

Emails

Send an email, then follow it until it lands.

Send an email

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.

    Fields 2
    • namestringoptional

    • valuestringoptional

Responses

202
Queued.
422
A field is missing or the domain is not verified.

Request

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 an email

GET/emails/{id}

Path parameters

  • idstringrequired

    The id the send returned.

Responses

200
The email and where it is.

Request

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"
}

Domains

The domains you send from, and whether their records check out.

List domains

GET/domains

Query parameters

  • limitinteger1 to 100.optional

    How many to return.

    Default20

Responses

200
The domains, newest first.

Request

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.

newly · Chrome 131 · Firefox 143 · Safari 18.4click

Give your coding agent the Moonarc catalogue

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.

  • search_motion: Components for an intent under a JavaScript budget in raw bytes.
  • get_component: Source, props and craft notes: easing, duration, the reduced-motion branch, ClientRouter behaviour.
  • get_install_command: The exact 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 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.

every browserclick

A hero for developer tools: split headline on the left, a terminal window on the right that types the install command and prints its output line by line, on a pulsing dot field.

Chrome 116 · not Firefox · Safari 18load

Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown