Markdown HTML Integration and Mixed Content: Complete Guide to Seamless HTML-Markdown Workflows and Advanced Content Creation
Markdown HTML integration and mixed content systems enable sophisticated document creation through seamless combination of Markdown’s simplicity with HTML’s power and flexibility. By implementing strategic HTML integration patterns, developing custom component systems, and creating robust mixed-content workflows, technical writers and developers can build comprehensive documentation platforms that leverage the best aspects of both markup languages while maintaining readability, accessibility, and maintainability across complex content architectures.
Why Master HTML Integration in Markdown?
Advanced HTML-Markdown integration provides essential capabilities for modern content creation:
- Enhanced Layout Control: Create complex layouts and responsive designs beyond Markdown’s limitations
- Custom Component Systems: Build reusable HTML components integrated seamlessly with Markdown content
- Interactive Content Creation: Embed dynamic elements, forms, and JavaScript-powered interactions
- Accessibility Enhancement: Implement proper semantic HTML and ARIA attributes for inclusive design
- Platform Consistency: Ensure consistent rendering across different Markdown processors and output formats
Foundation HTML Integration Principles
Basic HTML in Markdown Patterns
Understanding how HTML and Markdown interact in various processors:
# Basic HTML Integration Examples
## Inline HTML Elements
Standard Markdown text with <strong>inline HTML emphasis</strong> and
<em>styled elements</em> integrated naturally within paragraphs.
You can use <code>inline code elements</code> alongside `markdown code`
for different styling effects and semantic meaning.
Custom styling with <span class="highlight">CSS classes</span> and
<mark>semantic HTML elements</mark> for enhanced content presentation.
## Block-Level HTML Integration
<div class="info-box">
<h3>Information Block</h3>
<p>This is a complete HTML block integrated within Markdown content.
Standard Markdown formatting like **bold text** and *italic text*
still works within HTML blocks in many processors.</p>
<ul>
<li>HTML lists within blocks</li>
<li>Mixed content capabilities</li>
<li>Semantic structure preservation</li>
</ul>
</div>
Regular Markdown content continues seamlessly after HTML blocks,
maintaining document flow and readability.
## Complex Layout Integration
<section class="two-column-layout">
<div class="column">
<h4>Left Column Content</h4>
Standard **Markdown formatting** continues to work within
HTML containers, providing flexible content creation options.
- Markdown lists
- Within HTML containers
- Seamless integration
</div>
<div class="column">
<h4>Right Column Content</h4>
```javascript
// Code blocks also work within HTML containers
function integration() {
return "HTML + Markdown";
}
```
> Blockquotes and other Markdown elements
> maintain their formatting within HTML structures.
</div>
</section>
## Custom Component Integration
<article class="tutorial-step" data-step="1">
<header class="step-header">
<h3>Step 1: Setup Process</h3>
<span class="step-badge">Essential</span>
</header>
<div class="step-content">
Begin by configuring your **Markdown processor** to handle mixed content:
1. Enable HTML support in processor settings
2. Configure HTML whitelist for security
3. Test **mixed content rendering** thoroughly
<div class="code-example">
```bash
# Example configuration command
markdown-processor --enable-html --secure-mode
```
</div>
<div class="step-notes">
**Important**: Always validate HTML security when accepting
user-generated content with mixed HTML-Markdown integration.
</div>
</div>
</article>
## Table Enhancement with HTML
<table class="enhanced-table" role="grid">
<thead>
<tr>
<th scope="col">Feature</th>
<th scope="col">Markdown Only</th>
<th scope="col">HTML Enhanced</th>
<th scope="col">Benefits</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Basic Tables</strong></td>
<td>✅ Simple syntax</td>
<td>✅ Enhanced styling</td>
<td>
<ul class="benefit-list">
<li>Responsive design</li>
<li>Custom styling</li>
</ul>
</td>
</tr>
<tr>
<td><strong>Complex Layouts</strong></td>
<td>❌ Limited</td>
<td>✅ Full control</td>
<td>
Advanced table features:
- Cell spanning
- **Custom headers**
- Interactive elements
</td>
</tr>
</tbody>
</table>
## Form Integration
<form class="markdown-form" action="#" method="post">
<fieldset>
<legend>Contact Information Form</legend>
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
<small>*Enter your full name as it should appear in documentation.*</small>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="content-type">Content Type:</label>
<select id="content-type" name="content-type">
<option value="tutorial">Tutorial Content</option>
<option value="reference">Reference Documentation</option>
<option value="example">Code Examples</option>
</select>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea id="description" name="description" rows="4">
Write your content description using **Markdown formatting**
if supported by your form processor.
</textarea>
</div>
<button type="submit" class="submit-button">Submit Content</button>
</fieldset>
</form>
Integration continues with normal Markdown content flow...
HTML Security and Sanitization
Critical considerations for safe HTML integration:
// markdown-html-sanitizer.js - Comprehensive HTML sanitization for Markdown
class MarkdownHTMLSanitizer {
constructor(options = {}) {
this.options = {
allowedTags: [
'div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'strong', 'em', 'code', 'pre', 'blockquote',
'ul', 'ol', 'li', 'table', 'thead', 'tbody', 'tr', 'td', 'th',
'a', 'img', 'br', 'hr', 'section', 'article', 'header', 'footer',
'nav', 'aside', 'main', 'figure', 'figcaption', 'details', 'summary'
],
allowedAttributes: {
'div': ['class', 'id', 'role', 'data-*'],
'span': ['class', 'id'],
'a': ['href', 'title', 'target', 'rel'],
'img': ['src', 'alt', 'title', 'width', 'height', 'loading'],
'table': ['class', 'role'],
'th': ['scope', 'colspan', 'rowspan'],
'td': ['colspan', 'rowspan'],
'*': ['class', 'id', 'aria-*', 'data-*']
},
allowedProtocols: ['http', 'https', 'mailto', 'tel'],
blockScripts: true,
blockStyles: false,
preserveMarkdown: true,
validateNesting: true,
...options
};
this.dangerousTags = ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button'];
this.selfClosingTags = ['br', 'hr', 'img', 'input', 'area', 'base', 'col', 'embed', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
}
sanitizeDocument(content) {
// Step 1: Extract and protect Markdown code blocks
const codeBlocks = [];
const protectedContent = content.replace(/```[\s\S]*?```|`[^`]+`/g, (match, offset) => {
const placeholder = `__CODEBLOCK_${codeBlocks.length}__`;
codeBlocks.push(match);
return placeholder;
});
// Step 2: Extract and validate HTML blocks
const htmlBlocks = this.extractHTMLBlocks(protectedContent);
let sanitizedContent = protectedContent;
// Step 3: Sanitize each HTML block
htmlBlocks.forEach((block, index) => {
const sanitized = this.sanitizeHTMLBlock(block.content);
sanitizedContent = sanitizedContent.replace(block.placeholder, sanitized);
});
// Step 4: Restore code blocks
codeBlocks.forEach((code, index) => {
sanitizedContent = sanitizedContent.replace(`__CODEBLOCK_${index}__`, code);
});
// Step 5: Final validation and cleanup
return this.finalValidation(sanitizedContent);
}
extractHTMLBlocks(content) {
const htmlBlocks = [];
const htmlRegex = /<(\w+)(?:\s[^>]*)?>([\s\S]*?)<\/\1>/gi;
let match;
while ((match = htmlRegex.exec(content)) !== null) {
const tag = match[1].toLowerCase();
const fullMatch = match[0];
const innerContent = match[2];
// Validate tag is allowed
if (this.options.allowedTags.includes(tag)) {
const placeholder = `__HTMLBLOCK_${htmlBlocks.length}__`;
htmlBlocks.push({
placeholder: placeholder,
content: fullMatch,
tag: tag,
innerContent: innerContent
});
content = content.replace(fullMatch, placeholder);
}
}
return htmlBlocks;
}
sanitizeHTMLBlock(htmlContent) {
// Parse HTML and validate structure
const doc = new DOMParser().parseFromString(`<div>${htmlContent}</div>`, 'text/html');
const container = doc.querySelector('div');
if (!container) {
return this.escapeHTML(htmlContent);
}
// Recursively sanitize all elements
this.sanitizeElement(container);
// Return inner HTML (excluding the wrapper div)
return container.innerHTML;
}
sanitizeElement(element) {
const tagName = element.tagName.toLowerCase();
// Remove dangerous tags entirely
if (this.dangerousTags.includes(tagName)) {
element.remove();
return;
}
// Remove disallowed tags but keep content
if (!this.options.allowedTags.includes(tagName)) {
const parent = element.parentNode;
while (element.firstChild) {
parent.insertBefore(element.firstChild, element);
}
element.remove();
return;
}
// Sanitize attributes
this.sanitizeAttributes(element);
// Recursively sanitize child elements
const children = Array.from(element.children);
children.forEach(child => this.sanitizeElement(child));
// Validate nesting if enabled
if (this.options.validateNesting) {
this.validateNesting(element);
}
}
sanitizeAttributes(element) {
const tagName = element.tagName.toLowerCase();
const allowedAttrs = this.options.allowedAttributes[tagName] || this.options.allowedAttributes['*'] || [];
// Get all attributes
const attributes = Array.from(element.attributes);
attributes.forEach(attr => {
const attrName = attr.name.toLowerCase();
const attrValue = attr.value;
let isAllowed = false;
// Check if attribute is explicitly allowed
for (const allowed of allowedAttrs) {
if (allowed === attrName) {
isAllowed = true;
break;
} else if (allowed.includes('*')) {
// Handle wildcard attributes like 'data-*' or 'aria-*'
const pattern = new RegExp('^' + allowed.replace('*', '.*') + '$');
if (pattern.test(attrName)) {
isAllowed = true;
break;
}
}
}
if (!isAllowed) {
element.removeAttribute(attrName);
return;
}
// Sanitize attribute values
const sanitizedValue = this.sanitizeAttributeValue(attrName, attrValue);
if (sanitizedValue !== attrValue) {
element.setAttribute(attrName, sanitizedValue);
}
});
}
sanitizeAttributeValue(attrName, value) {
// Handle URL attributes
if (['href', 'src', 'action'].includes(attrName)) {
return this.sanitizeURL(value);
}
// Handle class attributes
if (attrName === 'class') {
return this.sanitizeClassName(value);
}
// Handle ID attributes
if (attrName === 'id') {
return this.sanitizeID(value);
}
// Handle data attributes
if (attrName.startsWith('data-')) {
return this.sanitizeDataAttribute(value);
}
// Handle ARIA attributes
if (attrName.startsWith('aria-')) {
return this.sanitizeAriaAttribute(value);
}
// Default: escape potentially dangerous characters
return value.replace(/[<>"']/g, (char) => {
const entities = { '<': '<', '>': '>', '"': '"', "'": ''' };
return entities[char] || char;
});
}
sanitizeURL(url) {
try {
const parsedURL = new URL(url, window.location.href);
if (this.options.allowedProtocols.includes(parsedURL.protocol.slice(0, -1))) {
return parsedURL.href;
} else {
return '#'; // Return safe fallback
}
} catch (e) {
// If URL parsing fails, check for relative URLs or anchors
if (url.startsWith('#') || url.startsWith('/') || !url.includes(':')) {
return url; // Allow relative URLs and anchors
}
return '#'; // Return safe fallback
}
}
sanitizeClassName(className) {
// Allow alphanumeric, hyphens, underscores, and spaces
return className.replace(/[^a-zA-Z0-9\-_\s]/g, '').trim();
}
sanitizeID(id) {
// Allow alphanumeric, hyphens, and underscores only
return id.replace(/[^a-zA-Z0-9\-_]/g, '');
}
sanitizeDataAttribute(value) {
// Basic data attribute sanitization
return value.replace(/[<>"']/g, '').trim();
}
sanitizeAriaAttribute(value) {
// ARIA attributes can contain specific values
return value.replace(/[<>"]/g, '').trim();
}
validateNesting(element) {
const tagName = element.tagName.toLowerCase();
const parent = element.parentElement;
if (!parent) return;
const parentTag = parent.tagName.toLowerCase();
// Define invalid nesting combinations
const invalidNesting = {
'p': ['div', 'section', 'article', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
'h1': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
'h2': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
'a': ['a', 'button', 'input']
};
if (invalidNesting[parentTag] && invalidNesting[parentTag].includes(tagName)) {
// Move element outside of invalid parent
const grandparent = parent.parentNode;
if (grandparent) {
grandparent.insertBefore(element, parent.nextSibling);
}
}
}
finalValidation(content) {
// Final cleanup and validation
return content
.replace(/__HTMLBLOCK_\d+__/g, '') // Remove any unreplaced placeholders
.replace(/\n\s*\n\s*\n/g, '\n\n') // Normalize excessive whitespace
.trim();
}
escapeHTML(html) {
const div = document.createElement('div');
div.textContent = html;
return div.innerHTML;
}
// Public API methods
sanitize(content) {
return this.sanitizeDocument(content);
}
validateHTMLSafety(content) {
const dangerousPatterns = [
/<script[\s\S]*?<\/script>/gi,
/javascript:/gi,
/vbscript:/gi,
/on\w+\s*=/gi,
/<iframe[\s\S]*?<\/iframe>/gi,
/<object[\s\S]*?<\/object>/gi,
/<embed[\s\S]*?>/gi
];
const issues = [];
dangerousPatterns.forEach((pattern, index) => {
const matches = content.match(pattern);
if (matches) {
issues.push({
type: 'security',
pattern: pattern.toString(),
matches: matches,
severity: 'high'
});
}
});
return {
isSafe: issues.length === 0,
issues: issues,
recommendation: issues.length > 0 ? 'Content contains potentially dangerous HTML and should be sanitized before use.' : 'Content appears safe for use.'
};
}
}
// Usage examples
const sanitizer = new MarkdownHTMLSanitizer({
allowedTags: ['div', 'p', 'h1', 'h2', 'h3', 'strong', 'em', 'code', 'a', 'img'],
blockScripts: true,
validateNesting: true
});
// Sanitize mixed HTML-Markdown content
const mixedContent = `
# My Document
<div class="content-block">
This is **mixed content** with <strong>HTML tags</strong>.
<script>alert('dangerous');</script>
## Subheading
<p>Regular paragraph with <a href="https://example.com">safe link</a>.</p>
</div>
Normal Markdown continues here...
`;
const sanitizedContent = sanitizer.sanitize(mixedContent);
console.log(sanitizedContent);
// Validate content safety
const safetyReport = sanitizer.validateHTMLSafety(mixedContent);
console.log(safetyReport);
Advanced Component Systems
Custom HTML Component Framework
Building reusable components that integrate seamlessly with Markdown:
// markdown-component-system.js - Advanced HTML component framework for Markdown
class MarkdownComponentSystem {
constructor(options = {}) {
this.options = {
componentPrefix: 'md-',
enableNesting: true,
allowMarkdownInComponents: true,
validateComponents: true,
debugMode: false,
customComponents: {},
...options
};
this.components = new Map();
this.processors = new Map();
this.renderingContext = {
depth: 0,
maxDepth: 10,
variables: new Map()
};
this.initializeDefaultComponents();
}
initializeDefaultComponents() {
// Register built-in components
this.registerComponent('callout', this.createCalloutComponent());
this.registerComponent('tabs', this.createTabsComponent());
this.registerComponent('code-example', this.createCodeExampleComponent());
this.registerComponent('image-gallery', this.createImageGalleryComponent());
this.registerComponent('table-wrapper', this.createTableWrapperComponent());
this.registerComponent('info-card', this.createInfoCardComponent());
// Register custom components if provided
Object.entries(this.options.customComponents).forEach(([name, component]) => {
this.registerComponent(name, component);
});
}
registerComponent(name, component) {
if (typeof component !== 'object' || !component.render) {
throw new Error(`Component ${name} must have a render method`);
}
this.components.set(name, {
name: name,
render: component.render,
validate: component.validate || (() => true),
attributes: component.attributes || [],
allowChildren: component.allowChildren !== false,
selfClosing: component.selfClosing || false
});
}
processDocument(content) {
this.renderingContext.depth = 0;
this.renderingContext.variables.clear();
// Step 1: Extract and protect Markdown code blocks
const { content: protectedContent, codeBlocks } = this.protectCodeBlocks(content);
// Step 2: Process component syntax
let processedContent = this.processComponents(protectedContent);
// Step 3: Restore code blocks
processedContent = this.restoreCodeBlocks(processedContent, codeBlocks);
return processedContent;
}
protectCodeBlocks(content) {
const codeBlocks = [];
const protectedContent = content.replace(/```[\s\S]*?```|`[^`]+`/g, (match) => {
const placeholder = `__PROTECTED_CODE_${codeBlocks.length}__`;
codeBlocks.push(match);
return placeholder;
});
return { content: protectedContent, codeBlocks };
}
restoreCodeBlocks(content, codeBlocks) {
codeBlocks.forEach((code, index) => {
content = content.replace(`__PROTECTED_CODE_${index}__`, code);
});
return content;
}
processComponents(content) {
// Enhanced component syntax: <md-component-name attr="value">content</md-component-name>
const componentRegex = new RegExp(`<${this.options.componentPrefix}([^\\s>]+)([^>]*)>([\\s\\S]*?)<\\/${this.options.componentPrefix}\\1>`, 'gi');
return content.replace(componentRegex, (match, componentName, attributes, innerContent) => {
try {
return this.renderComponent(componentName, attributes, innerContent);
} catch (error) {
if (this.options.debugMode) {
console.error(`Error rendering component ${componentName}:`, error);
}
return this.renderErrorComponent(componentName, error.message);
}
});
}
renderComponent(componentName, attributeString, innerContent) {
const component = this.components.get(componentName);
if (!component) {
throw new Error(`Unknown component: ${componentName}`);
}
// Check rendering depth to prevent infinite recursion
if (this.renderingContext.depth >= this.renderingContext.maxDepth) {
throw new Error(`Maximum component nesting depth exceeded for ${componentName}`);
}
// Parse attributes
const attributes = this.parseAttributes(attributeString);
// Validate component usage
if (this.options.validateComponents && !component.validate(attributes, innerContent)) {
throw new Error(`Invalid component usage for ${componentName}`);
}
// Process nested content if allowed
let processedContent = innerContent;
if (component.allowChildren && this.options.enableNesting) {
this.renderingContext.depth++;
if (this.options.allowMarkdownInComponents) {
// Process nested components first, then let Markdown processor handle the rest
processedContent = this.processComponents(innerContent);
}
this.renderingContext.depth--;
}
// Render the component
const renderContext = {
attributes,
content: processedContent,
variables: this.renderingContext.variables,
componentName
};
return component.render(renderContext);
}
parseAttributes(attributeString) {
const attributes = {};
// Enhanced attribute parsing with support for various formats
const attrRegex = /(\w+)(?:=(?:["']([^"']*)["']|([^\s]+)))?/g;
let match;
while ((match = attrRegex.exec(attributeString)) !== null) {
const name = match[1];
const value = match[2] || match[3] || true;
attributes[name] = value;
}
return attributes;
}
createCalloutComponent() {
return {
attributes: ['type', 'title', 'icon', 'collapsible'],
render: ({ attributes, content }) => {
const type = attributes.type || 'info';
const title = attributes.title || '';
const icon = attributes.icon || this.getDefaultIcon(type);
const collapsible = attributes.collapsible === 'true';
const titleHtml = title ? `<div class="callout-title">${icon} ${title}</div>` : '';
const contentClass = collapsible ? 'callout-content collapsible' : 'callout-content';
return `
<div class="callout callout-${type}" role="alert">
${titleHtml}
<div class="${contentClass}">
${content}
</div>
</div>`;
},
validate: (attributes) => {
const validTypes = ['info', 'warning', 'error', 'success', 'note'];
return !attributes.type || validTypes.includes(attributes.type);
}
};
}
createTabsComponent() {
return {
attributes: ['default-tab'],
render: ({ attributes, content }) => {
const defaultTab = attributes['default-tab'] || 0;
// Parse tab content (simplified - would need more robust parsing)
const tabs = this.parseTabContent(content);
let tabsHtml = '<div class="tabs-component">';
// Tab headers
tabsHtml += '<div class="tab-headers" role="tablist">';
tabs.forEach((tab, index) => {
const isActive = index == defaultTab ? 'active' : '';
tabsHtml += `<button class="tab-header ${isActive}"
role="tab"
aria-selected="${index == defaultTab}"
data-tab="${index}">
${tab.title}
</button>`;
});
tabsHtml += '</div>';
// Tab content
tabsHtml += '<div class="tab-contents">';
tabs.forEach((tab, index) => {
const isActive = index == defaultTab ? 'active' : '';
tabsHtml += `<div class="tab-content ${isActive}"
role="tabpanel"
data-tab="${index}">
${tab.content}
</div>`;
});
tabsHtml += '</div>';
tabsHtml += '</div>';
// Add JavaScript for tab functionality
tabsHtml += `
<script>
document.addEventListener('DOMContentLoaded', function() {
const tabComponent = document.querySelector('.tabs-component:last-of-type');
const headers = tabComponent.querySelectorAll('.tab-header');
const contents = tabComponent.querySelectorAll('.tab-content');
headers.forEach(header => {
header.addEventListener('click', () => {
const tabIndex = header.dataset.tab;
// Update headers
headers.forEach(h => {
h.classList.remove('active');
h.setAttribute('aria-selected', 'false');
});
header.classList.add('active');
header.setAttribute('aria-selected', 'true');
// Update content
contents.forEach(c => c.classList.remove('active'));
const targetContent = tabComponent.querySelector(\`[data-tab="\${tabIndex}"]\`);
if (targetContent) {
targetContent.classList.add('active');
}
});
});
});
</script>`;
return tabsHtml;
}
};
}
createCodeExampleComponent() {
return {
attributes: ['language', 'title', 'filename', 'highlight-lines', 'copy-button'],
render: ({ attributes, content }) => {
const language = attributes.language || 'text';
const title = attributes.title || '';
const filename = attributes.filename || '';
const highlightLines = attributes['highlight-lines'] || '';
const copyButton = attributes['copy-button'] !== 'false';
const headerContent = title || filename;
const header = headerContent ?
`<div class="code-header">
<span class="code-title">${headerContent}</span>
${copyButton ? '<button class="copy-button" title="Copy code">📋</button>' : ''}
</div>` : '';
return `
<div class="code-example" data-language="${language}">
${header}
<pre class="code-block"><code class="language-${language}">${this.escapeHTML(content.trim())}</code></pre>
</div>`;
}
};
}
createImageGalleryComponent() {
return {
attributes: ['layout', 'columns', 'lightbox'],
render: ({ attributes, content }) => {
const layout = attributes.layout || 'grid';
const columns = attributes.columns || '3';
const lightbox = attributes.lightbox === 'true';
// Parse image content
const images = this.parseImageContent(content);
let galleryHtml = `<div class="image-gallery layout-${layout}" style="--columns: ${columns}">`;
images.forEach((image, index) => {
const lightboxAttr = lightbox ? `data-lightbox="gallery" data-index="${index}"` : '';
galleryHtml += `
<figure class="gallery-item">
<img src="${image.src}"
alt="${image.alt || ''}"
title="${image.title || ''}"
loading="lazy"
${lightboxAttr}>
${image.caption ? `<figcaption>${image.caption}</figcaption>` : ''}
</figure>`;
});
galleryHtml += '</div>';
if (lightbox) {
galleryHtml += this.generateLightboxScript();
}
return galleryHtml;
}
};
}
createTableWrapperComponent() {
return {
attributes: ['responsive', 'sortable', 'filterable', 'pagination'],
render: ({ attributes, content }) => {
const responsive = attributes.responsive !== 'false';
const sortable = attributes.sortable === 'true';
const filterable = attributes.filterable === 'true';
const pagination = attributes.pagination === 'true';
let wrapperClass = 'table-wrapper';
if (responsive) wrapperClass += ' responsive';
if (sortable) wrapperClass += ' sortable';
let tableHtml = `<div class="${wrapperClass}">`;
if (filterable) {
tableHtml += `
<div class="table-controls">
<input type="text"
class="table-filter"
placeholder="Filter table contents..."
aria-label="Filter table">
</div>`;
}
tableHtml += content;
if (pagination) {
tableHtml += `
<div class="table-pagination">
<button class="pagination-prev" disabled>Previous</button>
<span class="pagination-info">Page 1 of 1</span>
<button class="pagination-next" disabled>Next</button>
</div>`;
}
tableHtml += '</div>';
if (sortable || filterable || pagination) {
tableHtml += this.generateTableEnhancementScript({
sortable,
filterable,
pagination
});
}
return tableHtml;
}
};
}
createInfoCardComponent() {
return {
attributes: ['variant', 'icon', 'link', 'target'],
render: ({ attributes, content }) => {
const variant = attributes.variant || 'default';
const icon = attributes.icon || '';
const link = attributes.link || '';
const target = attributes.target || '_self';
const iconHtml = icon ? `<div class="card-icon">${icon}</div>` : '';
const cardContent = `${iconHtml}<div class="card-content">${content}</div>`;
if (link) {
return `
<a href="${link}"
target="${target}"
class="info-card variant-${variant} card-link">
${cardContent}
</a>`;
} else {
return `
<div class="info-card variant-${variant}">
${cardContent}
</div>`;
}
}
};
}
parseTabContent(content) {
// Simplified tab parsing - would need more robust implementation
const tabs = [];
const lines = content.split('\n');
let currentTab = null;
lines.forEach(line => {
const tabMatch = line.match(/^##\s+(.+)/);
if (tabMatch) {
if (currentTab) {
tabs.push(currentTab);
}
currentTab = {
title: tabMatch[1],
content: ''
};
} else if (currentTab) {
currentTab.content += line + '\n';
}
});
if (currentTab) {
tabs.push(currentTab);
}
return tabs;
}
parseImageContent(content) {
const images = [];
const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)(?:\s*"([^"]*)")?/g;
let match;
while ((match = imageRegex.exec(content)) !== null) {
images.push({
alt: match[1] || '',
src: match[2],
caption: match[3] || match[1] || ''
});
}
return images;
}
getDefaultIcon(type) {
const icons = {
info: 'ℹ️',
warning: '⚠️',
error: '❌',
success: '✅',
note: '📝'
};
return icons[type] || '📄';
}
generateLightboxScript() {
return `
<script>
document.addEventListener('DOMContentLoaded', function() {
const galleryImages = document.querySelectorAll('[data-lightbox="gallery"]');
galleryImages.forEach(img => {
img.addEventListener('click', function() {
// Simple lightbox implementation
const lightbox = document.createElement('div');
lightbox.className = 'lightbox';
lightbox.innerHTML = \`
<div class="lightbox-content">
<span class="lightbox-close">×</span>
<img src="\${img.src}" alt="\${img.alt}">
<div class="lightbox-caption">\${img.title || img.alt}</div>
</div>
\`;
document.body.appendChild(lightbox);
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox || e.target.className === 'lightbox-close') {
lightbox.remove();
}
});
});
});
});
</script>`;
}
generateTableEnhancementScript(features) {
return `
<script>
document.addEventListener('DOMContentLoaded', function() {
const tableWrapper = document.querySelector('.table-wrapper:last-of-type');
const table = tableWrapper.querySelector('table');
${features.sortable ? this.generateSortableScript() : ''}
${features.filterable ? this.generateFilterableScript() : ''}
${features.pagination ? this.generatePaginationScript() : ''}
});
</script>`;
}
generateSortableScript() {
return `
// Add sorting functionality
const headers = table.querySelectorAll('th');
headers.forEach((header, index) => {
header.style.cursor = 'pointer';
header.addEventListener('click', () => sortTable(index));
});
function sortTable(columnIndex) {
const tbody = table.querySelector('tbody');
const rows = Array.from(tbody.rows);
const isNumeric = rows.every(row => !isNaN(row.cells[columnIndex].textContent));
rows.sort((a, b) => {
const aVal = a.cells[columnIndex].textContent.trim();
const bVal = b.cells[columnIndex].textContent.trim();
if (isNumeric) {
return parseFloat(aVal) - parseFloat(bVal);
} else {
return aVal.localeCompare(bVal);
}
});
rows.forEach(row => tbody.appendChild(row));
}`;
}
generateFilterableScript() {
return `
// Add filtering functionality
const filterInput = tableWrapper.querySelector('.table-filter');
filterInput.addEventListener('input', function() {
const filterValue = this.value.toLowerCase();
const rows = table.querySelectorAll('tbody tr');
rows.forEach(row => {
const text = row.textContent.toLowerCase();
row.style.display = text.includes(filterValue) ? '' : 'none';
});
});`;
}
generatePaginationScript() {
return `
// Add pagination functionality
const rowsPerPage = 10;
let currentPage = 1;
const rows = Array.from(table.querySelectorAll('tbody tr'));
const totalPages = Math.ceil(rows.length / rowsPerPage);
function showPage(page) {
const start = (page - 1) * rowsPerPage;
const end = start + rowsPerPage;
rows.forEach((row, index) => {
row.style.display = (index >= start && index < end) ? '' : 'none';
});
document.querySelector('.pagination-info').textContent =
\`Page \${page} of \${totalPages}\`;
document.querySelector('.pagination-prev').disabled = page === 1;
document.querySelector('.pagination-next').disabled = page === totalPages;
}
document.querySelector('.pagination-prev').addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
showPage(currentPage);
}
});
document.querySelector('.pagination-next').addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage++;
showPage(currentPage);
}
});
showPage(1);`;
}
renderErrorComponent(componentName, errorMessage) {
return `
<div class="component-error" role="alert">
<strong>Component Error:</strong> Failed to render "${componentName}" component.
${this.options.debugMode ? `<br><em>Error: ${errorMessage}</em>` : ''}
</div>`;
}
escapeHTML(html) {
const div = document.createElement('div');
div.textContent = html;
return div.innerHTML;
}
// Public API
process(content) {
return this.processDocument(content);
}
addComponent(name, component) {
this.registerComponent(name, component);
}
removeComponent(name) {
this.components.delete(name);
}
listComponents() {
return Array.from(this.components.keys());
}
setVariable(name, value) {
this.renderingContext.variables.set(name, value);
}
getVariable(name) {
return this.renderingContext.variables.get(name);
}
}
// Usage example
const componentSystem = new MarkdownComponentSystem({
componentPrefix: 'md-',
enableNesting: true,
allowMarkdownInComponents: true,
debugMode: true
});
// Example document with custom components
const documentContent = `
# Advanced Content Example
<md-callout type="info" title="Getting Started">
This is an **informational callout** with Markdown formatting support.
- Feature 1
- Feature 2
- Feature 3
</md-callout>
<md-tabs default-tab="0">
## JavaScript Example
\`\`\`javascript
function example() {
console.log("Tab content with code");
}
\`\`\`
## Python Example
\`\`\`python
def example():
print("Python code example")
\`\`\`
</md-tabs>
<md-code-example language="html" title="Component Usage" copy-button="true">
<md-callout type="success">
Nested components work seamlessly!
</md-callout>
</md-code-example>
Regular **Markdown content** continues normally between components.
`;
const processedContent = componentSystem.process(documentContent);
console.log(processedContent);
Responsive Design Integration
CSS Framework for HTML-Markdown Integration
Comprehensive styling system for mixed content:
/* markdown-html-integration.css - Complete styling for mixed HTML-Markdown content */
/* Base responsive grid system for HTML components */
.markdown-content {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
line-height: 1.6;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* Component container base styles */
.component-wrapper {
margin: 2rem 0;
position: relative;
}
/* Two-column responsive layout */
.two-column-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin: 2rem 0;
}
.column {
padding: 1rem;
border-radius: 8px;
background: #f8f9fa;
border-left: 4px solid #007bff;
}
@media (max-width: 768px) {
.two-column-layout {
grid-template-columns: 1fr;
gap: 1rem;
}
}
/* Enhanced table styling */
.enhanced-table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.enhanced-table th,
.enhanced-table td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #e9ecef;
}
.enhanced-table th {
background: #495057;
color: white;
font-weight: 600;
position: sticky;
top: 0;
z-index: 10;
}
.enhanced-table tbody tr:hover {
background: #f8f9fa;
}
.enhanced-table .benefit-list {
margin: 0;
padding-left: 1rem;
list-style: none;
}
.enhanced-table .benefit-list li {
padding: 0.25rem 0;
position: relative;
}
.enhanced-table .benefit-list li::before {
content: '✓';
color: #28a745;
font-weight: bold;
position: absolute;
left: -1rem;
}
/* Responsive table wrapper */
.table-wrapper {
overflow-x: auto;
margin: 1.5rem 0;
border: 1px solid #dee2e6;
border-radius: 8px;
}
.table-wrapper.responsive table {
min-width: 600px;
}
/* Table controls styling */
.table-controls {
padding: 1rem;
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.table-filter {
flex: 1;
min-width: 200px;
padding: 0.5rem;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 0.9rem;
}
.table-pagination {
padding: 1rem;
background: #f8f9fa;
border-top: 1px solid #dee2e6;
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
}
.pagination-prev,
.pagination-next {
padding: 0.5rem 1rem;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.pagination-prev:disabled,
.pagination-next:disabled {
background: #6c757d;
cursor: not-allowed;
}
.pagination-prev:hover:not(:disabled),
.pagination-next:hover:not(:disabled) {
background: #0056b3;
}
.pagination-info {
font-size: 0.9rem;
color: #6c757d;
min-width: 100px;
text-align: center;
}
/* Form integration styling */
.markdown-form {
background: white;
padding: 2rem;
border-radius: 8px;
border: 1px solid #dee2e6;
margin: 2rem 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.markdown-form fieldset {
border: none;
padding: 0;
margin: 0;
}
.markdown-form legend {
font-size: 1.25rem;
font-weight: 600;
color: #495057;
margin-bottom: 1.5rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #e9ecef;
width: 100%;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #495057;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.2s, box-shadow 0.2s;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
outline: none;
}
.form-group small {
display: block;
margin-top: 0.5rem;
color: #6c757d;
font-size: 0.875rem;
}
.submit-button {
background: #28a745;
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s;
}
.submit-button:hover {
background: #218838;
}
/* Custom component styling */
.callout {
padding: 1.5rem;
margin: 1.5rem 0;
border-radius: 8px;
border-left: 4px solid;
position: relative;
}
.callout-info {
background: #e7f3ff;
border-color: #007bff;
}
.callout-warning {
background: #fff3cd;
border-color: #ffc107;
}
.callout-error {
background: #f8d7da;
border-color: #dc3545;
}
.callout-success {
background: #d4edda;
border-color: #28a745;
}
.callout-note {
background: #f8f9fa;
border-color: #6c757d;
}
.callout-title {
font-weight: 600;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.callout-content {
margin: 0;
}
.callout-content.collapsible {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
/* Tabs component styling */
.tabs-component {
margin: 2rem 0;
border: 1px solid #dee2e6;
border-radius: 8px;
overflow: hidden;
}
.tab-headers {
display: flex;
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
flex-wrap: wrap;
}
.tab-header {
flex: 1;
min-width: 120px;
padding: 1rem;
background: none;
border: none;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
color: #6c757d;
transition: all 0.2s;
border-bottom: 3px solid transparent;
}
.tab-header:hover {
background: #e9ecef;
}
.tab-header.active {
background: white;
color: #007bff;
border-bottom-color: #007bff;
}
.tab-contents {
background: white;
}
.tab-content {
display: none;
padding: 2rem;
}
.tab-content.active {
display: block;
}
/* Code example component styling */
.code-example {
margin: 1.5rem 0;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e1e8ed;
}
.code-header {
background: #f8f9fa;
padding: 0.75rem 1rem;
border-bottom: 1px solid #e1e8ed;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.9rem;
font-weight: 600;
color: #495057;
}
.code-title {
flex: 1;
}
.copy-button {
background: none;
border: none;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.copy-button:hover {
background: #e9ecef;
}
.code-block {
margin: 0;
padding: 1rem;
background: #f8f9fa;
font-family: 'Fira Code', 'Monaco', 'Menlo', monospace;
font-size: 0.9rem;
line-height: 1.5;
overflow-x: auto;
}
/* Image gallery styling */
.image-gallery {
margin: 2rem 0;
}
.image-gallery.layout-grid {
display: grid;
grid-template-columns: repeat(var(--columns, 3), 1fr);
gap: 1rem;
}
.gallery-item {
margin: 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.2s;
}
.gallery-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.gallery-item img {
width: 100%;
height: 200px;
object-fit: cover;
cursor: pointer;
}
.gallery-item figcaption {
padding: 1rem;
font-size: 0.9rem;
color: #495057;
}
/* Info card styling */
.info-card {
display: block;
padding: 1.5rem;
margin: 1rem 0;
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: all 0.2s;
}
.info-card.card-link {
text-decoration: none;
color: inherit;
cursor: pointer;
}
.info-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.info-card.variant-primary {
border-color: #007bff;
background: linear-gradient(135deg, #e7f3ff 0%, #ffffff 100%);
}
.info-card.variant-success {
border-color: #28a745;
background: linear-gradient(135deg, #d4edda 0%, #ffffff 100%);
}
.info-card.variant-warning {
border-color: #ffc107;
background: linear-gradient(135deg, #fff3cd 0%, #ffffff 100%);
}
.card-icon {
font-size: 2rem;
margin-bottom: 1rem;
text-align: center;
}
.card-content {
flex: 1;
}
/* Lightbox styling */
.lightbox {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
animation: fadeIn 0.3s forwards;
}
@keyframes fadeIn {
to { opacity: 1; }
}
.lightbox-content {
position: relative;
max-width: 90%;
max-height: 90%;
}
.lightbox-content img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.lightbox-close {
position: absolute;
top: -40px;
right: 0;
color: white;
font-size: 2rem;
cursor: pointer;
line-height: 1;
}
.lightbox-caption {
position: absolute;
bottom: -40px;
left: 0;
right: 0;
color: white;
text-align: center;
font-size: 0.9rem;
}
/* Component error styling */
.component-error {
background: #f8d7da;
color: #721c24;
padding: 1rem;
margin: 1rem 0;
border: 1px solid #f5c6cb;
border-radius: 4px;
font-size: 0.9rem;
}
/* Mobile responsive adjustments */
@media (max-width: 768px) {
.markdown-content {
padding: 0.5rem;
}
.image-gallery.layout-grid {
grid-template-columns: 1fr;
}
.tab-headers {
flex-direction: column;
}
.tab-header {
flex: none;
}
.table-controls {
flex-direction: column;
align-items: stretch;
}
.table-filter {
min-width: auto;
}
.callout,
.info-card,
.code-example {
margin-left: -0.5rem;
margin-right: -0.5rem;
border-radius: 0;
}
}
/* Print styles for mixed content */
@media print {
.lightbox,
.copy-button,
.tab-headers,
.table-controls,
.table-pagination {
display: none !important;
}
.tab-content {
display: block !important;
page-break-inside: avoid;
}
.callout,
.info-card,
.code-example {
page-break-inside: avoid;
border: 1px solid #000;
background: white !important;
}
.enhanced-table {
font-size: 0.8rem;
}
.image-gallery.layout-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* High contrast mode support */
@media (prefers-contrast: high) {
.callout,
.info-card,
.enhanced-table,
.code-example {
border: 2px solid;
background: white;
}
.tab-header.active {
background: highlight;
color: highlighttext;
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
.gallery-item,
.info-card,
.callout-content,
.lightbox {
transition: none;
}
.lightbox {
animation: none;
opacity: 1;
}
}
/* Focus management for keyboard navigation */
.tab-header:focus,
.copy-button:focus,
.gallery-item img:focus,
.info-card:focus {
outline: 2px solid #007bff;
outline-offset: 2px;
}
/* Skip links for accessibility */
.skip-to-content {
position: absolute;
top: -40px;
left: 6px;
background: #000;
color: white;
padding: 8px;
text-decoration: none;
z-index: 1000;
border-radius: 4px;
}
.skip-to-content:focus {
top: 6px;
}
Platform-Specific Integration Patterns
Jekyll and Liquid Integration
Advanced Jekyll integration with HTML components:
<!-- _includes/html-component-handler.liquid -->
<!-- Enhanced HTML component processor for Jekyll -->
{% comment %}
Usage: {% include html-component-handler.liquid content=page.content %}
{% endcomment %}
{% assign processed_content = include.content %}
<!-- Process custom HTML components -->
{% assign component_types = 'callout,tabs,code-example,gallery,info-card' | split: ',' %}
{% for component_type in component_types %}
{% case component_type %}
{% when 'callout' %}
{% assign processed_content = processed_content | replace: '<md-callout', '<div class="callout' %}
{% assign processed_content = processed_content | replace: '</md-callout>', '</div>' %}
{% when 'tabs' %}
{% assign processed_content = processed_content | replace: '<md-tabs', '<div class="tabs-component' %}
{% assign processed_content = processed_content | replace: '</md-tabs>', '</div>' %}
{% when 'code-example' %}
{% assign processed_content = processed_content | replace: '<md-code-example', '<div class="code-example' %}
{% assign processed_content = processed_content | replace: '</md-code-example>', '</div>' %}
{% endcase %}
{% endfor %}
<!-- Process enhanced attributes -->
{% assign processed_content = processed_content | replace: 'type="info"', 'class="callout-info"' %}
{% assign processed_content = processed_content | replace: 'type="warning"', 'class="callout-warning"' %}
{% assign processed_content = processed_content | replace: 'type="error"', 'class="callout-error"' %}
<!-- Enhanced table processing -->
{% if processed_content contains '<table' %}
{% assign enhanced_tables = processed_content | split: '<table' %}
{% assign rebuilt_content = enhanced_tables[0] %}
{% for table_part in enhanced_tables offset:1 %}
{% if table_part contains 'class="enhanced"' %}
{% assign rebuilt_content = rebuilt_content | append: '<div class="table-wrapper responsive"><table class="enhanced-table"' | append: table_part %}
{% assign rebuilt_content = rebuilt_content | append: '</div>' %}
{% else %}
{% assign rebuilt_content = rebuilt_content | append: '<table' | append: table_part %}
{% endif %}
{% endfor %}
{% assign processed_content = rebuilt_content %}
{% endif %}
<!-- Output processed content -->
{{ processed_content }}
<!-- Add required JavaScript for interactive components -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize tabs
const tabComponents = document.querySelectorAll('.tabs-component');
tabComponents.forEach(component => {
initializeTabs(component);
});
// Initialize copy buttons
const copyButtons = document.querySelectorAll('.copy-button');
copyButtons.forEach(button => {
initializeCopyButton(button);
});
// Initialize enhanced tables
const enhancedTables = document.querySelectorAll('.enhanced-table');
enhancedTables.forEach(table => {
initializeTableEnhancements(table);
});
});
function initializeTabs(component) {
const headers = component.querySelectorAll('.tab-header');
const contents = component.querySelectorAll('.tab-content');
headers.forEach((header, index) => {
header.addEventListener('click', () => {
// Remove active class from all
headers.forEach(h => h.classList.remove('active'));
contents.forEach(c => c.classList.remove('active'));
// Add active class to clicked
header.classList.add('active');
contents[index].classList.add('active');
});
});
}
function initializeCopyButton(button) {
button.addEventListener('click', () => {
const codeBlock = button.closest('.code-example').querySelector('code');
if (codeBlock) {
navigator.clipboard.writeText(codeBlock.textContent).then(() => {
button.textContent = '✓ Copied!';
setTimeout(() => {
button.textContent = '📋';
}, 2000);
});
}
});
}
function initializeTableEnhancements(table) {
// Add sorting to headers
const headers = table.querySelectorAll('th');
headers.forEach((header, index) => {
header.style.cursor = 'pointer';
header.addEventListener('click', () => sortTable(table, index));
});
}
function sortTable(table, columnIndex) {
const tbody = table.querySelector('tbody');
const rows = Array.from(tbody.rows);
const isNumeric = rows.every(row => {
const cellText = row.cells[columnIndex].textContent.trim();
return !isNaN(cellText) && cellText !== '';
});
rows.sort((a, b) => {
const aVal = a.cells[columnIndex].textContent.trim();
const bVal = b.cells[columnIndex].textContent.trim();
if (isNumeric) {
return parseFloat(aVal) - parseFloat(bVal);
} else {
return aVal.localeCompare(bVal);
}
});
rows.forEach(row => tbody.appendChild(row));
}
</script>
Hugo Shortcode Integration
Creating powerful shortcodes for HTML-Markdown integration:
<!-- layouts/shortcodes/enhanced-content.html -->
{{ $type := .Get "type" | default "info" }}
{{ $title := .Get "title" }}
{{ $icon := .Get "icon" }}
{{ $collapsible := .Get "collapsible" | default false }}
{{ $validTypes := slice "info" "warning" "error" "success" "note" }}
{{ $type = cond (in $validTypes $type) $type "info" }}
<div class="callout callout-{{ $type }}" role="alert">
{{ if $title }}
<div class="callout-title">
{{ with $icon }}{{ . }}{{ end }}
{{ $title }}
</div>
{{ end }}
<div class="callout-content{{ if $collapsible }} collapsible{{ end }}">
{{ .Inner | markdownify }}
</div>
</div>
<!-- layouts/shortcodes/tabs.html -->
{{ $defaultTab := .Get "default" | default 0 }}
{{ $tabs := split .Inner "---" }}
<div class="tabs-component">
<div class="tab-headers" role="tablist">
{{ range $index, $tab := $tabs }}
{{ $parts := split $tab ":" }}
{{ $title := index $parts 0 | trim }}
{{ $isActive := eq $index (int $defaultTab) }}
<button class="tab-header{{ if $isActive }} active{{ end }}"
role="tab"
aria-selected="{{ $isActive }}"
data-tab="{{ $index }}">
{{ $title }}
</button>
{{ end }}
</div>
<div class="tab-contents">
{{ range $index, $tab := $tabs }}
{{ $parts := split $tab ":" }}
{{ $content := index $parts 1 | default "" | trim }}
{{ $isActive := eq $index (int $defaultTab) }}
<div class="tab-content{{ if $isActive }} active{{ end }}"
role="tabpanel"
data-tab="{{ $index }}">
{{ $content | markdownify }}
</div>
{{ end }}
</div>
</div>
<!-- layouts/shortcodes/code-example.html -->
{{ $language := .Get "language" | default "text" }}
{{ $title := .Get "title" }}
{{ $filename := .Get "filename" }}
{{ $copyButton := .Get "copy-button" | default true }}
{{ $headerContent := $title | default $filename }}
<div class="code-example" data-language="{{ $language }}">
{{ if $headerContent }}
<div class="code-header">
<span class="code-title">{{ $headerContent }}</span>
{{ if $copyButton }}
<button class="copy-button" title="Copy code">📋</button>
{{ end }}
</div>
{{ end }}
<pre class="code-block"><code class="language-{{ $language }}">{{ .Inner | htmlEscape }}</code></pre>
</div>
<!-- layouts/shortcodes/gallery.html -->
{{ $layout := .Get "layout" | default "grid" }}
{{ $columns := .Get "columns" | default "3" }}
{{ $lightbox := .Get "lightbox" | default false }}
<div class="image-gallery layout-{{ $layout }}" style="--columns: {{ $columns }}">
{{ range $index, $image := split .Inner "\n" }}
{{ if $image }}
{{ $parts := split $image "|" }}
{{ $src := index $parts 0 | trim }}
{{ $alt := index $parts 1 | default "" | trim }}
{{ $caption := index $parts 2 | default $alt | trim }}
{{ if $src }}
<figure class="gallery-item">
<img src="{{ $src }}"
alt="{{ $alt }}"
loading="lazy"
{{ if $lightbox }}data-lightbox="gallery" data-index="{{ $index }}"{{ end }}>
{{ if $caption }}
<figcaption>{{ $caption | markdownify }}</figcaption>
{{ end }}
</figure>
{{ end }}
{{ end }}
{{ end }}
</div>
{{ if $lightbox }}
{{ partial "lightbox-script.html" . }}
{{ end }}
Integration with Modern Documentation Systems
HTML-Markdown integration complements comprehensive documentation workflows. When combined with automated content validation and quality assurance, mixed content systems enable systematic verification of HTML components, accessibility compliance testing, and consistent formatting across complex documentation architectures.
For enhanced content organization, HTML integration works effectively with advanced table systems and performance optimization to create sophisticated data presentation interfaces that combine Markdown simplicity with HTML’s advanced layout capabilities, enabling complex data visualization within standard Markdown workflows.
When developing comprehensive content platforms, HTML-Markdown integration supports Progressive Web App documentation systems by providing offline-capable interactive components, cached HTML processing, and enhanced user interfaces that maintain functionality across different network conditions and device capabilities.
Conclusion
Markdown HTML integration and mixed content systems transform static documentation into dynamic, interactive experiences that leverage the strengths of both markup languages while maintaining accessibility, performance, and maintainability. By implementing comprehensive HTML sanitization, building reusable component systems, and establishing robust integration patterns, technical teams can create sophisticated content platforms that scale effectively across complex documentation requirements.
The key to successful HTML-Markdown integration lies in establishing clear security boundaries, implementing consistent component architectures, and maintaining semantic HTML practices that enhance rather than compromise accessibility. Whether you’re building technical documentation, educational content, or complex reference materials, the integration techniques covered in this guide provide the foundation for creating powerful, flexible content systems that combine Markdown’s simplicity with HTML’s advanced capabilities.
Remember to prioritize security when accepting mixed content, implement comprehensive testing for cross-platform compatibility, and establish clear guidelines for HTML usage within your Markdown workflows. With proper implementation of HTML-Markdown integration systems, your content can achieve new levels of interactivity and sophistication while maintaining the readability and maintainability that makes Markdown an ideal choice for technical documentation.