Skip to main content
. Not present in production builds. Safe no-op in prod. */ body { padding: 0 !important; } /* Colors Used #3CB540 - Jade Green #2b9a66 - Light Green #18794E - Dark Green Complementary Greens See https://coolors.co/004225-1a794e-08a045-3cb540-62ba4f #004225 - Deep Forrest #1A794E - Turf Green #08A045 - Medium Jungle #3CB540 - Jade Green #6BBF59 - Moss Green See https://coolors.co/0c0c0c-073b3a-1a794e-08a045-6bbf59 #0C0C0C - Onyx Black #073B3A - Dark Teal #1A794E - Turf Green #08A045 - Medium Jungle #6BBF59 - Moss Green See https://coolors.co/fffffa-073b3a-1a794e-08a045-6bbf59 #FFFFFA - Porcelain #073B3A - Dark Teal #1A794E - Turf Green #08A045 - Medium Jungle #6BBF59 - Moss Green Pink Offset Colour See https://coolors.co/073b3a-1a794e-f61067-08a045-6bbf59 #F61067 - Razzmatazz Pink #073B3A - Dark Teal #1A794E - Turf Green #08A045 - Medium Jungle #6BBF59 - Moss Green */ /* ============================================ GLOBAL THEME VARIABLES Component governance source of truth ============================================ */ :root { --lp-color-accent: #3cb540; --lp-color-accent-strong: #18794e; --lp-color-accent-soft: #6bbf59; --lp-color-accent-bright: #5dd662; --lp-color-accent-brightest: #a0f0a5; --lp-color-arbitrum: #3ea6f8; --lp-color-text-primary: #181c18; --lp-color-text-secondary: #717571; --lp-color-text-muted: #9ca3af; --lp-color-bg-page: #ffffff; --lp-color-bg-card: #f9fafb; --lp-color-bg-elevated: #f3f6f4; --lp-color-bg-subtle: rgba(24, 28, 24, 0.04); --lp-color-bg-overlay: rgba(12, 12, 12, 0.5); --lp-color-border-default: #e5e7eb; --lp-color-border-strong: rgba(24, 28, 24, 0.18); --lp-color-border-inverse: rgba(255, 255, 255, 0.5); --lp-color-on-accent: #ffffff; --lp-color-link: #18794e; --lp-color-link-hover: #004225; --lp-color-brand-discord: #5865f2; --lp-color-brand-forum: #00aeef; --lp-color-brand-github: #181c18; --lp-color-brand-x: #181c18; --lp-color-brand-globe: #00c0ff; --lp-color-brand-twitch: #9048ff; --lp-color-brand-youtube: #ff0034; --lp-color-brand-instagram: #dc2275; --lp-color-brand-linkedin: #0189df; --lp-color-brand-preview: #b636dd; --lp-color-brand-coming-soon: #ef1a73; --lp-color-brand-linux: #ff9a0e; --lp-color-brand-windows: #14bbf7; --lp-color-brand-macos: #60ba47; --lp-color-status-good: #22c55e; --lp-color-status-warn: #fbbf24; --lp-color-status-bad: #ef4444; --lp-spacing-1: 0.25rem; --lp-spacing-2: 0.5rem; --lp-spacing-3: 0.75rem; --lp-spacing-4: 1rem; --lp-spacing-6: 1.5rem; --lp-spacing-8: 2rem; --lp-spacing-px-3: 3px; --lp-spacing-px-4: 4px; --lp-spacing-px-6: 6px; --lp-spacing-px-8: 8px; --lp-spacing-px-12: 12px; --lp-font-sans: 'Inter', 'Segoe UI', sans-serif; --lp-font-mono: 'SFMono-Regular', 'SF Mono', 'Menlo', monospace; --lp-radius-sm: 0.25rem; --lp-radius-md: 0.5rem; --lp-radius-lg: 0.75rem; --lp-shadow-card: 0 8px 24px rgba(24, 28, 24, 0.08); --lp-z-base: 1; --lp-z-overlay: 10; --lp-z-modal: 50; /* Legacy aliases maintained during migration */ --accent: var(--lp-color-accent); --accent-dark: var(--lp-color-accent-strong); --hero-text: var(--lp-color-text-primary); --text: var(--lp-color-text-secondary); --text-secondary: var(--lp-color-text-secondary); --muted-text: var(--lp-color-text-muted); --background: var(--lp-color-bg-page); --card-background: var(--lp-color-bg-card); --background-highlight: var(--lp-color-bg-subtle); --border: var(--lp-color-border-default); --button-text: var(--lp-color-on-accent); --page-header-description-color: var(--lp-color-text-secondary); --arbitrum: var(--lp-color-arbitrum); } .dark { --lp-color-accent: #2b9a66; --lp-color-accent-strong: #18794e; --lp-color-accent-soft: #3cb540; --lp-color-accent-bright: #5dd662; --lp-color-accent-brightest: #7fe584; --lp-color-text-primary: #e0e4e0; --lp-color-text-secondary: #a0a4a0; --lp-color-text-muted: #6b7280; --lp-color-bg-page: #0d0d0d; --lp-color-bg-card: #1a1a1a; --lp-color-bg-elevated: #141a16; --lp-color-bg-subtle: rgba(255, 255, 255, 0.1); --lp-color-bg-overlay: rgba(0, 0, 0, 0.5); --lp-color-border-default: #333333; --lp-color-border-strong: rgba(255, 255, 255, 0.3); --lp-color-border-inverse: rgba(255, 255, 255, 0.5); --lp-color-on-accent: #ffffff; --lp-color-link: #5dd662; --lp-color-link-hover: #a0f0a5; --lp-color-brand-github: #f0f0f0; /* Legacy aliases maintained during migration */ --accent: var(--lp-color-accent); --accent-dark: var(--lp-color-accent-strong); --hero-text: var(--lp-color-text-primary); --text: var(--lp-color-text-secondary); --text-secondary: var(--lp-color-text-secondary); --muted-text: var(--lp-color-text-muted); --background: var(--lp-color-bg-page); --card-background: var(--lp-color-bg-card); --background-highlight: var(--lp-color-bg-subtle); --border: var(--lp-color-border-default); --button-text: var(--lp-color-on-accent); --page-header-description-color: var(--lp-color-text-secondary); --arbitrum: var(--lp-color-arbitrum); } /* ============================================ */ /* Code block themes hiki codeblock themes: Popular Dark Themes: github-dark (what you have now) github-dark-dimmed github-dark-high-contrast dracula dracula-soft monokai nord one-dark-pro poimandres rose-pine everforest-dark vitesse-dark Popular Light Themes: github-light (what you have now) github-light-high-contrast solarized-light rose-pine-dawn everforest-light vitesse-light */ /* img[alt="dark logo"], img[alt="light logo"] { max-width: 180px; } */ /* V2 TEST */ /* a.nav-tabs-item[href="/pages/resources/resources_hub.mdx"], a.nav-tabs-item[href="/pages/08_help/README"] { color: rgba(255, 90, 90, 0.342) !important; } */ /* Make the nav-tabs container full width */ .nav-tabs { width: 100%; justify-content: flex-start; } /* Fix Mintlify content width and centering. Regular pages: balance padding + widen inner cap. Portal/frame pages: balance padding (smaller) + widen inner cap for full-width hero. */ @media (min-width: 1024px) { /* Regular pages */ #content-container:not(:has(.frame-mode-hero-full)):not( :has(.frame-mode-container) ) { padding-left: 3rem !important; padding-right: 3rem !important; } #content-container:not(:has(.frame-mode-hero-full)):not( :has(.frame-mode-container) ) > .max-w-5xl { max-width: 72rem !important; } /* Portal/frame pages — tighter balanced padding, wider inner cap */ #content-container:has(.frame-mode-hero-full), #content-container:has(.frame-mode-container) { padding-left: 2rem !important; padding-right: 2rem !important; } #content-container:has(.frame-mode-hero-full) > .max-w-5xl, #content-container:has(.frame-mode-container) > .max-w-5xl { max-width: 80rem !important; } } #navbar > div.z-10.mx-auto.relative > div.hidden.lg\:flex.px-12.h-12 > div { column-gap: 2rem !important; } a.nav-tabs-item[href*='/internal/'] { margin-left: 1rem; margin-right: -1rem; padding-right: 0; border-bottom-color: transparent !important; } /* .gap-x-6 { column-gap: 2rem !important; } */ /* .nav-tabs h-full flex text-sm gap-x-6 { column-gap: 2rem !important; } */ /* Push Resource HUB to the right and style as outlined button */ a.nav-tabs-item[href$='/resources/redirect'], a.nav-tabs-item[href$='/resources/portal'], a.nav-tabs-item[href$='/07_resources/redirect'], a.nav-tabs-item[href$='/07_resources/portal'] { margin-left: auto; background-color: transparent; border: 1px solid var(--accent) !important; padding: 4px 8px; border-radius: 4px; font-size: 0.7rem; height: auto !important; align-self: center; margin-right: -2rem; } /* Color the text */ /* a.nav-tabs-item[href="/v2/resources/resources_hub"] { color: #2b9a66 !important; } */ /* Shrink & color the icon */ a.nav-tabs-item[href$='/resources/redirect'] svg, a.nav-tabs-item[href$='/resources/portal'] svg, a.nav-tabs-item[href$='/07_resources/redirect'] svg, a.nav-tabs-item[href$='/07_resources/portal'] svg, a.nav-tabs-item[href$='/07_resources/resources_hub'] svg { height: 0.75rem; width: 0.75rem; /* background-color: #2b9a66 !important; */ } /* Hide the underline on the button */ a.nav-tabs-item[href$='/resources/redirect'] > div:last-child, a.nav-tabs-item[href$='/resources/portal'] > div:last-child, a.nav-tabs-item[href$='/07_resources/redirect'] > div:last-child, a.nav-tabs-item[href$='/07_resources/portal'] > div:last-child, a.nav-tabs-item[href$='/07_resources/resources_hub'] > div:last-child { display: none; } /* Stack footer links vertically */ #footer .flex-col .flex.gap-4 { flex-direction: column !important; gap: 0rem !important; } /* Reduce footer padding */ #footer > div { padding-top: 2rem !important; padding-bottom: 1rem !important; } /* Accessibility: prevent hidden assistant sheet from receiving focus */ #chat-assistant-sheet[aria-hidden='true'] { display: none !important; } /* Accessibility: ensure CTA buttons meet minimum target size */ button.text-left.text-gray-600.text-sm.font-medium { min-height: 24px; padding-top: 4px; padding-bottom: 4px; } /* #footer > div > div:first-child { display: flex; flex-direction: row !important; color: red !important; } #footer > div > div:first-child > div { display: flex; flex-direction: row !important; color: green !important; } */ /* Fix bad styling of cards with arrows */ [data-component-part='card-content-container'] { padding-right: 2.5rem; /* Creates space for the arrow */ } /* Reposition View component dropdown */ /* To find the correct selector: 1. Open your page with View components in the browser 2. Right-click on the dropdown in the top-right corner 3. Select "Inspect Element" 4. Find the class name or data attribute 5. Replace the selector below with the actual one */ /* Common possible selectors - uncomment and adjust the one that works */ /* Option 1: If it has a data attribute */ /* [data-view-dropdown] { position: relative !important; top: 60px !important; right: 20px !important; } */ /* Option 2: If it's in a fixed container */ /* .fixed [class*="view"] { position: relative !important; top: 60px !important; } */ /* Option 3: Target by position (fixed elements in top-right) */ /* .fixed.top-0.right-0 [class*="select"], .fixed.top-0.right-0 [class*="dropdown"] { position: relative !important; top: 60px !important; margin-right: 20px !important; } */ /* Option 4: Move it inline with content instead of fixed position */ /* Replace 'ACTUAL_SELECTOR' with the real class name from browser inspection */ /* ACTUAL_SELECTOR { position: static !important; display: inline-block !important; margin-bottom: 20px !important; } */ .code-block > div > div > svg { background-color: #18794e !important; } /* Error 404 Styling */ #error-description > span > div > div { border: 1px solid #18794e !important; } body > div.relative.antialiased.text-gray-500.dark\:text-gray-400 > div.peer-\[\.is-not-custom\]\:lg\:flex.peer-\[\.is-custom\]\:\[\&\>div\:first-child\]\:\!hidden.peer-\[\.is-custom\]\:\[\&\>div\:first-child\]\:sm\:\!hidden.peer-\[\.is-custom\]\:\[\&\>div\:first-child\]\:md\:\!hidden.peer-\[\.is-custom\]\:\[\&\>div\:first-child\]\:lg\:\!hidden.peer-\[\.is-custom\]\:\[\&\>div\:first-child\]\:xl\:\!hidden > div.flex.flex-col.items-center.justify-center.w-full.max-w-lg.overflow-x-hidden.mx-auto.py-48.px-5.text-center.\*\:text-center.gap-y-8.not-found-container > div { margin-top: -5rem; } #error-description > span > div > div > div.relative.rounded-xl.overflow-hidden.flex.justify-center > img { width: 500px; aspect-ratio: 4 / 3; object-fit: cover; /* border: 1px solid #fff; */ } /* Step List Color Icons Styling */ /* #content > div.steps > div > div.absolute.ml-\[-13px\].py-2 > div { background-color: #18794e !important; } */ /* Step List Color Titles */ #content > div.steps.ml-3\.5.mt-10.mb-6 > div > div.w-full.overflow-hidden.pl-8.pr-px > p { color: #2b9a66 !important; } /* View Dropdown */ /* #radix-_R_5slubt9fen9fdb_ */ /* Turn off bg-white in dark mode for multi-view dropdown (PALM THEME BUG) */ .dark .bg-white\/\[0\.95\].multi-view-dropdown-trigger { background-color: transparent !important; background: none !important; } /* Sidebar collapse button - bigger and easier to click */ /* #sidebar button.absolute { min-width: 2.5rem !important; min-height: 2.5rem !important; padding: 0.75rem !important; z-index: 100 !important; } */ /* Override US flag with UK flag in language selector */ /* Hide the original img and use background-image instead */ /* #localization-select-trigger img[alt="US"], #localization-select-item-en img[alt="US"], img[alt="US"][src*="flags/US.svg"] { opacity: 0 !important; position: relative !important; } #localization-select-trigger img[alt="US"]::before, #localization-select-item-en img[alt="US"]::before, img[alt="US"][src*="flags/US.svg"]::before { content: "" !important; position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background-image: url("/snippets/assets/media/images/site/united-kingdom-flag-icon.svg") !important; background-size: cover !important; background-position: center !important; border-radius: 50% !important; opacity: 1 !important; } */ /* Hide the panel on frame mode pages (MINTLIFY SUCKS) */ /* Hide empty table of contents layout only when it's empty */ #table-of-contents-layout:empty, #content-side-layout:has(#table-of-contents-layout:empty) { display: none; } /* DynamicTable: force fixed layout so columnWidths prop values take effect. Mintlify's Tailwind prose resets table-layout to auto — !important required. */ [data-docs-dynamic-table] { table-layout: fixed !important; } /* StyledTable should sit flush inside its own border shell. Mint wraps rendered tables in a scroll container with vertical padding, which creates a false gap above/below the header row. */ [data-docs-styled-table-shell] > div { padding-top: 0 !important; padding-bottom: 0 !important; margin-top: 0 !important; margin-bottom: 0 !important; } /* BorderedBox should own its internal spacing. Trim default block margins on the first/last rendered child so headings and paragraphs do not add a false gap inside the padded shell. */ [data-docs-bordered-box] > :first-child { margin-top: 0 !important; } [data-docs-bordered-box] > :last-child { margin-bottom: 0 !important; } [data-docs-bordered-box][data-accent-bar]::before { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 4px; background-color: var(--accent-bar-color); border-radius: inherit; border-top-right-radius: 0; border-bottom-right-radius: 0; } /* Frame mode container - 80% of #content-container width, centered */ /* Breaks out of #content padding to center in full #content-container */ .frame-mode-container { width: calc(100% + 96px + 20px); /* 976px */ margin-left: -96px; margin-right: -20px; margin-bottom: 2rem; padding-left: 15%; /* Adjust this for desired content width */ padding-right: 15%; /* Adjust this for desired content width */ box-sizing: border-box; } /* Frame mode container inside hero - already broken out, so reset */ .frame-mode-hero-full .frame-mode-container { width: 100%; margin-left: 0; margin-right: 0; padding-left: 0%; padding-right: 0%; } /* Pagination on frame mode pages ONLY - match container padding */ [data-page-mode='frame'] #pagination { width: calc(100% + 96px + 20px); margin-left: -96px; margin-right: -20px; padding-left: calc((100% + 96px + 20px) * 0.1 + 96px); padding-right: calc((100% + 96px + 20px) * 0.1 + 20px); box-sizing: border-box; } /* Hero full width - breaks out of #content padding to fill #content-container */ .frame-mode-hero-full { width: calc(100% + 96px + 20px); margin-left: -96px; margin-right: -20px; position: relative; } @media (max-width: 1023px) { .frame-mode-container { width: 100%; margin-left: 0; margin-right: 0; padding-left: 1rem; padding-right: 1rem; } [data-page-mode='frame'] #pagination { width: 100%; margin-left: 0; margin-right: 0; padding-left: 1rem; padding-right: 1rem; } .frame-mode-hero-full { width: 100%; margin-left: 0; margin-right: 0; } } #starfield { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; } /* Target the card content container */ .frame-mode-hero-full [data-component-part='card-content-container'] { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; padding-right: 2.5rem; /* Space for arrow icon (0.75rem right + icon width ~1rem + margin) */ } /* Target the arrow icon */ .frame-mode-hero-full #card-link-arrow-icon { top: 0.75rem; right: 0.75rem; } /* #content > div.frame-mode-hero-full > div.frame-mode-container > div > div:nth-child(2) > div > div > div:nth-child(4) > a > div { padding-top: 0.5rem; padding-bottom: 0.5rem; } #content > div.frame-mode-hero-full > div.frame-mode-container > div > div:nth-child(2) > div > div > div:nth-child(4) > a > div > #card-link-arrow-icon { top: 0.75rem; right: 0.75rem; } */ /* ============================================ ACCESSIBILITY — Focus indicators ============================================ */ input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible, a:focus-visible, [tabindex]:focus-visible { outline: 2px solid var(--accent) !important; outline-offset: 2px; } /* ============================================ ACCESSIBILITY — Responsive breakpoints ============================================ */ @media (max-width: 767px) { .frame-mode-hero-full { width: 100%; max-width: 100%; overflow-x: hidden; } } @media (max-width: 480px) { #content { padding-left: 1rem; padding-right: 1rem; } } /* ============================================ UTILITY CLASSES — inline element styling Used where components can't replace inline spans (e.g., inside Mintlify , components) ============================================ */ .lp-inline-flex { display: flex; align-items: center; } .lp-text-muted { color: var(--lp-color-text-secondary); } .lp-text-italic-muted { font-style: italic; color: var(--lp-color-text-secondary); } .lp-inline-flex-gap { display: flex; align-items: center; gap: 0.2rem; } .lp-link-underline { border-bottom: 1.5px solid var(--lp-color-text-primary); color: var(--lp-color-text-primary); padding-bottom: 0.25rem; }

Overview

lpd-mdx-preview is a VS Code extension that renders .mdx and .md files in a side panel with governed Mintlify built-ins, Livepeer custom component renderers, and Mermaid diagrams. It is the primary local authoring preview tool for this repo, but runtime-dependent surfaces still need verification in a Mintlify dev session. Keybinding: Cmd+Shift+V (Mac) / Ctrl+Shift+V (Windows/Linux) Location: tools/editor-extensions/lpd-mdx-preview/

Repo Context

tools/editor-extensions/lpd-mdx-preview
extension.js
package.json
README.md
lpd-mdx-preview-0.0.2.vsix

Installation

Works in VS Code, Cursor, and Windsurf (all VS Code forks). The same .vsix file installs in all three. All editors at once (recommended):
bash tools/editor-extensions/install.sh
Detects which editors are installed (~/.vscode, ~/.cursor, ~/.windsurf) and deploys to each. The installer verifies that the checked-in .vsix matches source before installing. If the package is stale, the install fails and prints the exact rebuild command. Manual install: Extensions sidebar → ... menu → Install from VSIX → select tools/editor-extensions/lpd-mdx-preview/lpd-mdx-preview-0.0.2.vsix. Rebuild after source changes:
cd tools/editor-extensions/lpd-mdx-preview
npx @vscode/vsce package --no-dependencies -o lpd-mdx-preview-0.0.2.vsix
Then re-run install.sh or reinstall manually.

Usage

Open any .mdx or .md file, then press Cmd+Shift+V (Mac) or Ctrl+Shift+V (Windows/Linux).The preview opens in a side panel and updates automatically as you type (300ms debounce).
The preview detects your VS Code colour theme automatically (auto mode). You can override this in settings:
"livepeer.mdxPreview.theme": "dark"
Options: auto | light | dark
Frontmatter is stripped from the preview body and rendered as a header bar showing: title, pageType, audience, status, purpose, and lastVerified.
Fenced code blocks tagged with mermaid are rendered using the bundled Mermaid.js with Livepeer theme colours. Diagrams update live as you edit.

Component Rendering Tiers

Custom components (your own project .jsx files) fall into Tier 3 — they render as labelled placeholders with their children content visible. Most Mintlify built-ins and the Livepeer component library render as styled HTML, but runtime-heavy built-ins such as OpenAPI remain approximate.

Component Reference

Tier 1 — Mintlify Built-ins

Purpose: Callout boxes for supplementary information.
<Note>Standard info callout.</Note>
<Tip>Helpful suggestion.</Tip>
<Warning>Something to watch out for.</Warning>
<Info>Neutral informational note.</Info>
PropTypeDefaultDescription
(none)Content passed as children
Purpose: Generic callout with a custom Font Awesome icon.
<Callout icon="rocket">Custom callout with icon.</Callout>
PropTypeDefaultDescription
iconstringinfoFont Awesome icon name (e.g. rocket, star)
Purpose: Linked or unlinked content card with optional icon and arrow.
<Card title="Gateway Setup" icon="server" href="/v2/gateways/setup" arrow>
  Run your first gateway in minutes.
</Card>

<Card title="Horizontal" icon="code" href="/v2/developers" arrow horizontal>
  Displayed inline.
</Card>
PropTypeDefaultDescription
titlestringCard heading
iconstringFont Awesome icon name
hrefstringLink target; wraps card in <a>
arrowbooleanfalseAppends to title
horizontalbooleanfalseInline flex layout
Purpose: Responsive grid wrapper for <Card> components.
<CardGroup cols={2}>
  <Card title="One" />
  <Card title="Two" />
</CardGroup>
PropTypeDefaultDescription
cols1 | 2 | 3 | 42Number of grid columns
Purpose: Interactive tabbed content sections.
<Tabs>
  <Tab title="On-chain" icon="link">Content for tab one.</Tab>
  <Tab title="Off-chain" icon="cloud">Content for tab two.</Tab>
</Tabs>
Tab props:
PropTypeDefaultDescription
titlestringTabTab label
iconstringFont Awesome icon name
Purpose: Collapsible content sections. Rendered as <details>/<summary> in the preview.
<AccordionGroup>
  <Accordion title="From a Cloud Background?" icon="cloud">
    Content here.
  </Accordion>
</AccordionGroup>
Accordion props:
PropTypeDefaultDescription
titlestringDetailsSummary/header text
iconstringFont Awesome icon name
Purpose: Numbered step-by-step walkthrough.
<Steps>
  <Step title="Install dependencies">Run `npm install`.</Step>
  <Step title="Configure">Set your environment variables.</Step>
</Steps>
Step props:
PropTypeDefaultDescription
titlestringStep heading
Purpose: Bordered container for isolating content visually. No props — content as children.
<Frame>
  <img src="/path/to/image.png" />
</Frame>
Purpose: Two-column layout grid. No props.
<Columns>
  <div>Left</div>
  <div>Right</div>
</Columns>
Purpose: Show/hide toggle for secondary content.
<Expandable title="Advanced options">Hidden until expanded.</Expandable>
PropTypeDefaultDescription
titlestringShow moreToggle label
Purpose: Code block container. Rendered as plain <pre><code> in the preview without syntax highlighting.
<CodeBlock language="bash">livepeer -gateway</CodeBlock>
PropTypeDefaultDescription
languagestringLanguage identifier (e.g. bash, json)
Purpose: Inline Font Awesome icon.
<Icon icon="rocket" />
PropTypeDefaultDescription
iconstringFont Awesome icon name
namestringAlias for icon
Purpose: Changelog or versioned annotation with a label.
<Update label="Q4 2025">Off-chain gateway mode shipped.</Update>
PropTypeDefaultDescription
labelstringDate or version label
datestringAlias for label
Purpose: API response field documentation row.
<ResponseField name="id" type="string">
  Unique identifier for the job.
</ResponseField>
PropTypeDefaultDescription
namestringField name (rendered in accent colour)
typestringData type label
Purpose: API parameter documentation row.
<ParamField path="streamId" type="string" required>
  Stream identifier passed in the URL path.
</ParamField>
PropTypeDefaultDescription
pathstringPath parameter name
querystringQuery parameter name
bodystringRequest-body field name
headerstringHeader name
typestringData type label
requiredbooleanfalseAdds a required marker
Purpose: High-severity callout variant.
<Danger>Do not expose this secret in client-side code.</Danger>
Purpose: Inline pill label with accent styling.
<Badge color="var(--accent)">Preview</Badge>
PropTypeDefaultDescription
colorstringvar(--accent)Accent colour used for border, text, and background tint
textstringFallback text when no children are passed
Purpose: Group multiple code blocks in one bordered container.
<CodeGroup>
  <CodeBlock language="bash">npm install</CodeBlock>
  <CodeBlock language="bash">npm run dev</CodeBlock>
</CodeGroup>
Purpose: Repo-style file tree rendering using canonical Mintlify syntax.
<Tree>
  <Tree.Folder name="src" defaultOpen>
    <Tree.File name="index.js" />
  </Tree.Folder>
</Tree>
Tree.Folder props:
PropTypeDefaultDescription
namestringfolderFolder label
defaultOpenbooleanfalseExpands the folder by default
Tree.File props:
PropTypeDefaultDescription
namestringfileFile label
Purpose: API reference embed. The preview renders a descriptive placeholder rather than the full interactive Mintlify API experience.
<OpenAPI method="get" path="/asset/{id}" />
PropTypeDefaultDescription
methodstringHTTP method label
pathstringAPI path shown in the placeholder
openapistringAlias for path
Purpose: Styled HTML table with header and body rows.
<StyledTable>
  <TableRow header>
    <TableCell header>Column A</TableCell>
    <TableCell header>Column B</TableCell>
  </TableRow>
  <TableRow>
    <TableCell>Value</TableCell>
    <TableCell>Value</TableCell>
  </TableRow>
</StyledTable>
TableRow props:
PropTypeDefaultDescription
headerbooleanfalseRenders row with header background
TableCell props:
PropTypeDefaultDescription
headerbooleanfalseRenders as <th> with bold weight

Tier 2 — Livepeer Custom Components

Spacing

Purpose: Horizontal rule with optional centred label text.Location: snippets/components/elements/spacing/Divider.jsx
<CustomDivider />
<CustomDivider middleText="Role Evolution" />
PropTypeDefaultDescription
middleTextstringText centred in the divider
textstringAlias for middleText
Purpose: Explicit vertical whitespace block.
<Spacer height="48px" />
PropTypeDefaultDescription
heightstring24pxCSS height value
sizestringAlias for height
Purpose: Plain <hr> divider. No props.
<Divider />

Containers

Purpose: Constrains content width and centres it horizontally.Location: snippets/components/wrappers/containers/Containers.jsx
<CenteredContainer maxWidth="960px">
  Content here.
</CenteredContainer>
PropTypeDefaultDescription
maxWidthstring100%CSS max-width value
Purpose: Card-like container with a visible border.
<BorderedBox borderColor="#2d9a67" padding="24px">
  Content here.
</BorderedBox>
PropTypeDefaultDescription
borderColorstringvar(--border)CSS border colour
backgroundColorstringvar(--card-bg)CSS background colour
paddingstring16pxCSS padding
Purpose: Forces content to full available width. No props.
<FullWidthContainer>Content here.</FullWidthContainer>
Purpose: Flexbox layout wrapper.
<FlexContainer direction="row" gap="16px" align="center">
  <div>Item A</div>
  <div>Item B</div>
</FlexContainer>
PropTypeDefaultDescription
directionstringCSS flex-direction (row, column)
gapstring12pxCSS gap
alignstringCSS align-items
justifystringCSS justify-content
wrapbooleanfalseEnables flex-wrap: wrap
Purpose: CSS Grid layout wrapper.
<GridContainer columns="repeat(3, 1fr)" gap="16px">
  <div>Cell 1</div>
  <div>Cell 2</div>
  <div>Cell 3</div>
</GridContainer>
PropTypeDefaultDescription
columnsstringCSS grid-template-columns value
gapstring12pxCSS gap
Purpose: Scrollable container with a fixed maximum height.
<ScrollBox maxHeight="300px">Long content here.</ScrollBox>
PropTypeDefaultDescription
maxHeightstring400pxCSS max-height value

Diagrams

Purpose: Horizontally scrollable wrapper for Mermaid or wide diagram content.Location: snippets/components/displays/diagrams/ScrollableDiagram.jsx
<ScrollableDiagram title="Gateway Role Evolution" maxHeight="420px">
```mermaid
flowchart LR
  A --> B
```
</ScrollableDiagram>
PropTypeDefaultDescription
titlestringLabel shown above the diagram
maxHeightstringCSS max-height to constrain tall diagrams
Purpose: Inline text link with suffix.Location: snippets/components/elements/links/Links.jsx
<LinkArrow href="/v2/gateways/concepts/capabilities" label="Gateway Capabilities" newline={false} />
PropTypeDefaultDescription
hrefstring#Link target
labelstringLink text
textstringAlias for label
newlinebooleantrueIf false, renders inline (no line break)
Purpose: Card that links to a destination.
<GotoCard href="/v2/gateways/setup">Setup content here.</GotoCard>
PropTypeDefaultDescription
hrefstring#Link target

Text & Headings

Purpose: Styled italic accent text. Used below page titles. No props.
<Subtitle>Understand how gateways route workloads.</Subtitle>
Purpose: Blockquote with Livepeer accent border. FrameQuote adds an outer border frame. No props.
<Quote>Gateways are the demand side of the network.</Quote>
<FrameQuote>Framed and quoted.</FrameQuote>
Purpose: Heading components with optional Font Awesome icon prefix. Used on FrameMode pages.
<H2 icon="server">Gateway Setup</H2>
PropTypeDefaultDescription
iconstringFont Awesome icon name shown before heading text
Purpose: Explicit paragraph wrapper. Used on FrameMode pages. No props.
<P>Body text content here.</P>
Purpose: Inline code-styled text value.
<CopyText text="livepeer -gateway -orchAddr 0x..." />
PropTypeDefaultDescription
textstringText to display as inline code
valuestringAlias for text
Purpose: Composable title elements with suffix. No props — content as children.
<CardTitleTextWithArrow>Quick Start</CardTitleTextWithArrow>
<AccordionTitleWithArrow>Show Details</AccordionTitleWithArrow>

Cards

Purpose: Standalone content card with title and icon.
<DisplayCard title="Run a Gateway" icon="server">
  Step-by-step instructions.
</DisplayCard>
PropTypeDefaultDescription
titlestringCard heading
iconstringFont Awesome icon name
Purpose: Card constrained to a max width and centred.
<WidthCard width="600px">Constrained content.</WidthCard>
PropTypeDefaultDescription
widthstring100%CSS max-width
maxWidthstringAlias for width
Purpose: Card with an image on the left and content on the right.
<InlineImageCard src="/snippets/assets/logos/Livepeer-Logo-Symbol.svg">
  Description text here.
</InlineImageCard>
PropTypeDefaultDescription
srcstringImage source URL
Purpose: Card with a heading and freeform children content.
<InteractiveCard title="Configuration">Content here.</InteractiveCard>
PropTypeDefaultDescription
titlestringCard heading
Purpose: Horizontally scrollable row of cards. No props.
<CardCarousel>
  <Card title="A" />
  <Card title="B" />
</CardCarousel>

Callout Banners

Purpose: Styled callout with a custom icon (emoji or character).
<CustomCallout icon="🔧">Manual configuration required.</CustomCallout>
PropTypeDefaultDescription
iconstringℹ️Icon character or emoji
Purpose: Tip callout with a 💡 icon. No props — content as children.
<TipWithArrow>Use the remote signer to skip ETH setup.</TipWithArrow>
Purpose: Status banners indicating a feature is pending, in preview, or under review. No props.
<ComingSoonCallout>Dashboard analytics</ComingSoonCallout>
<PreviewCallout>Python SDK support</PreviewCallout>
<ReviewCallout>Pricing model — subject to change</ReviewCallout>

Media

Purpose: Image with optional caption.
<Image src="/snippets/assets/diagrams/gateway-flow.png" alt="Gateway flow" caption="Traffic routing through the gateway layer" />
PropTypeDefaultDescription
srcstringImage URL
altstringAlt text
captionstringCaption displayed below image
Purpose: Clickable image that links to a URL.
<LinkImage src="/snippets/assets/logos/Livepeer-Logo-Symbol.svg" href="https://livepeer.org" alt="Livepeer" />
PropTypeDefaultDescription
srcstringImage URL
hrefstring#Link target
altstringAlt text
Purpose: Embedded YouTube player (16:9 aspect ratio).
<YouTubeVideo id="dQw4w9WgXcQ" />
PropTypeDefaultDescription
idstringYouTube video ID
videoIdstringAlias for id
Purpose: Video or media block with a heading label.
<TitledVideo title="Gateway Demo">
  <video src="/assets/demo.mp4" controls />
</TitledVideo>
PropTypeDefaultDescription
titlestringHeading displayed above the media

Steps

Purpose: Custom step list with numbered indicators and a connecting line.
<StyledSteps>
  <StyledStep title="Configure your node">Edit the config file.</StyledStep>
  <StyledStep title="Start the service">Run `systemctl start livepeer`.</StyledStep>
</StyledSteps>
StyledStep props:
PropTypeDefaultDescription
titlestringStep heading
Purpose: Alias for StyledSteps. Same rendering, same props.
<ListSteps>
  <StyledStep title="Step one">Content</StyledStep>
</ListSteps>

Grids & Layouts

Purpose: Two-column grid (2×n). No props.
<QuadGrid>
  <Card title="A" />
  <Card title="B" />
</QuadGrid>
Purpose: Flex column wrappers for custom accordion arrangements. No props.
<AccordionLayout>
  <Accordion title="Item">Content</Accordion>
</AccordionLayout>

Tables

Purpose: Scrollable table wrapper. No props — pass raw HTML table elements as children.
<DynamicTable>
  <tr><th>Column</th></tr>
</DynamicTable>
Purpose: Filterable table — live text search and category dropdown. Fully interactive in deployed Mintlify (uses useState). Renders as a static mock in the preview: non-functional search bar + category dropdown chrome + full table body. An ⚡ interactive — static in preview badge is shown to set expectations.
<SearchTable searchPlaceholder="Search terms…" categoryLabel="All categories">
  <tr><th>Term</th><th>Definition</th></tr>
  <tr><td>Example</td><td>Description</td></tr>
</SearchTable>
PropTypeDefaultPurpose
searchPlaceholderstringSearch…Placeholder text in the search input
categoryLabelstringAll categoriesLabel for the category dropdown
searchColumnsarrayColumns to include in search (passed to SearchTable.jsx)
SEO note: Custom components render client-side only — table rows are not in the initial HTML. Acceptable trade-off for reference/glossary pages.

Hero & Portal Scaffolding

Purpose: Structural wrappers for full-width portal and hero layouts on FrameMode pages. No props on any of these.
<HeroSectionContainer>
  <HeroContentContainer>
    <PortalHeroContent>Hero copy here.</PortalHeroContent>
  </HeroContentContainer>
</HeroSectionContainer>

Icons & Indicators

Purpose: Inline Livepeer brand indicator badge. No props.
<LivepeerIcon />
Purpose: Animated icon badge (animation runs in Mintlify; static badge in preview).
<BlinkingIcon icon="terminal" />
PropTypeDefaultDescription
iconstringterminalFont Awesome icon name
Purpose: Inline download link styled as a button.
<DownloadButton href="/assets/config.yaml" label="Download config" />
PropTypeDefaultDescription
hrefstring#Download URL
labelstringDownloadButton label text

Embeds & Social

Purpose: Bordered container for embedded markdown content. No props.
<MarkdownEmbed>Embedded content here.</MarkdownEmbed>
Purpose: Framed container for external or imported content with a title header.
<ExternalContent title="API Response">Response body here.</ExternalContent>
PropTypeDefaultDescription
titlestringExternal ContentHeader label

Math

Purpose: Mathematical expression display. Rendered as styled <code> in preview (not LaTeX-rendered).
<MathInline expression="E = mc^2" />
<MathBlock expression="\sum_{i=1}^{n} x_i" />
PropTypeDefaultDescription
expressionstringMathematical expression string
mathstringAlias for expression

Tier 3 — Placeholder Rendering

Any component not in Tier 1 or Tier 2 renders as:
┌ - - - - - - - - - - - - - - ┐
  &lt;ComponentName prop="value"&gt;
  [children rendered here]
└ - - - - - - - - - - - - - - ┘
Props are shown truncated to 40 characters. Children render inside so content remains readable. To promote a component from Tier 3 to Tier 2, add a renderer function to lib/component-map.js under the livepeerComponents object.

Interactive components

Some Tier 2 components use React hooks (useState, useEffect) and are fully interactive in deployed Mintlify but cannot run in the VS Code webview (Node.js environment, no React runtime). These render as static mocks — they show the correct UI chrome but do not respond to input. Currently affected: SearchTable, SocialLinks, MarkdownEmbed, and OpenAPI. These components display an ⚡ interactive — static in preview badge so authors know to verify their behaviour in a Mintlify dev session, not just the local preview. Data-feed integrators (ShowcaseCards, CoinGeckoExchanges, LumaEvents, DiscordAnnouncements, TwitterTimeline) fall to Tier 3 placeholders for the same reason — they fetch live data and cannot be meaningfully mocked offline.

Configuration

SettingTypeDefaultOptionsDescription
livepeer.mdxPreview.themestringautoauto | light | darkPreview colour theme. auto matches VS Code.

Architecture

Registers two commands (livepeer.openMdxPreview, livepeer.openMdxPreviewToSide), the Cmd+Shift+V keybinding, and two event listeners (document change → debounced re-render; theme change → full re-render). Manages a Map of open panels keyed by document URI.
Parses raw MDX text into a flat array of typed segments: frontmatter, import, markdown, mermaid, codeblock, jsx, jsx-expression. Regex-based extraction — not a full MDX compiler, for robustness with non-standard MDX.
Functions for the governed Mintlify built-ins used by this repo, including canonical Tree.Folder / Tree.File rendering and descriptive placeholders where full runtime parity is not practical in the preview. Each function signature: (props, childrenHtml) => htmlString.
Livepeer custom component renderers. Merges with Tier 1 into COMPONENT_MAP. Exports renderSegments() which dispatches each segment to its renderer or falls through to renderPlaceholder().
Builds the full webview HTML document including CSP headers, Font Awesome CDN (fa 6.5.1), local CSS/JS vscode-resource: URIs, and the rendered body.
Runs inside the webview. Initialises Mermaid.js with Livepeer theme vars, processes .lpd-md-raw blocks with markdown-it, and handles tab switching interactions.
CSS custom properties for light and dark themes using the governed Livepeer accent variables from style.css. Styles for all Tier 1 and Tier 2 component classes.

Future Features

renderSegments() returns { html, hasMermaid }. The webview template conditionally injects <script src="${mermaidUri}"> only when hasMermaid is true. Pages without Mermaid diagrams skip the 2.45 MB bundle entirely.
Any component that falls through to the placeholder renderer can be promoted to Tier 2 by adding a renderer function to lib/component-map.js under livepeerComponents. The function signature is (props, childrenHtml) => htmlString.Priority candidates based on usage frequency in the repo:
  • Snippet (used in composable page patterns)
  • data-feed integrators that could be represented by stable static mocks
  • additional low-use repo components that currently fall through to generic placeholders
Code blocks are currently rendered as plain <pre><code> without syntax colouring. Adding highlight.js (vendored, ~50 KB minified) as a third media asset and calling hljs.highlightAll() in preview.js would add language-aware colouring for all fenced blocks.
Sync the scroll position of the preview panel to the cursor position in the editor. VS Code’s built-in Markdown preview uses editor/scroll messages via the webview messaging API. The same pattern can be applied here: on onDidChangeTextEditorVisibleRanges, post a message to the webview to scroll to the corresponding rendered element.
onDidSaveTextDocument watches for any .jsx save in the workspace and re-renders all open preview panels. Component source changes are reflected immediately without touching the .mdx file.
Multi-line {/* ... */} comment blocks — typically template instructions or reviewer flags — are rendered as collapsed <details> elements instead of leaking into the preview as visible text. Single-line {/* ... */} comments are still silently stripped. The collapsible uses a dashed border and muted palette to distinguish it from content.Parser: lib/mdx-parser.js detects {/* without a same-line */} and collects lines until the closing */}, emitting a jsx-comment segment.Renderer: lib/component-map.js handles jsx-comment as <details class="lpd-comment"> with a 💬 comment summary and pre-formatted body.

References

Extension source

Entry point and command registration.

Component map

Tier 2 renderers and dispatch logic.

Authoring tools

Other VS Code tools in this repo.
Last modified on April 8, 2026