@fsegurai/marked-extended-slide
v17.0.0
Published
Extension for Marked.js that adds support for presentation slides, allowing the creation of slide-based presentations with Markdown. It supports layouts, backgrounds, transitions, nested rendering, and can be customized to fit your needs.
Maintainers
Readme
An extension library for Marked.js to enhance Markdown rendering.
@fsegurai/marked-extended-slide Extension for Marked.js that adds support for presentation slides, allowing the creation of slide-based presentations with Markdown. It supports layouts, backgrounds, transitions, nested rendering, and can be customized to fit your needs.
🎯 Overview
The marked-extended-slide extension transforms your Markdown into interactive presentation slides using explicit block syntax. It supports layouts, backgrounds, transitions, speaker notes, nested and custom extensions, and a programmatic navigation API.
✨ Key Features
- 🎨 11 Built-in Layouts: Cover, two-cols, quote, statement, section, and more
- 🌈 16 Pre-built Themes: Dark, light, Dracula, Nord, GitHub, Material, Tokyo Night, and more
- ✨ 6 Transition Effects: Slide, fade, zoom, slide-up/down, or none
- 📐 Responsive Design: Auto-scaling with configurable aspect ratios
- ⌨️ Keyboard Navigation: Arrow keys, space, home/end, overview mode
- 📱 Touch Navigation: Swipe support for mobile and tablets
- 🎭 Custom Backgrounds: Solid colors, gradients, or images
- 📊 Progress Tracking: Progress bar and slide numbers
- 🎪 Nested Extensions: Full support for other marked extensions inside slides, including custom block and inline extensions
🎪 Live Demo
Experience all layouts, themes, and transitions: View Demo
Table of contents
Installation
To add @fsegurai/marked-extended-slide along with Marked.js to your package.json use the following commands.
bun install @fsegurai/marked-extended-slide marked@^17 --saveUsage
Basic Usage
Import @fsegurai/marked-extended-slide and apply it to your Marked instance as shown below.
Quick Start
Basic Concept
The slide extension requires the presentation wrapper blocks:
- Start with
::::presentation - Add one or more
::::slideblocks - End each slide with
::::slideend - End the presentation with
::::presentationend
Without this wrapper syntax, content is rendered as normal Markdown.
import { marked } from 'marked';
import markedExtendedSlide from '@fsegurai/marked-extended-slide';
// 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-slide/lib/index.umd.js"></script>
marked.use(markedExtendedSlide());
// Required structural styles
import '@fsegurai/marked-extended-slide/styles/slide.css';
// Optional visual theme
import '@fsegurai/marked-extended-slide/styles/slide-theme.css';
marked.use(markedExtendedSlide({
theme: 'dark',
transition: 'slide',
showControls: true,
showProgress: true,
keyboardNavigation: true,
}));
const presentation = `
::::presentation{title="Building Modern Web Apps" author="Jane Developer" date="February 17, 2026" theme="dark"
transition="slide"}
::::slide{layout="cover" background="linear-gradient(to right, #667eea 0%, #764ba2 100%)"}
# Building Modern Web Apps
A Technical Deep Dive
**Jane Developer** | Senior Engineer
::::slideend
::::slide
## Agenda
1. Architecture Overview
2. Performance Optimization
3. Security Best Practices
4. Deployment Strategies
5. Q&A
::::slideend
::::presentationend
`;
const html = marked.parse(presentation);
document.getElementById('presentation-container').innerHTML = html;This extension injects navigation script behavior for interactivity by default (injectScript: true). Import styles
manually (slide.css required, slide-theme.css optional).
Runtime Behavior in SPA Frameworks
In Angular/React/Vue, slide HTML is often rendered after initial page load. The extension handles this with
MutationObserver and re-initialization hooks.
- Use
injectScript: truefor standard interactive behavior (recommended) - Avoid calling
window.initPresentation()repeatedly unless you really need manual control - Register the extension once in app bootstrap/singleton setup
Why injectScript: false can appear blank
This is expected with the current runtime model:
.marked-extended-slide-slidestarts hidden (opacity: 0)- JavaScript initialization marks one slide as
.active - with
injectScript: false, no runtime sets.active, so slides stay hidden
If you disable script injection, you must provide equivalent client-side activation logic.
Angular quick guidance (ngx-markdown)
provideMarkdown({
markedExtensions: [
{
provide: MARKED_EXTENSIONS,
useFactory: () => markedExtendedSlide({injectScript: true}),
multi: true,
},
],
});Use global styles (styles.scss) for slide styles, not component-scoped styles.
Troubleshooting
| Symptom | Likely Cause | Recommended Fix |
|------------------------------------|------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
| Slides not visible | slide.css missing or injectScript: false without manual activation | Import slide.css globally and keep injectScript: true unless you provide custom runtime |
| Duplicate keyboard button | Repeated presentation initialization | Update to latest package and avoid manual repeated window.initPresentation() calls |
| Overview mode feels glitchy | Duplicate listeners from repeated init | Ensure extension is registered once and avoid duplicate app bootstrap setup |
| Controls visible but no navigation | DOM replaced after init | Re-render once, then call window.initPresentation() once only if needed |
Syntax & Usage
Minimal Valid Presentation
::::presentation
::::slide
# First Slide
Hello slides.
::::slideend
::::presentationendCommon Pitfalls
- Using
---as separator without slide blocks (renders as normal markdown) - Missing
::::slideendor::::presentationend - Not importing
slide.css(structure/visibility issues)
Styling Your Presentations
This extension provides structural behavior and supports theme customization through CSS classes and variables.
Generated HTML Structure
<div class="marked-extended-slide-presentation" data-theme="dark" data-aspect-ratio="16:9" data-transition="slide">
<div class="marked-extended-slide-controls">
<button class="marked-extended-slide-control marked-extended-slide-prev">...</button>
<button class="marked-extended-slide-control marked-extended-slide-next">...</button>
</div>
<div class="marked-extended-slide-progress">
<div class="marked-extended-slide-progress-bar" style="width: 30%"></div>
</div>
<section class="marked-extended-slide-slide marked-extended-slide-layout-default">
<div class="marked-extended-slide-content">
<h1>Slide Title</h1>
</div>
</section>
</div>CSS Classes Reference
| Class | Purpose | Element |
|----------------------------------------------------------|-------------------------|---------------|
| .marked-extended-slide-presentation | Main container | Container |
| .marked-extended-slide-presentation[data-theme="dark"] | Theme variant | Container |
| .marked-extended-slide-slide | Individual slide | Slide wrapper |
| .marked-extended-slide-slide.active | Current/visible slide | Active slide |
| .marked-extended-slide-layout-default | Default layout | Slide |
| .marked-extended-slide-layout-cover | Cover/title layout | Slide |
| .marked-extended-slide-layout-two-cols | Two-column layout | Slide |
| .marked-extended-slide-layout-quote | Quote layout | Slide |
| .marked-extended-slide-layout-section | Section divider | Slide |
| .marked-extended-slide-content | Slide content area | Content |
| .marked-extended-slide-controls | Control buttons wrapper | Nav |
| .marked-extended-slide-control | Control button | Button |
| .marked-extended-slide-progress | Progress bar container | Progress |
| .marked-extended-slide-progress-bar | Progress bar fill | Bar |
| .marked-extended-slide-slide-number | Slide counter | Number |
Base Styling Example
/* Presentation Container */
.marked-extended-slide-presentation {
position: relative;
width: 100%;
height: 100vh;
background: #1a1a1a;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
overflow: hidden;
}
/* Slides */
.marked-extended-slide-slide {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 4rem;
background: var(--slide-bg, #fff);
color: var(--slide-text, #333);
opacity: 0;
transform: translateX(100%);
transition: all 0.6s ease;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.marked-extended-slide-slide.active {
opacity: 1;
transform: translateX(0);
z-index: 1;
}
/* Slide Content */
.marked-extended-slide-content {
width: 100%;
max-width: 960px;
}
/* Layout: Cover */
.marked-extended-slide-layout-cover {
text-align: center;
}
.marked-extended-slide-layout-cover h1 {
font-size: 3.5rem;
margin: 0 0 1rem 0;
line-height: 1.2;
}
/* Layout: Two Columns */
.marked-extended-slide-layout-two-cols .marked-extended-slide-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
align-items: start;
}
/* Layout: Quote */
.marked-extended-slide-layout-quote blockquote {
font-size: 2rem;
font-style: italic;
border-left: 4px solid var(--slide-accent, #0066cc);
padding-left: 2rem;
margin: 0;
}
/* Layout: Section Divider */
.marked-extended-slide-layout-section {
background: var(--slide-accent, #0066cc);
color: white;
font-size: 3rem;
font-weight: 700;
text-align: center;
}
/* Controls */
.marked-extended-slide-controls {
position: absolute;
bottom: 2rem;
right: 2rem;
display: flex;
gap: 0.5rem;
z-index: 100;
}
.marked-extended-slide-control {
width: 48px;
height: 48px;
border: none;
background: rgba(0, 0, 0, 0.6);
color: white;
border-radius: 50%;
font-size: 1.5rem;
cursor: pointer;
transition: all 0.2s;
backdrop-filter: blur(4px);
}
.marked-extended-slide-control:hover {
background: rgba(0, 0, 0, 0.8);
transform: scale(1.1);
}
/* Progress Bar */
.marked-extended-slide-progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
background: rgba(0, 0, 0, 0.3);
z-index: 100;
}
.marked-extended-slide-progress-bar {
height: 100%;
background: var(--slide-accent, #0066cc);
transition: width 0.3s ease;
}
/* Slide Number */
.marked-extended-slide-slide-number {
position: absolute;
bottom: 1rem;
right: 1rem;
font-size: 0.875rem;
color: rgba(0, 0, 0, 0.5);
}
/* Overview Mode */
.marked-extended-slide-presentation.overview-mode .marked-extended-slide-slide {
opacity: 0.8;
transform: scale(0.25);
transition: transform 0.2s, opacity 0.2s;
}
.marked-extended-slide-presentation.overview-mode .marked-extended-slide-slide:hover {
opacity: 1;
transform: scale(0.27);
cursor: pointer;
}Theme Examples
Dark Theme:
.marked-extended-slide-presentation[data-theme="dark"] {
--slide-bg: #1a1a1a;
--slide-text: #e0e0e0;
--slide-accent: #4a9eff;
--slide-heading: #ffffff;
background: #0a0a0a;
}Light Theme:
.marked-extended-slide-presentation[data-theme="light"] {
--slide-bg: #ffffff;
--slide-text: #333333;
--slide-accent: #0066cc;
--slide-heading: #1a1a1a;
background: #f5f5f5;
}Dracula Theme:
.marked-extended-slide-presentation[data-theme="dracula"] {
--slide-bg: #282a36;
--slide-text: #f8f8f2;
--slide-accent: #ff79c6;
--slide-heading: #50fa7b;
background: #1e1f29;
}Nord Theme:
.marked-extended-slide-presentation[data-theme="nord"] {
--slide-bg: #2e3440;
--slide-text: #d8dee9;
--slide-accent: #88c0d0;
--slide-heading: #eceff4;
background: #242933;
}GitHub Light Theme:
.marked-extended-slide-presentation[data-theme="github-light"] {
--slide-bg: #ffffff;
--slide-text: #24292e;
--slide-accent: #0366d6;
--slide-heading: #1b1f23;
background: #f6f8fa;
}Material Theme:
.marked-extended-slide-presentation[data-theme="material"] {
--slide-bg: #263238;
--slide-text: #cfd8dc;
--slide-accent: #80cbc4;
--slide-heading: #eceff1;
background: #1e272e;
}Cyberpunk Theme:
.marked-extended-slide-presentation[data-theme="cyberpunk"] {
--slide-bg: #0a0e27;
--slide-text: #00ffff;
--slide-accent: #ff00ff;
--slide-heading: #ffff00;
background: #000000;
}High Contrast Theme (Accessibility):
.marked-extended-slide-presentation[data-theme="high-contrast"] {
--slide-bg: #000000;
--slide-text: #ffffff;
--slide-accent: #ffff00;
--slide-heading: #ffffff;
background: #000000;
}Keyboard Navigation Styling
/* Show keyboard hint */
.marked-extended-slide-keyboard-toggle {
position: relative;
}
.marked-extended-slide-keyboard-toggle::after {
content: 'Keyboard: Enabled';
position: absolute;
bottom: 100%;
right: 0;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 0.5rem 1rem;
border-radius: 4px;
font-size: 0.75rem;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
margin-bottom: 0.5rem;
}
.marked-extended-slide-keyboard-toggle:hover::after {
opacity: 1;
}
/* Disabled keyboard state */
.marked-extended-slide-keyboard-toggle.disabled {
opacity: 0.5;
}Responsive Adjustments
@media (max-width: 768px) {
.marked-extended-slide-slide {
padding: 2rem;
}
.marked-extended-slide-layout-cover h1 {
font-size: 2rem;
}
.marked-extended-slide-layout-two-cols .marked-extended-slide-content {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.marked-extended-slide-controls {
bottom: 1rem;
right: 1rem;
}
.marked-extended-slide-control {
width: 40px;
height: 40px;
font-size: 1.25rem;
}
}Print Styles
@media print {
.marked-extended-slide-presentation {
height: auto;
background: white;
}
.marked-extended-slide-slide {
position: relative;
transform: none !important;
opacity: 1 !important;
page-break-after: always;
background: white !important;
color: black !important;
box-shadow: none !important;
}
.marked-extended-slide-controls,
.marked-extended-slide-progress {
display: none !important;
}
}Available Themes (16 Total)
- default - Clean and professional
- light - Bright white background
- dark - Dark background with light text
- solarized-light - Solarized light palette
- solarized-dark - Solarized dark palette
- dracula - Popular Dracula theme
- nord - Nord color scheme
- monokai - Classic Monokai
- github-light - GitHub's light theme
- github-dark - GitHub's dark theme
- one-dark - Atom One Dark
- gruvbox-light - Gruvbox light variant
- gruvbox-dark - Gruvbox dark variant
- material - Material Design
- tokyo-night - Tokyo Night theme
- high-contrast - Maximum accessibility
Copy Demo Theme
For all 16 themes: slide-themes.css
Check the demo to see all themes and layouts in action.
Keyboard Controls:
→/Space/Page Down- Next slide←/Page Up- Previous slideHome- First slideEnd- Last slideO/Escape- Toggle overview modeF- Toggle fullscreen
Programmatic API:
window.presentation.next(); // Go to next slide
window.presentation.prev(); // Go to previous slide
window.presentation.goto(5); // Go to slide 5
window.presentation.toggleOverview(); // Toggle overview
window.presentation.getCurrentSlide(); // Get current slide index
window.presentation.getTotalSlides(); // Get total slidesCreating Slides
Wrap your deck in a presentation block and add as many ::::slide blocks as needed:
::::presentation
::::slide
# First Slide
Content here
::::slideend
::::slide
# Second Slide
More content
::::slideend
::::presentationendFront Matter and Properties
This extension does not use YAML front matter blocks for parsing slide structure. Use inline properties instead:
Presentation-level properties:
::::presentation{title="My Presentation" author="John Doe" date="2024-02-07" theme="dark" transition="fade"}
::::slide
# First Slide
::::slideend
::::presentationendSlide-level properties:
::::presentation
::::slide{layout="cover" background="#667eea" transition="zoom" class="custom-class" notes="Remember this point"}
# Slide Content
::::slideend
::::presentationendSlide Layouts
The extension supports multiple built-in layouts:
default- Standard slide with contentcover- Centered title slidecenter- Centered contenttwo-cols- Two-column layoutimage-right- Image on the right, content on leftimage-left- Image on the left, content on rightfull- Full-screen content (no padding)statement- Large, bold statementfact- Highlight a key factquote- Styled quote layoutsection- Section divider slide
Example:
::::presentation
::::slide{layout="two-cols"}
# Two Column Layout
Left column
Right column
::::slideend
::::presentationendBackgrounds
Add custom backgrounds using slide properties:
Solid colors:
::::slide{background="#667eea"}
# Slide
::::slideendGradients:
::::slide{background="linear-gradient(to right, #667eea 0%, #764ba2 100%)"}
# Slide
::::slideendImages:
::::slide{backgroundImage="https://example.com/image.jpg" backgroundSize="cover" backgroundPosition="center"}
# Slide
::::slideendTransitions
Supported transition effects:
slide(default)fadeslide-upslide-downzoomnone
Set globally:
marked.use(markedExtendedSlide({
transition: 'fade',
}));Or per slide:
::::slide{transition="zoom"}
# Zoom Slide
::::slideendNavigation
Controls:
- Navigation arrows appear in the bottom-right
- Progress bar at the bottom
- Slide numbers (optional)
- Overview mode for slide grid
Disable controls:
marked.use(markedExtendedSlide({
showControls: false,
showProgress: false,
showSlideNumbers: false,
}));Configuration Options
| Option | Type | Default | Description |
|-------------------------|------------------|---------------------------|------------------------------|
| className | string | 'marked-extended-slide' | CSS class prefix |
| prefixId | string | 'slide-' | ID prefix for slides |
| transition | string | 'slide' | Default transition effect |
| transitionDuration | string | '0.6s' | Transition duration |
| defaultLayout | string | 'default' | Default slide layout |
| showSlideNumbers | boolean | true | Show slide numbers |
| showControls | boolean | true | Show navigation controls |
| showProgress | boolean | true | Show progress bar |
| keyboardNavigation | boolean | true | Enable keyboard shortcuts |
| touchNavigation | boolean | true | Enable touch/swipe |
| overviewMode | boolean | true | Enable overview mode |
| aspectRatio | string | '16:9' | Slide aspect ratio |
| theme | string | 'default' | Theme name |
| template | string | null | null | Custom slide template |
| containerTemplate | string | null | null | Custom container template |
| customizeToken | function | null | null | Token customization function |
| customizePresentation | function | null | null | Presentation customization |
| injectScript | boolean | true | Auto-inject navigation JS |
Note: Import CSS/SCSS files manually.
Advanced Features
Speaker Notes:
::::slide{notes="Remember to mention the quarterly results here"}
# Q4 Results
::::slideendCustom Classes:
::::slide{class="special-slide highlight"}
# Special Slide
::::slideendHide Slide Numbers:
::::slide{hideSlideNumber="true"}
# Clean Slide
::::slideendNested Extensions:
Slide bodies are rendered with the active Marked parser, so any registered extension can run inside a slide without special handling.
::::presentation
::::slide
# Slide with Accordion
::::accordion{title="Click to expand"}
Hidden content here
::::accordionend
::::slideend
::::slide
# Slide with Alert
> [!NOTE]
> This is an alert inside a slide
::::slideend
::::presentationendResponsive Design
Slides automatically scale on smaller screens while maintaining aspect ratio. The extension includes print styles for PDF export.
Themes
Create custom themes by overriding CSS variables:
.marked-extended-slide-presentation[data-theme="custom"] {
--slide-bg: #f0f0f0;
--slide-text: #333;
--slide-accent: #ff6b6b;
--slide-border: #ddd;
}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.
