@fsegurai/marked-extended-embeds
v17.0.0
Published
Marked extension for embedding rich media from various platforms
Maintainers
Readme
An extension library for Marked.js to enhance Markdown rendering.
@fsegurai/marked-extended-embeds Marked extension for embedding rich media from various platforms
🎯 Overview
The marked-extended-embeds extension transforms your Markdown documents into rich, interactive experiences by seamlessly embedding content from 18+ platforms. With automatic platform detection, responsive layouts, privacy controls, and extensive customization options, it's perfect for documentation, blogs, portfolios, and educational content.
✨ Key Features
- 🎬 18+ Supported Platforms: YouTube, Vimeo, CodePen, Spotify, Twitter, Figma, and more
- 🔍 Auto-Detection: Automatically identifies platforms from URLs
- 📐 Responsive Design: Automatic aspect ratio handling (16:9, 4:3, 1:1, 21:9, custom)
- 🔒 Privacy First: Privacy-enhanced embeds with no-cookie domains
- 🛡️ Security Built-in: Iframe sandboxing with configurable permissions
- ⚡ Performance Optimized: Lazy loading support for better page speed
- 🎨 Fully Customizable: Custom templates, themes, and styling
- ♿ Accessible: Semantic HTML with ARIA attributes
- 📱 Mobile Friendly: Responsive layouts that work on all devices
- 🎛️ Provider Configs: Per-provider customization options
- 🪝 Callback Hooks: onLoad and onError event handlers
- 🔧 Framework Agnostic: Works with React, Vue, Angular, Svelte, and vanilla JS
🎪 Live Demo
Experience all 18+ embed types in action: View Demo
Table of contents
Installation
To add @fsegurai/marked-extended-embeds along with Marked.js to your package.json use the following commands.
bun install @fsegurai/marked-extended-embeds marked@>=17.0.0 --saveUsage
Basic Usage
Import @fsegurai/marked-extended-embeds and apply it to your Marked instance as shown below.
Quick Start
Basic Syntax
The extension automatically detects the platform from URLs or you can explicitly specify the provider:
::::embed{title="My Video" aspectRatio="16:9"}
https://www.youtube.com/watch?v=dQw4w9WgXcQ
::::embedendimport { marked } from 'marked';
import markedExtendedEmbeds from '@fsegurai/marked-extended-embeds';
// or UMD script
// <script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.js"></script>
// <script src="https://cdn.jsdelivr.net/npm/@fsegurai/marked-extended-embeds/lib/index.umd.js"></script>
marked.use(markedExtendedEmbeds());
### Installation
Install the package using your preferred package manager:
```bash
# Using Bun (recommended)
bun add @fsegurai/marked-extended-embeds
# Using npm
npm install @fsegurai/marked-extended-embeds
# Using yarn
yarn add @fsegurai/marked-extended-embeds
# Using pnpm
pnpm add @fsegurai/marked-extended-embedsBasic Implementation
import { marked } from 'marked';
import markedExtendedEmbeds from '@fsegurai/marked-extended-embeds';
// Import styles (required for functionality)
import '@fsegurai/marked-extended-embeds/styles/embed.css';
// Optional: Import theme for styled appearance
import '@fsegurai/marked-extended-embeds/styles/embed-theme.css';
// Register the extension
marked.use(markedExtendedEmbeds());
// Your markdown content with embeds
const markdown = `
# My Document
## Video Tutorial
::::embed{title="Getting Started" aspectRatio="16:9"}
https://www.youtube.com/watch?v=dQw4w9WgXcQ
::::embedend
## Code Example
::::embed{provider="codepen" theme="dark"}
https://codepen.io/username/pen/abc123
::::embedend
## Music Playlist
::::embed{title="Coding Playlist"}
https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M
::::embedend
`;
// Parse and render
const html = marked.parse(markdown);
console.log(html);Syntax & Usage
Basic Embed Syntax
::::embed{properties}
URL or content
::::embedendAuto-Detection Examples
The extension automatically detects the platform from the URL:
<!-- YouTube video -->
::::embed{title="Tutorial Video"}
https://www.youtube.com/watch?v=dQw4w9WgXcQ
::::embedend
<!-- Vimeo video -->
::::embed{}
https://vimeo.com/123456789
::::embedend
<!-- CodePen pen -->
::::embed{}
https://codepen.io/username/pen/abc123
::::embedend
<!-- Twitter/X post -->
::::embed{}
https://twitter.com/username/status/1234567890
::::embedend
<!-- Spotify track -->
::::embed{}
https://open.spotify.com/track/abc123
::::embedend
<!-- GitHub Gist -->
::::embed{}
https://gist.github.com/username/abc123def456
::::embedendExplicit Provider
You can explicitly specify the provider:
::::embed{provider="youtube" title="My Video"}
https://www.youtube.com/watch?v=abc123
::::embedend
::::embed{provider="mermaid" title="System Architecture"}
graph TD;
A[Client] --> B[Server];
B --> C[Database];
::::embedend
::::embed{provider="pdf" title="Documentation"}
https://example.com/document.pdf
::::embedendCommon Property Examples
<!-- With aspect ratio -->
::::embed{aspectRatio="16:9"}
https://www.youtube.com/watch?v=abc123
::::embedend
<!-- With custom dimensions -->
::::embed{width="800px" height="600px"}
https://www.vimeo.com/123456789
::::embedend
<!-- With autoplay and muted -->
::::embed{autoplay="true" muted="true"}
https://www.youtube.com/watch?v=abc123
::::embedend
<!-- With privacy mode -->
::::embed{privacyMode="true"}
https://www.youtube.com/watch?v=abc123
::::embedend
<!-- With theme -->
::::embed{theme="dark"}
https://codepen.io/username/pen/abc123
::::embedend
<!-- With custom start time -->
::::embed{startTime="30"}
https://www.youtube.com/watch?v=abc123
::::embedend
The extension automatically detects providers, applies responsive layouts with proper aspect ratios, and includes
accessibility features. All embeds support lazy loading, sandbox security, and customizable styling.
### Importing Styles
This extension **does not automatically inject styles**. You must import the CSS or SCSS files manually to ensure proper styling.
#### Option 1: Using CSS (Recommended for most projects)
```javascript
// Import minimal structural styles (required for functionality)
import '@fsegurai/marked-extended-embeds/styles/embed.css';
// Optional: Import the basic theme
import '@fsegurai/marked-extended-embeds/styles/embed-theme.css';Or using CDN (vanilla HTML/JavaScript projects):
<!-- Latest version -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fsegurai/marked-extended-embeds@latest/dist/styles/embed.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fsegurai/marked-extended-embeds@latest/dist/styles/embed-theme.css">
<!-- Or lock to a specific version (recommended for production) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fsegurai/[email protected]/dist/styles/embed.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fsegurai/[email protected]/dist/styles/embed-theme.css">Option 2: Using SCSS (For customization)
// Import structural styles with customizable variables
@import '@fsegurai/marked-extended-embeds/styles/embed.scss';
// Optional: Import theme with customizable variables
@import '@fsegurai/marked-extended-embeds/styles/embed-theme.scss';
// Customize SCSS variables before importing (optional)
$embed-border-radius: 12px;
$embed-box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
@import '@fsegurai/marked-extended-embeds/styles/embed-theme.scss';Angular Integration
For Angular projects using ngx-markdown:
// In your angular.json, add the CSS file to styles array:
{
"styles": [
"node_modules/@fsegurai/marked-extended-embeds/dist/styles/embed.css",
"node_modules/@fsegurai/marked-extended-embeds/dist/styles/embed-theme.css",
// ... other styles
]
}
// Or import in your global styles.scss:
@import '@fsegurai/marked-extended-embeds/styles/embed.scss';
@import '@fsegurai/marked-extended-embeds/styles/embed-theme.scss';Styling Your Embeds
This extension follows a separation of concerns approach. The extension generates semantic HTML with CSS classes, while styles are imported separately, giving you full control over the visual appearance.
Generated HTML Structure
<div class="marked-extended-embed-container"
data-provider="youtube"
data-embed-id="dQw4w9WgXcQ">
<!-- Loading state -->
<div class="marked-extended-embed-loading">
<div class="marked-extended-embed-spinner"></div>
<p>Loading...</p>
</div>
<!-- Embedded content (iframe) -->
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
loading="lazy"
sandbox="allow-scripts allow-same-origin"
allowfullscreen>
</iframe>
<!-- Optional caption -->
<div class="marked-extended-embed-caption">
Video description
</div>
</div>CSS Classes Reference
| Class | Purpose | Element |
|-------|---------|---------|
| .marked-extended-embed-container | Main wrapper | Container |
| .marked-extended-embed-container[data-provider="youtube"] | Provider-specific styling | Container |
| .marked-extended-embed-loading | Loading state overlay | Loading div |
| .marked-extended-embed-spinner | Loading spinner animation | Spinner div |
| .marked-extended-embed-loading-text | "Loading..." text | Text element |
| .marked-extended-embed-caption | Caption/description | Caption div |
| iframe | Embedded content | Iframe |
Complete Styling Example
/* Base Container */
.marked-extended-embed-container {
position: relative;
width: 100%;
margin: 1.5rem 0;
border-radius: 8px;
overflow: hidden;
background: #f5f5f5;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* Responsive aspect ratio wrapper */
.marked-extended-embed-container::before {
content: '';
display: block;
padding-top: 56.25%; /* 16:9 aspect ratio */
}
.marked-extended-embed-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
/* Loading State */
.marked-extended-embed-loading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.05);
z-index: 1;
}
.marked-extended-embed-spinner {
width: 40px;
height: 40px;
border: 4px solid #e0e0e0;
border-top-color: #2196F3;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.marked-extended-embed-loading-text {
margin-top: 1rem;
font-size: 0.875rem;
font-weight: 500;
color: #666;
}
/* Caption Styling */
.marked-extended-embed-caption {
padding: 0.75rem 1rem;
font-size: 0.875rem;
color: #666;
background: #fafafa;
border-top: 1px solid #e0e0e0;
text-align: center;
}
/* Provider-Specific Backgrounds */
.marked-extended-embed-container[data-provider="youtube"] {
background: #000;
}
.marked-extended-embed-container[data-provider="vimeo"] {
background: #000;
}
.marked-extended-embed-container[data-provider="codepen"] {
background: #1e1e1e;
}
.marked-extended-embed-container[data-provider="codesandbox"] {
background: #151515;
}
.marked-extended-embed-container[data-provider="spotify"] {
background: #191414;
}
.marked-extended-embed-container[data-provider="soundcloud"] {
background: #ff5500;
}
.marked-extended-embed-container[data-provider="twitter"] {
background: #fff;
}
.marked-extended-embed-container[data-provider="github-gist"] {
background: #f6f8fa;
}
.marked-extended-embed-container[data-provider="figma"] {
background: #fff;
}
.marked-extended-embed-container[data-provider="mermaid"] {
background: #fff;
padding: 1rem;
}
.marked-extended-embed-container[data-provider="pdf"] {
background: #525659;
}
.marked-extended-embed-container[data-provider="loom"] {
background: #000;
}
.marked-extended-embed-container[data-provider="miro"] {
background: #fff;
}
.marked-extended-embed-container[data-provider="excalidraw"] {
background: #fff;
}
.marked-extended-embed-container[data-provider="drawio"],
.marked-extended-embed-container[data-provider="diagrams-net"] {
background: #fff;
}Dark Mode Support
/* Light theme */
body.light .marked-extended-embed-container {
background: #f5f5f5;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
body.light .marked-extended-embed-caption {
background: #fafafa;
color: #666;
border-top-color: #e0e0e0;
}
body.light .marked-extended-embed-loading {
color: #666;
}
body.light .marked-extended-embed-spinner {
border-color: #e0e0e0;
border-top-color: #2196F3;
}
/* Dark theme */
body.dark .marked-extended-embed-container {
background: #1e1e1e;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
body.dark .marked-extended-embed-caption {
background: #2a2a2a;
color: #e0e0e0;
border-top-color: #3a3a3a;
}
body.dark .marked-extended-embed-loading {
color: #ccc;
}
body.dark .marked-extended-embed-spinner {
border-color: #333;
border-top-color: #2196F3;
}
/* Provider-specific dark mode overrides */
body.dark .marked-extended-embed-container[data-provider="mermaid"],
body.dark .marked-extended-embed-container[data-provider="twitter"],
body.dark .marked-extended-embed-container[data-provider="figma"],
body.dark .marked-extended-embed-container[data-provider="miro"],
body.dark .marked-extended-embed-container[data-provider="excalidraw"],
body.dark .marked-extended-embed-container[data-provider="drawio"],
body.dark .marked-extended-embed-container[data-provider="diagrams-net"] {
background: #1e1e1e;
}
body.dark .marked-extended-embed-container[data-provider="github-gist"] {
background: #0d1117;
}Custom Aspect Ratios
/* 16:9 (default - videos) */
.marked-extended-embed-container[data-provider="youtube"]::before,
.marked-extended-embed-container[data-provider="vimeo"]::before,
.marked-extended-embed-container[data-provider="loom"]::before {
padding-top: 56.25%;
}
/* 1:1 (square - music players) */
.marked-extended-embed-container[data-provider="spotify"]::before,
.marked-extended-embed-container[data-provider="soundcloud"]::before {
padding-top: 100%;
}
/* 4:3 (presentations) */
.marked-extended-embed-container[data-provider="google-slides"]::before {
padding-top: 75%;
}
/* Full height (code editors) */
.marked-extended-embed-container[data-provider="codepen"]::before,
.marked-extended-embed-container[data-provider="codesandbox"]::before,
.marked-extended-embed-container[data-provider="replit"]::before {
padding-top: 100%;
min-height: 500px;
}Responsive Adjustments
@media (max-width: 768px) {
.marked-extended-embed-container {
margin: 1rem 0;
border-radius: 4px;
}
.marked-extended-embed-caption {
padding: 0.5rem 0.75rem;
font-size: 0.8125rem;
}
/* Reduce height for code editors on mobile */
.marked-extended-embed-container[data-provider="codepen"]::before,
.marked-extended-embed-container[data-provider="codesandbox"]::before {
min-height: 400px;
}
}Print Styles
@media print {
.marked-extended-embed-container {
break-inside: avoid;
box-shadow: none;
border: 1px solid #ccc;
}
.marked-extended-embed-loading {
display: none;
}
/* Show caption */
.marked-extended-embed-caption {
display: block !important;
}
/* Hide interactive embeds, show placeholder */
.marked-extended-embed-container iframe {
display: none;
}
.marked-extended-embed-container::after {
content: "Embedded content: " attr(data-provider);
display: block;
padding: 2rem;
text-align: center;
color: #666;
font-style: italic;
}
}Copy Demo Theme
For complete styling with all 18+ providers: embed-theme.css
Check the demo to see all embed types in action.
Supported Platforms
The extension supports 18 different platforms with automatic URL detection:
| Platform | Type | Example URL |
|------------------|---------------|-------------------------------|
| YouTube | Video | youtube.com/watch?v=... |
| Vimeo | Video | vimeo.com/123456789 |
| CodePen | Code | codepen.io/user/pen/abc |
| CodeSandbox | Code | codesandbox.io/s/abc |
| Twitter | Social | twitter.com/user/status/123 |
| GitHub Gist | Code | gist.github.com/user/abc |
| Spotify | Music | open.spotify.com/track/abc |
| SoundCloud | Music | soundcloud.com/user/track |
| SlideShare | Presentation | slideshare.net/user/slides |
| Figma | Design | figma.com/file/abc |
| Loom | Video | loom.com/share/abc |
| Miro | Collaboration | miro.com/app/board/abc |
| Mermaid | Diagram | mermaid://graph TD; A-->B; |
| Excalidraw | Drawing | excalidraw.com/#room=abc |
| Draw.io | Diagram | diagrams.net/... |
| Diagrams.net | Diagram | diagrams.net/... |
| PDF | Document | Any PDF URL |
| iframe | Generic | Any URL |
Each platform is automatically detected from the URL pattern. You can also explicitly specify the provider using the
provider property.
Configuration Options
The marked-extended-embeds extension accepts the following configuration options:
className: The base CSS class name for embeds. Defaults to 'marked-extended-embed.'prefixId: The prefix ID for embed elements. Defaults to 'embed'.defaultAspectRatio: Default aspect ratio ('16:9', '4:3', '1:1', '21:9'). Defaults to '16:9'.allowFullscreen: Allow fullscreen mode for embeds. Defaults to true.lazyLoad: Enable lazy loading for better performance. Defaults to true.privacyMode: Use privacy-enhanced domains when available. Defaults to false.template: Custom HTML template for rendering embeds. Defaults to built-in template.customizeToken: Function to customize embed tokens. Defaults to null.enableSandbox: Enable iframe sandbox for security. Defaults to true. to ['allow-scripts', 'allow-same-origin', 'allow-popups', 'allow-presentation'].providers: Custom provider configurations. Defaults to {}.onEmbedLoad: Callback when embed loads. Defaults to null.onEmbedError: Callback when embed fails. Defaults to null.
Note: For styling, import CSS/SCSS files manually (see Importing Styles).
Embed Properties
Embed syntax supports the following properties:
Common Properties:
title: Title/caption for the embedaspectRatio: Aspect ratio ('16:9', '4:3', '1:1', '21:9', 'custom')width: Custom width (e.g., '800px')height: Custom height (e.g., '600px')provider: Explicit provider nameid: Custom identifier for the embedclassName: Custom CSS class
Media Properties:
autoplay: Auto-play video/audio (boolean)muted: Start muted (boolean)loop: Loop playback (boolean)startTime: Start time for videos (e.g., '30')controls: Show media controls (boolean)
Display Properties:
lazyLoad: Enable lazy loading (boolean)allowFullscreen: Allow fullscreen mode (boolean)privacyMode: Use privacy-enhanced mode (boolean)theme: Theme setting ('light', 'dark')
Custom Parameters:
param-*: Any property starting with 'param-' becomes a URL parameter
Advanced Examples
YouTube Video with Options
::::embed{
title="Tutorial Video"
aspectRatio="16:9"
autoplay="true"
muted="true"
startTime="30"
privacyMode="true"
}
https://www.youtube.com/watch?v=dQw4w9WgXcQ
::::embedendCodePen with Dark Theme
::::embed{title="Interactive Demo" theme="dark"}
https://codepen.io/username/pen/abc123
::::embedendResponsive Design File
::::embed{
title="Mobile App Design"
aspectRatio="4:3"
width="100%"
}
https://www.figma.com/file/abc123/Mobile-App
::::embedendSpotify Playlist
::::embed{title="Coding Playlist" aspectRatio="1:1"}
https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M
::::embedendCodeSandbox Project
::::embed{
title="React Example"
theme="dark"
param-view="preview"
}
https://codesandbox.io/s/react-new
::::embedendMermaid Diagram
::::embed{provider="mermaid" title="System Architecture"}
mermaid://graph TD;
A[Client] -->|Request| B[API Gateway];
B --> C[Service 1];
B --> D[Service 2];
C --> E[Database];
D --> E;
::::embedendPDF Document
::::embed{
provider="pdf"
title="Technical Specification"
aspectRatio="4:3"
}
https://example.com/document.pdf
::::embedendLoom Video with Custom Size
::::embed{
title="Product Demo"
width="800px"
height="450px"
}
https://www.loom.com/share/abc123
::::embedendTwitter Thread
::::embed{title="Latest Announcement"}
https://twitter.com/username/status/1234567890
::::embedendGitHub Gist
::::embed{title="Code Snippet"}
https://gist.github.com/username/abc123
::::embedendExcalidraw Sketch
::::embed{title="Whiteboard Sketch" aspectRatio="16:9"}
https://excalidraw.com/#room=abc123,def456
::::embedendDraw.io Diagram
::::embed{provider="drawio" title="System Architecture" aspectRatio="4:3"}
https://viewer.diagrams.net/?highlight=0000ff&edit=_blank&title=diagram
::::embedendPrivacy Mode
Enable privacy-enhanced embedding for supported platforms:
::::embed{privacyMode="true"}
https://www.youtube.com/watch?v=abc123
::::embedendThis uses youtube-nocookie.com for YouTube and similar privacy-focused domains when available.
Custom Provider Configuration
Configure provider-specific settings:
marked.use(markedExtendedEmbeds({
providers: {
youtube: {
domain: 'www.youtube-nocookie.com',
params: {
rel: '0',
modestbranding: '1',
},
},
spotify: {
params: {
theme: '0',
},
},
},
}));Security Features
The extension includes several security features:
- Sandbox by default: Iframes use sandbox attribute with restricted permissions
- Configurable permissions: Customize allowed iframe capabilities
- Privacy mode: Use no-cookie domains when available
- XSS protection: All user input is escaped
- HTTPS enforcement: Embed URLs use secure protocols
Accessibility
All embeds include proper accessibility features:
- Semantic HTML with proper roles
- Alt text and titles
- Keyboard navigation support
- Screen reader compatibility
- Loading states with aria-labels
Platform-Specific Guides
Video Platforms
YouTube
<!-- Basic -->
::::embed{}
https://www.youtube.com/watch?v=dQw4w9WgXcQ
::::embedend
<!-- With options -->
::::embed{
autoplay="true"
muted="true"
startTime="30"
privacyMode="true"
param-rel="0"
param-modestbranding="1"
}
https://www.youtube.com/watch?v=dQw4w9WgXcQ
::::embedend
<!-- YouTube Shorts -->
::::embed{}
https://www.youtube.com/shorts/abc123
::::embedendVimeo
<!-- Basic -->
::::embed{}
https://vimeo.com/123456789
::::embedend
<!-- With options -->
::::embed{
autoplay="true"
loop="true"
param-color="ff0000"
param-portrait="0"
}
https://vimeo.com/123456789
::::embedendLoom
::::embed{title="Product Demo"}
https://www.loom.com/share/abc123def456
::::embedendCode Platforms
CodePen
<!-- Basic -->
::::embed{}
https://codepen.io/username/pen/abc123
::::embedend
<!-- With theme -->
::::embed{
theme="dark"
param-default-tab="js,result"
param-editable="true"
}
https://codepen.io/username/pen/abc123
::::embedendCodeSandbox
::::embed{
theme="dark"
param-view="preview"
param-hidenavigation="1"
}
https://codesandbox.io/s/react-new
::::embedendGitHub Gist
::::embed{title="Code Snippet"}
https://gist.github.com/username/abc123def456
::::embedendMusic & Audio Platforms
Spotify
<!-- Track -->
::::embed{aspectRatio="1:1"}
https://open.spotify.com/track/abc123
::::embedend
<!-- Album -->
::::embed{}
https://open.spotify.com/album/abc123
::::embedend
<!-- Playlist -->
::::embed{}
https://open.spotify.com/playlist/abc123
::::embedend
<!-- Podcast -->
::::embed{}
https://open.spotify.com/episode/abc123
::::embedendSoundCloud
::::embed{param-color="ff5500"}
https://soundcloud.com/artist/track-name
::::embedendSocial Media
Twitter/X
::::embed{title="Latest Announcement"}
https://twitter.com/username/status/1234567890
::::embedend
<!-- Also supports X.com URLs -->
::::embed{}
https://x.com/username/status/1234567890
::::embedendDesign & Collaboration Tools
Figma
::::embed{
title="Mobile App Design"
aspectRatio="4:3"
}
https://www.figma.com/file/abc123/Project-Name
::::embedend
<!-- Prototype view -->
::::embed{}
https://www.figma.com/proto/abc123
::::embedendMiro
::::embed{title="Brainstorm Board"}
https://miro.com/app/board/abc123
::::embedendExcalidraw
::::embed{title="Whiteboard Sketch"}
https://excalidraw.com/#room=abc123,def456
::::embedendDiagram Tools
Mermaid
::::embed{provider="mermaid" title="System Flow"}
graph TD;
A[Start] --> B{Decision};
B -->|Yes| C[Process 1];
B -->|No| D[Process 2];
C --> E[End];
D --> E;
::::embedend
<!-- Sequence diagram -->
::::embed{provider="mermaid"}
sequenceDiagram
Alice->>John: Hello John!
John-->>Alice: Hi Alice!
::::embedend
<!-- Gantt chart -->
::::embed{provider="mermaid"}
gantt
title Project Timeline
section Phase 1
Task 1: 2026-01-01, 30d
Task 2: 2026-02-01, 20d
::::embedendDraw.io / Diagrams.net
::::embed{provider="drawio" aspectRatio="4:3"}
https://viewer.diagrams.net/?highlight=0000ff&title=architecture
::::embedendDocuments
::::embed{
provider="pdf"
title="Technical Specification"
aspectRatio="4:3"
}
https://example.com/documents/spec.pdf
::::embedendSlideShare
::::embed{title="Conference Presentation"}
https://www.slideshare.net/username/presentation-title
::::embedendGeneric Iframe
Custom Embed
::::embed{
provider="iframe"
title="Custom Content"
aspectRatio="16:9"
}
https://example.com/custom-embed
::::embedendBest Practices
1. Choose Appropriate Aspect Ratios
Match aspect ratio to content type:
<!-- Videos: 16:9 (default) -->
::::embed{aspectRatio="16:9"}
https://www.youtube.com/watch?v=abc123
::::embedend
<!-- Presentations: 4:3 -->
::::embed{aspectRatio="4:3"}
https://www.slideshare.net/user/presentation
::::embedend
<!-- Music players: 1:1 (square) -->
::::embed{aspectRatio="1:1"}
https://open.spotify.com/track/abc123
::::embedend
<!-- Ultra-wide content: 21:9 -->
::::embed{aspectRatio="21:9"}
https://example.com/ultrawide-content
::::embedend2. Use Privacy Mode for User Privacy
Enable privacy mode for platforms that support it:
<!-- Good: Privacy-conscious -->
::::embed{privacyMode="true"}
https://www.youtube.com/watch?v=abc123
::::embedend
<!-- Uses youtube-nocookie.com instead of youtube.com -->3. Implement Lazy Loading
Improve page performance with lazy loading:
marked.use(markedExtendedEmbeds({
lazyLoad: true // Default is true
}));<!-- Lazy loading enabled by default -->
::::embed{}
https://www.youtube.com/watch?v=abc123
::::embedend
<!-- Explicitly disable for above-the-fold content -->
::::embed{lazyLoad="false"}
https://www.youtube.com/watch?v=abc123
::::embedend4. Add Meaningful Titles
Always provide descriptive titles for accessibility:
<!-- Good: Descriptive title -->
::::embed{title="React Hooks Tutorial - Complete Guide"}
https://www.youtube.com/watch?v=abc123
::::embedend
<!-- Avoid: Generic title -->
::::embed{title="Video"}
https://www.youtube.com/watch?v=abc123
::::embedend5. Control Autoplay Wisely
Use autoplay sparingly and always with muted:
<!-- Good: Autoplay with muted -->
::::embed{autoplay="true" muted="true"}
https://www.youtube.com/watch?v=abc123
::::embedend
<!-- Avoid: Autoplay without muted (poor UX) -->
::::embed{autoplay="true"}
https://www.youtube.com/watch?v=abc123
::::embedend6. Optimize for Mobile
Test embeds on mobile devices:
/* Add mobile-specific styles */
@media (max-width: 768px) {
.marked-extended-embed-container {
margin: 1rem 0;
}
/* Reduce height for code editors on mobile */
.marked-extended-embed-container[data-provider="codepen"],
.marked-extended-embed-container[data-provider="codesandbox"] {
min-height: 400px;
}
}7. Use Sandbox for Security
Keep sandbox enabled (default) for security:
// Good: Sandbox enabled (default)
marked.use(markedExtendedEmbeds({
enableSandbox: true,
sandboxPermissions: [
'allow-scripts',
'allow-same-origin',
'allow-popups',
'allow-presentation'
]
}));
// Only disable if absolutely necessaryTroubleshooting
Embeds Not Displaying
Problem: Embeds don't appear on the page.
Solutions:
- Verify syntax is correct:
<!-- Correct -->
::::embed{}
https://www.youtube.com/watch?v=abc123
::::embedend
<!-- Wrong -->
:::embed{} <!-- ❌ Missing colon -->
https://www.youtube.com/watch?v=abc123
:::embedend- Check if provider is supported:
// List of supported providers
const supported = [
'youtube', 'vimeo', 'twitter', 'codepen', 'codesandbox',
'github-gist', 'spotify', 'soundcloud', 'slideshare',
'figma', 'loom', 'miro', 'mermaid', 'excalidraw',
'drawio', 'diagrams-net', 'pdf', 'iframe'
];- Verify URL format:
<!-- YouTube - multiple formats supported -->
https://www.youtube.com/watch?v=abc123 ✅
https://youtu.be/abc123 ✅
https://www.youtube.com/embed/abc123 ✅
https://www.youtube.com/shorts/abc123 ✅- Import styles:
import '@fsegurai/marked-extended-embeds/styles/embed.css';Styling Not Applied
Problem: Embeds appear unstyled or broken.
Solutions:
- Import CSS files:
import '@fsegurai/marked-extended-embeds/styles/embed.css';
import '@fsegurai/marked-extended-embeds/styles/embed-theme.css';Check CSS file paths in build config
Verify CSS is loaded (check DevTools Network tab)
For CDN usage, check for 404 errors
Incorrect Aspect Ratio
Problem: Embed appears stretched or squashed.
Solution:
<!-- Specify correct aspect ratio -->
::::embed{aspectRatio="16:9"} <!-- Videos -->
https://www.youtube.com/watch?v=abc123
::::embedend
::::embed{aspectRatio="1:1"} <!-- Music players -->
https://open.spotify.com/track/abc123
::::embedend
::::embed{aspectRatio="4:3"} <!-- Presentations -->
https://www.slideshare.net/user/slides
::::embedend
<!-- Or use custom dimensions -->
::::embed{width="800px" height="600px"}
https://example.com/content
::::embedendPrivacy Mode Not Working
Problem: Privacy mode doesn't activate.
Solutions:
- Check if provider supports privacy mode:
<!-- Supported: YouTube, Vimeo -->
::::embed{privacyMode="true"}
https://www.youtube.com/watch?v=abc123
::::embedend
<!-- Not all providers support privacy mode -->- Configure globally:
marked.use(markedExtendedEmbeds({
privacyMode: true // Enable for all embeds
}));Lazy Loading Not Working
Problem: All embeds load immediately.
Solutions:
- Ensure lazy loading is enabled:
marked.use(markedExtendedEmbeds({
lazyLoad: true // Default is true
}));- Check browser support:
<!-- Fallback for older browsers -->
<script>
if ('loading' in HTMLIFrameElement.prototype) {
// Lazy loading supported
} else {
// Load polyfill or fallback
}
</script>Autoplay Not Working
Problem: Videos don't autoplay.
Solutions:
- Always use muted with autoplay:
<!-- Modern browsers require muted for autoplay -->
::::embed{autoplay="true" muted="true"}
https://www.youtube.com/watch?v=abc123
::::embedend- Check browser autoplay policies:
- Chrome: Requires muted or user interaction
- Safari: Requires muted
- Firefox: Respects user preferences
Sandbox Restrictions
Problem: Embed functionality limited due to sandbox.
Solution:
// Customize sandbox permissions
marked.use(markedExtendedEmbeds({
enableSandbox: true,
sandboxPermissions: [
'allow-scripts',
'allow-same-origin',
'allow-popups',
'allow-presentation',
'allow-forms', // Add if forms needed
'allow-modals' // Add if modals needed
]
}));Framework Integration
React Integration
// EmbedMarkdown.tsx
import { marked } from 'marked';
import markedExtendedEmbeds from '@fsegurai/marked-extended-embeds';
import '@fsegurai/marked-extended-embeds/styles/embed.css';
import '@fsegurai/marked-extended-embeds/styles/embed-theme.css';
import { useEffect, useState, useCallback } from 'react';
// Configure marked
marked.use(markedExtendedEmbeds({
lazyLoad: true,
privacyMode: true,
onEmbedLoad: (id, provider) => {
console.log(\`Embed loaded: \${provider} (\${id})\`);
},
onEmbedError: (id, provider, error) => {
console.error(\`Embed error: \${provider} (\${id})\`, error);
}
}));
interface Props {
content: string;
enablePrivacy?: boolean;
}
export function EmbedMarkdown({ content, enablePrivacy = true }: Props) {
const [html, setHtml] = useState('');
useEffect(() => {
// Reconfigure based on props
marked.use(markedExtendedEmbeds({
privacyMode: enablePrivacy,
lazyLoad: true
}));
const parsed = marked.parse(content);
setHtml(parsed);
}, [content, enablePrivacy]);
return (
<div
className="markdown-with-embeds"
dangerouslySetInnerHTML={{ __html: html }}
/>
);
}
// Usage:
// <EmbedMarkdown content={markdown} enablePrivacy={true} />Vue 3 Integration
<script setup lang="ts">
import { marked } from 'marked';
import markedExtendedEmbeds from '@fsegurai/marked-extended-embeds';
import '@fsegurai/marked-extended-embeds/styles/embed.css';
import '@fsegurai/marked-extended-embeds/styles/embed-theme.css';
import { computed, watch } from 'vue';
marked.use(markedExtendedEmbeds({
lazyLoad: true,
privacyMode: true,
onEmbedLoad: (id, provider) => {
console.log(\`Loaded: \${provider}\`);
}
}));
interface Props {
content: string;
privacyMode?: boolean;
lazyLoad?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
privacyMode: true,
lazyLoad: true
});
const html = computed(() => {
marked.use(markedExtendedEmbeds({
privacyMode: props.privacyMode,
lazyLoad: props.lazyLoad
}));
return marked.parse(props.content);
});
watch([() => props.privacyMode, () => props.lazyLoad], () => {
console.log('Config changed');
});
</script>
<template>
<div class="markdown-content" v-html="html" />
</template>Angular Integration
// embed-markdown.component.ts
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { marked } from 'marked';
import markedExtendedEmbeds from '@fsegurai/marked-extended-embeds';
marked.use(markedExtendedEmbeds({
lazyLoad: true,
privacyMode: true,
onEmbedLoad: (id, provider) => {
console.log(\`Embed loaded: \${provider}\`);
}
}));
@Component({
selector: 'app-embed-markdown',
template: \`<div [innerHTML]="parsedContent"></div>\`,
styleUrls: [
'../node_modules/@fsegurai/marked-extended-embeds/styles/embed.css',
'../node_modules/@fsegurai/marked-extended-embeds/styles/embed-theme.css'
]
})
export class EmbedMarkdownComponent implements OnChanges {
@Input() content: string = '';
@Input() privacyMode: boolean = true;
@Input() lazyLoad: boolean = true;
parsedContent: SafeHtml = '';
constructor(private sanitizer: DomSanitizer) {}
ngOnChanges(changes: SimpleChanges): void {
if (changes['content'] || changes['privacyMode'] || changes['lazyLoad']) {
marked.use(markedExtendedEmbeds({
privacyMode: this.privacyMode,
lazyLoad: this.lazyLoad
}));
const html = marked.parse(this.content);
this.parsedContent = this.sanitizer.bypassSecurityTrustHtml(html);
}
}
}Svelte Integration
<script lang="ts">
import { marked } from 'marked';
import markedExtendedEmbeds from '@fsegurai/marked-extended-embeds';
import '@fsegurai/marked-extended-embeds/styles/embed.css';
import '@fsegurai/marked-extended-embeds/styles/embed-theme.css';
marked.use(markedExtendedEmbeds({
lazyLoad: true,
privacyMode: true,
onEmbedLoad: (id, provider) => {
console.log(\`Loaded: \${provider}\`);
}
}));
export let content: string;
export let privacyMode: boolean = true;
$: html = (() => {
marked.use(markedExtendedEmbeds({
privacyMode
}));
return marked.parse(content);
})();
</script>
<div class="markdown-content">
{@html html}
</div>Performance Optimization
1. Enable Lazy Loading
marked.use(markedExtendedEmbeds({
lazyLoad: true // Defers loading until visible
}));2. Use Intersection Observer
// Custom intersection observer for advanced control
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const iframe = entry.target.querySelector('iframe');
if (iframe && iframe.dataset.src) {
iframe.src = iframe.dataset.src;
delete iframe.dataset.src;
}
}
});
});
document.querySelectorAll('.marked-extended-embed-container').forEach(embed => {
observer.observe(embed);
});3. Optimize for Multiple Embeds
<!-- Load only above-the-fold embeds immediately -->
::::embed{lazyLoad="false"}
https://www.youtube.com/watch?v=first-video
::::embedend
<!-- Lazy load below-the-fold embeds -->
::::embed{lazyLoad="true"}
https://www.youtube.com/watch?v=second-video
::::embedend
::::embed{lazyLoad="true"}
https://www.youtube.com/watch?v=third-video
::::embedend4. Use Facade Pattern
// Load lightweight preview, full embed on click
marked.use(markedExtendedEmbeds({
customizeToken: (token) => {
// Add click-to-load functionality
token.meta.useFacade = true;
}
}));Contributing
Found a bug or have a feature request? Please open an issue on GitHub.
Related Resources
Available Extensions
| Extension | Package | Version | Description |
|-------------|--------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|----------------------------------------------------------------------|
| All - Bundle | @fsegurai/marked-extended-bundle | | Includes all extensions in a single package for easy integration |
| Accordion | @fsegurai/marked-extended-accordion |
| Add collapsible accordion sections to your markdown |
| Alert | @fsegurai/marked-extended-alert |
| Create styled alert boxes for important information |
| Comments | @fsegurai/marked-extended-comments |
| Add comment sections with author and timestamp metadata |
| Embeds | @fsegurai/marked-extended-embeds |
| Easily embed content from various platforms (YouTube, Twitter, etc.) |
| Footnote | @fsegurai/marked-extended-footnote |
| Add footnotes with automatic numbering |
| Kanban | @fsegurai/marked-extended-kanban |
| Create kanban boards with customizable columns and cards |
| Lists | @fsegurai/marked-extended-lists |
| Enhanced list formatting options |
| Slide | @fsegurai/marked-extended-slide |
| Create slide decks directly from markdown content |
| Spoiler | @fsegurai/marked-extended-spoiler |
| Hide content behind spoiler tags |
| Tables | @fsegurai/marked-extended-tables |
| Advanced table formatting with cell spanning |
| Tabs | @fsegurai/marked-extended-tabs |
| Create tabbed content sections |
| Timeline | @fsegurai/marked-extended-timeline |
| Display content in an interactive timeline format |
| Typographic | @fsegurai/marked-extended-typographic |
| Improve typography with smart quotes, dashes, and more |
Demo Application
To see all extensions in action, check out the [DEMO].
To set up the demo locally, follow the next steps:
git clone https://github.com/fsegurai/marked-extensions.git
bun install
bun startThis will serve the application locally at http://[::1]:8000.
License
Licensed under MIT.
