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. The language picked once switches every sample on the page through radios and :has(), with no script; a small script remembers it for the next page. A type with more to say (a format, a range) carries it in a Tooltip, and the sidebar marks the operation in view where the browser has scroll-target-group. It takes operations already shaped, the way a template turns an OpenAPI document into them at build.
Live demo
API reference
Every request takes your key as a bearer token. Sample API, made up for this demo.
Base URLhttps://api.sitename.example/v1
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.
Live, from the library itself, at full size: scroll, hover, navigate away and back. The demo is never gated.
Measured
JavaScript of its own
404 B raw
285 B gzip.
Uses CopyButton and the shared runtime (1.9 kB raw, once per site). With those included: 2.9 kB raw.
CSS 28.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 131 · Firefox 143 · Safari 18.4; needs has, details-content. Elsewhere: without ::details-content the sidebar on a wide screen opens with a click; without :has() the first language shows.
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/api-reference
src/pages/index.astro
---import ApiReference from '@/components/moonarc/pro/ApiReference.astro';import { reference } from '../lib/openapi'; // your OpenAPI document, shaped at build---<ApiReference title="API reference" intro="Every request takes your key as a bearer token." base="https://api.example.com/v1" languages={[{ label: 'cURL', lang: 'sh' }, { label: 'JavaScript', lang: 'js' }, { label: 'Python', lang: 'python' }]} groups={reference.groups}/>
Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: code-tabs, copy-button.
Props
Prop
Type
Default
Description
groups
{ id; title; description?; operations }[]
none
The groups, each with its operations: { id, method, path, summary, description?, deprecated?, params?, body?, samples, responses }. params are fields with in (path, query, header); a field is { name, type, required?, description?, detail?, enum?, default?, children? }; samples are { label, code, lang? }, one per language; responses are { status, description?, example? }. Descriptions are paragraphs split by a blank line, with `code` in backticks.
languages
{ label: string; lang?: string }[]
none
One to six languages every sample comes in, in this order; the first is picked at first. lang is the Shiki language id. An operation without a sample for one of them stops the build naming it.
base
string
none
The base URL, printed in the introduction.
title
string
none
The page's heading.
intro
string
none
Paragraphs under the heading, split by a blank line; `code` in backticks.
id
string
'ma-apiref'
Prefix of the radio names and the localStorage key the language is kept under; unique on the page.
words
Partial<Words>
none
Every word the reference prints around your content: Language, Endpoints, the parameter headings, Body, Responses, required and optional, Deprecated, One of, Default, Fields, Request.
level
1 | 2
1
Heading level of the title; groups are one below and operations two below.
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.
Nothing moves but the language picker's colour, which changes at once.
With ClientRouter
The remembering script is bound through the shared runtime and reads the language again on every page.
Why it is built this way
One language for the whole page: a reader who works in Python reads Python in every sample, and the choice follows them to the next page. The radios do the switching; the script only remembers.
The request panel is dark on every page, as a terminal is, and --ma-dark is 1 inside it so Shiki's dark colours are the ones painted, each moved at build until it reads at 4.5 : 1. The responses are CodeTabs, re-tokened to the same panel.
Nested fields wait under a disclosure with their count, so a large object does not push the next operation off the screen.
The samples stick beside the docs on a wide box and follow them on a narrow one: a reader always has the request in view while reading its parameters.
Every operation must have a sample in every language, checked at build, so switching never lands on an empty panel.
A path's variables ({id}) are set in the emphasis colour, so the part to replace is the part that stands out.
Replaces: Redoc · Swagger UI · Scalar API reference · Protocol (Tailwind Plus) API reference pages. See the migration table.
A code block with tabs (for example npm, pnpm, bun) and a copy button per panel.
every browserclick
Send the API keyA secret key from your dashboard, sent as a bearer token. with every request, and pass an Idempotency-KeyAny unique string. A retry with the same key returns the first response instead of acting twice. on writes.
A short tip anchored to a word, a code or a button: it shows after a moment on hover and at once on focus or a tap, stays while the pointer moves onto it, flips when there is no room, and Escape hides it.
A documentation layout: a sticky sidebar with the current page marked, a reading-progress hairline bound to the scroll, the header and content revealed in two beats, and previous/next links.
Chrome 115 · not Firefox · Safari 26scroll
Quickstart
Your first email in three steps
1
Install the SDK
npm install sitename
pnpm add sitename
bun add sitename
2
Keep your key in the environment
Create 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.
3
Send your first email
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.',});
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.
Chrome 115 · not Firefox · Safari 26scroll
Email API
Email that arrives
One request sends it. Webhooks tell you when it lands, and every message keeps its own log.
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.