Case study · Documentation engineering
Building docs.recurly.com
How I designed, built, and automated the documentation experience for Recurly’s five product hubs on ReadMe: the landing pages, a component design system for guides, a JavaScript behavior layer, and an AI-assisted authoring pipeline.
- Author
- Christian Arango
- Role
- Documentation Engineer & Technical Writer
- Live site
- docs.recurly.com
- Captured
- Public site, October 13, 2026
(open full-size image)At a glance
One system, five product hubs.
Recurly runs separate documentation hubs for Subscriptions, Commerce, Engage, RevRec, and Recover, all on ReadMe. I owned the layer that makes them feel like one product: what readers see, how pages behave, and how new content gets written and shipped.
- HTML/CSS
Landing pages
The global hub and each product’s home: hero, product cards, quick navigation, cross-product discovery. Responsive down to 375px, reduced-motion aware.
- CSS
Guide design system
A tokenized component library (overview, plan pill, TOC pills, benefits, steps, callouts, tables, card grids, carousels) layered on top of ReadMe’s own UI.
- JavaScript
Behavior layer
SPA-safe scripts for navigation, deep-link copying, carousels, and search, written to survive ReadMe’s client-side routing and redeploys.
- Claude skill
Draft → publish-ready MDX
An authoring skill that turns raw drafts into pasteable MDX using the component library, the house voice, and guardrails learned from the renderer.
- Claude + ReadMe connector
Automated rollout
Claude connected to ReadMe to restructure existing pages in batches, so a whole hub could move onto the new system at once.
- Products
Engage and Recover
Engage is the hub I improved most: nearly every page rebuilt. Recover was the last launch I worked on, built on the system from day one.
- 57
- rp-* component classes in the guide design system
- 586
- CSS rule blocks, including 43 ReadMe UI hooks restyled
- 141/146
- Recurly Engage guide pages built on the system
- 20/21
- Recurly Recover guide pages built on the system
Landing pages
A front door for every hub.
The global hub routes readers to each product’s documentation from a single HTML block with its own scoped stylesheet. Each product home is built from the same vocabulary, tuned to how that product’s readers navigate.
Featured launch card
Full-width spotlight for the newest product: content column, logo panel, and a “New” badge whose pulse turns off under prefers-reduced-motion.
2×2 product grid
Uniform cards with a visual strip, label, title, description, and CTA row. Hover lifts the card, tints the border, and nudges the arrow.
Responsive by design
Breakpoints at 768, 480, and 375px. The featured card stacks, the grid collapses to one column, and type scales down step by step.
Defensive against the host
Overflow protection, min-width: 0 on flex children, and explicit resets so ReadMe’s theme can’t leak underlines or widths into the layout.
Search that’s actually ReadMe’s
The hero search bar is a custom element wired to open ReadMe’s native search, so it looks on-brand without reinventing search.
One vocabulary per product home
Each hub home reuses the same eyebrow, title, and description block, with a quick-navigation panel and task-oriented feature cards tuned to that product’s readers.
1 / 2
Guide design system
Components, not one-off pages.
Every guide is plain MDX. Writers drop in rp-* components; the host theme’s custom CSS does the rest. No inline styles, no per-page CSS, so a design change ships to every page at once. Brand tokens live as CSS custom properties shared by the components and 43 restyled ReadMe UI hooks: header, sidebar, table of contents, code tabs, API playground, changelog, and more.
Guide pages using each component, out of 146
Same system across hubs: 20 of 21 Recover pages and 163 of the 165 Subscriptions pages sampled open with the rp-page header cluster. Counted from the published MDX.
Anatomy of a guide page
- Overview
rp-overviewTwo to four sentences on what the page covers.
- Plan pill
rp-planWho can use the feature, with fixed wording per plan so availability reads the same everywhere.
- Callout
rp-calloutFour variants (note, tip, important, warning), each with its own icon and color.
- Section pills
rp-tocNumbered jump links to every top-level section, generated from the headings.
- Requirements list
rp-listUnder a Prerequisites or Limitations heading.
- Definition
rp-definitionPlain-language “what is this and why does it exist,” distinct from the overview.
- Key benefits
rp-benefitsThree or five auto-fit; exactly four switch to a 2×2 grid.
Readers get the same rhythm on every page: what it is, who can use it, where to jump, then the details. Writers get a fixed skeleton instead of a blank page.
1 / 3
Behavior layer
JavaScript that survives a single-page app.
ReadMe renders with React and swaps pages client-side, so a script that runs once on load breaks on the next click. Every module is written to re-attach, self-heal, and stay idempotent.
- Module 1
Changelog navigation override
Points each hub’s native Changelog tab at a custom changelog page and keeps the active tab correct on every route: SPA navigation, reloads, back/forward, and the mobile flyout menu.
- Module 2
“Link copied” heading pill
Click any heading to copy its deep link. A pill floats up and fades out (double requestAnimationFrame so the transition always fires). Re-binds after each page swap.
- Module 3
Infinite carousel
Seamless loop using two clones on each side, an active-card state, arrow-key support, resize-aware positioning, and re-init on navigation.
- Module 4
Hero search and image zoom
The landing search bar opens ReadMe’s native search with bounded polling until the target exists. A lightweight zoom follows the cursor on large screenshots.
// Capture phase: runs before ReadMe's
// own React click handlers
window.addEventListener("click", handleClick, true);
// Child-list changes only, so this script's
// own class/href writes can't loop back
state.observer = new MutationObserver(handleRouteCheck);
state.observer.observe(document.body, {
childList: true, subtree: true,
});
// ReadMe updates URL, article, and active
// classes in separate render passes
[50, 150, 400, 900].forEach((ms) =>
setTimeout(queueApply, ms));Stable hooks only
Targets ReadMe’s rm- classes and ARIA labels, never the hashed CSS-module names that change on every deploy.
Idempotent by construction
A global key with destroy() tears down the previous instance if the script loads twice.
Respects user intent
Cmd/Ctrl/Shift-clicks and middle-clicks keep native behavior, with the corrected URL already in place.
AI-assisted authoring
A Claude skill that writes publish-ready MDX.
I encoded the design system, the brand voice, and every renderer quirk I found into a Claude skill. Paste in a raw draft; get back MDX that goes straight into the ReadMe editor.
- Step 0
Detect doc type
General, Exports, Payment Gateways, Commerce, Engage, or API Guide, each with its own section registry.
- Step 1
One round of questions
Scans for what would become a TODO (plan tier, missing URLs, empty code) and asks once, in a single batch.
- Step 2
Generate
Applies voice, grammar, terminology, and component templates. Title, meta description, and MDX.
- Output
Paste and publish
Pure MDX: markdown headings,
rp-*divs, native components, and a TODO list only for what was deferred.
<div class="rp-page">
<div class="rp-overview">Recurly Engage supports a variety of
proven engagement patterns ...</div>
<div class="rp-plan"><i class="fa-solid fa-key" aria-hidden="true"></i>
Available to all customers on any Recurly Engage subscription plan</div>
<div class="rp-toc">
<a class="rp-toc-pill" href="#definition"><span class="rp-toc-num">1</span>Definition</a>
<a class="rp-toc-pill" href="#key-benefits"><span class="rp-toc-num">2</span>Key benefits</a>
</div>
</div>
### Prerequisites
<ul class="rp-list">
<li>You must have <strong>Company</strong>, <strong>App Administrator</strong>,
or <strong>App Member</strong> permissions in Recurly Engage.</li>
</ul>Guardrails learned the hard way
No blank lines inside a div
ReadMe’s MDX parser exits HTML context on a blank line and renders the rest as raw code.
{curly} placeholders become ALL_CAPS
A single {site_id} is evaluated as JSX and blocks the whole page.
CodeTabs, not Tabs, for code
Consecutive fences with no blank line render as one multi-language block.
Never invent; never alter code
Suspect code gets a TODO for developer review instead of a silent fix.
Automation
Claude and the ReadMe connector.
A skill makes one page fast. Rebuilding a whole hub needs a loop. I connected Claude to ReadMe and ran the skill against existing pages, so restructuring became a review job instead of a copy-paste job.
- Read
Pull the live page
Claude reads the current page content from ReadMe through the connector.
- Transform
Run the skill
The page is rebuilt on the
rp-*system with the house voice and structure. - Write
Update in ReadMe
The new MDX goes back to the same page, keeping its slug and place in the sidebar.
- Review
Human check
I review the rendered page, fix what the renderer disagrees with, and fold lessons back into the skill.
- 135/146
- Engage pages last updated in September–October 2026, the window in which the hub moved onto the design system
- ReadMe updatedAt, read from each published page
Consistency at scale
Every page gets the same header cluster, section order, and components, which is hard to hold by hand across hundreds of pages.
The skill kept getting better
Each renderer bug found in review became a rule in the skill, so the next batch didn’t repeat it.
Writers stay in control
Automation proposes; a person approves. Nothing ships without a rendered check.
Products
The hub I improved most, and the last one I launched.
Recurly Engage
Engage documents a product for growth and lifecycle teams: prompts, experiments, segments, SDKs, and dozens of partner integrations. Today 141 of its 146 guide pages follow the same skeleton and component set. Instructional pages read as procedures, with numbered step cards and screenshots at a consistent width; thirty-plus partner pages share one template, so readers know where setup, fields, and troubleshooting live.
Recurly Recover
Recover is Recurly’s standalone retry engine for failed payments. Its hub launched on the finished system (20 of 21 pages use the rp-page structure), with a developer-first landing page and an API guide that leads with the request a developer actually sends.
(open full-size image)Outcomes
What changed for readers.
Results from my wider role at Recurly (2023–2026), of which this system was a central part.
- 45%
- reduction in support ticket volume, through an audit of every API endpoint, version, and field against real support feedback, release-triggered automation, and visual-clarity improvements
- 56→92%
- user-reported documentation satisfaction after consolidating three products’ docs into one ReadMe Enterprise hub
- 2025
- Technology Impact Award, presented by Recurly’s CTO




