@fsegurai/marked-extended-tabs
v17.0.0
Published
Extension for Marked.js that adds support for extended tabs, allowing the creation of tabbed content sections within Markdown documents. It supports any Markdown rendering and can be customized to fit your needs.
Maintainers
Readme
An extension library for Marked.js to enhance Markdown rendering.
@fsegurai/marked-extended-tabs Extension for Marked.js that adds support for extended tabs, allowing the creation of tabbed content sections within Markdown documents. It supports any Markdown rendering and can be customized to fit your needs.
🎯 Overview
The marked-extended-tabs extension transforms your Markdown into interactive tabbed content sections with support for custom icons, smooth animations, and nested Markdown content. With a CSS-only implementation for excellent performance and accessibility, it's perfect for code examples, documentation, API references, tutorials, and organizing complex content.
✨ Key Features
- 📑 Interactive Tabs: Create multi-tab content sections with easy navigation
- 🎨 Custom Icons: Add emojis or Unicode icons to tab headers
- ✨ 3 Animation Types: Fade, slide, or no animation between tabs
- 🚀 CSS-Only: Pure CSS implementation for optimal performance
- 📝 Rich Content: Full Markdown support including code, tables, lists
- ♿ Fully Accessible: ARIA labels, keyboard navigation, semantic HTML
- ⌨️ Keyboard Navigation: Arrow keys, Home/End keys for tab switching
- 💾 Persist Selection: Remember tab choice across sessions (localStorage)
- 🎯 Auto-Activation: Automatically activate first tab
- 🎪 Custom Events:
tab-switchedevents for external script integration - 🔄 Callback Hooks:
onBeforeSwitchandonAfterSwitchcallbacks - 🎨 Customizable Styles: Multiple variants (compact, vertical, pills)
- 🌈 Dark Mode Ready: Theme support included
- 📱 Responsive: Mobile-friendly with scrollable tabs
- 🔧 Highly Configurable: Extensive customization options
🎪 Live Demo
Experience all tab styles and animations: View Demo
Table of contents
Installation
To add @fsegurai/marked-extended-tabs along with Marked.js to your package.json use the following commands.
bun install @fsegurai/marked-extended-tabs marked@^17 --saveUsage
Basic Usage
Import @fsegurai/marked-extended-tabs and apply it to your Marked instance as shown below.
Quick Start
Basic Concept
tabs is the identifier for the tabbed container, and tab for individual tab items. Each tab can have a label,
icon, and active state.
import { marked } from 'marked';
import markedExtendedTabs from '@fsegurai/marked-extended-tabs';
// 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-tabs/lib/index.umd.js"></script>
marked.use(markedExtendedTabs());
### Installation
Install the package using your preferred package manager:
```bash
# Using Bun (recommended)
bun add @fsegurai/marked-extended-tabs
# Using npm
npm install @fsegurai/marked-extended-tabs
# Using yarn
yarn add @fsegurai/marked-extended-tabs
# Using pnpm
pnpm add @fsegurai/marked-extended-tabsBasic Implementation
import {marked} from 'marked';
import markedExtendedTabs from '@fsegurai/marked-extended-tabs';
// Import styles (required for functionality)
import '@fsegurai/marked-extended-tabs/styles/tabs.css';
// Optional: Import theme for enhanced visuals
import '@fsegurai/marked-extended-tabs/styles/tabs-theme.css';
// Register the extension
marked.use(markedExtendedTabs({
animation: 'fade',
autoActivate: true,
persistSelection: false
}));
// Your markdown content with tabs
const markdown = `
# API Documentation
## Quick Start Guide
::::tabs
:::tab{label="JavaScript" icon="📜" active="true"}
## Installation
\`\`\`bash
npm install @myapi/client
\`\`\`
## Basic Usage
\`\`\`javascript
import { APIClient } from '@myapi/client';
const client = new APIClient({
apiKey: 'your-api-key',
baseURL: 'https://api.example.com'
});
// Fetch data
const users = await client.users.list();
console.log(users);
// Create a resource
const newUser = await client.users.create({
name: 'John Doe',
email: '[email protected]'
});
\`\`\`
**Key Features:**
- ✅ TypeScript support
- ✅ Promise-based API
- ✅ Automatic retries
- ✅ Request/response interceptors
:::tabend
:::tab{label="Python" icon="🐍"}
## Installation
\`\`\`bash
pip install myapi-client
\`\`\`
## Basic Usage
\`\`\`python
from myapi import APIClient
# Initialize client
client = APIClient(
api_key='your-api-key',
base_url='https://api.example.com'
)
# Fetch data
users = client.users.list()
print(users)
# Create a resource
new_user = client.users.create(
name='John Doe',
email='[email protected]'
)
\`\`\`
**Key Features:**
- ✅ Type hints included
- ✅ Async/await support
- ✅ Automatic pagination
- ✅ Built-in error handling
:::tabend
:::tab{label="cURL" icon="🔧"}
## Authentication
Add your API key to the request headers:
\`\`\`bash
curl -H "Authorization: Bearer YOUR_API_KEY" \\
https://api.example.com/users
\`\`\`
## List Users
\`\`\`bash
curl -X GET https://api.example.com/users \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json"
\`\`\`
## Create User
\`\`\`bash
curl -X POST https://api.example.com/users \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
"name": "John Doe",
"email": "[email protected]"
}'
\`\`\`
**Response:**
\`\`\`json
{
"id": "usr_123",
"name": "John Doe",
"email": "[email protected]",
"created_at": "2026-02-17T10:00:00Z"
}
\`\`\`
:::tabend
:::tab{label="Ruby" icon="💎"}
## Installation
\`\`\`bash
gem install myapi-client
\`\`\`
## Basic Usage
\`\`\`ruby
require 'myapi'
# Initialize client
client = MyAPI::Client.new(
api_key: 'your-api-key',
base_url: 'https://api.example.com'
)
# Fetch data
users = client.users.list
puts users
# Create a resource
new_user = client.users.create(
name: 'John Doe',
email: '[email protected]'
)
\`\`\`
**Key Features:**
- ✅ Ruby 3.0+ compatible
- ✅ ActiveRecord-style interface
- ✅ Comprehensive test suite
- ✅ Webhook support
:::tabend
::::tabsend
## Response Format
All API responses follow this structure:
\`\`\`json
{
"data": { /* Your resource */ },
"meta": {
"timestamp": "2026-02-17T10:00:00Z",
"version": "v2.0"
}
}
\`\`\`
`;
// Parse and render
const html = marked.parse(markdown);
document.getElementById('content').innerHTML = html;Syntax & Usage
Basic Tabs
Create simple tabs with labels:
::::tabs
:::tab{label="Tab 1" active="true"}
Content for tab 1
:::tabend
:::tab{label="Tab 2"}
Content for tab 2
:::tabend
:::tab{label="Tab 3"}
Content for tab 3
:::tabend
::::tabsendTabs with Icons
Add emojis or Unicode icons:
::::tabs
:::tab{label="JavaScript" icon="📜" active="true"}
JavaScript code here
:::tabend
:::tab{label="Python" icon="🐍"}
Python code here
:::tabend
:::tab{label="Ruby" icon="💎"}
Ruby code here
:::tabend
::::tabsendAuto-Active First Tab
When no tab has active="true", the first tab activates automatically (if autoActivate: true):
::::tabs
:::tab{label="Overview"}
First tab (auto-active)
:::tabend
:::tab{label="Details"}
Second tab
:::tabend
::::tabsendRich Content in Tabs
Tabs support full Markdown:
::::tabs
:::tab{label="Documentation" icon="📖" active="true"}
# Getting Started
Follow these steps:
1. Install the package
2. Import the library
3. Initialize the client
## Code Example
\`\`\`javascript
import { Client } from 'mylib';
const client = new Client();
\`\`\`
**Important**: Don't forget to set your API key!
[Read more →](https://docs.example.com)
:::tabend
:::tab{label="FAQ" icon="❓"}
## Frequently Asked Questions
### How do I authenticate?
Use your API key in the Authorization header.
### What's the rate limit?
- Free tier: 100 requests/hour
- Pro tier: 10,000 requests/hour
- Enterprise: Custom limits
:::tabend
::::tabsendShort Aliases
Use shorter syntax:
:tabs
:tab{label="Tab 1"}
Content here
:tabend
:tab{label="Tab 2"}
More content
:tabend
:tabsend
The extension supports **nested Markdown content** within tabs, including code blocks, tables, lists, and even other
marked extensions. Tabs use a CSS-only implementation for optimal performance and can be styled to match your design
system.
### Styling Your Tabs
This extension provides optional CSS/SCSS files located in the `styles/` directory. Import them into your project to
style the tabs.
Usage examples:
```javascript
// JS/TS
import '@fsegurai/marked-extended-tabs/styles/tabs.css';
import '@fsegurai/marked-extended-tabs/styles/tabs-theme.css';<!-- Plain HTML -->
<link rel="stylesheet" href="node_modules/@fsegurai/marked-extended-tabs/styles/tabs.css">
<link rel="stylesheet" href="node_modules/@fsegurai/marked-extended-tabs/styles/tabs-theme.css">Generated HTML Structure
<div class="marked-extended-tabs-container" id="tabs-{id}">
<nav class="marked-extended-tabs-nav" role="tablist">
<input type="radio" name="tabs-{id}" id="tab-{id}-0" class="marked-extended-tabs-input" checked>
<label for="tab-{id}-0" class="marked-extended-tabs-label" role="tab">
<span class="marked-extended-tabs-icon">🔧</span>
<span class="marked-extended-tabs-label-text">JavaScript</span>
</label>
<!-- More tabs... -->
</nav>
<div class="marked-extended-tabs-content">
<div class="marked-extended-tabs-content-pane" role="tabpanel">
<!-- Tab 1 content -->
</div>
<div class="marked-extended-tabs-content-pane" role="tabpanel">
<!-- Tab 2 content -->
</div>
</div>
</div>CSS Classes Reference
| Class | Purpose | Element |
|-----------------------------------------------------|----------------------|----------------|
| .marked-extended-tabs-container | Main wrapper | Container |
| .marked-extended-tabs-nav | Tab navigation bar | Nav |
| .marked-extended-tabs-input | Hidden radio input | Input (hidden) |
| .marked-extended-tabs-label | Tab button/label | Label |
| .marked-extended-tabs-label[aria-selected="true"] | Active tab | Active Label |
| .marked-extended-tabs-icon | Tab icon | Span |
| .marked-extended-tabs-label-text | Tab text | Span |
| .marked-extended-tabs-content | Content area wrapper | Div |
| .marked-extended-tabs-content-pane | Individual tab pane | Div |
Complete Styling Example
/* Container */
.marked-extended-tabs-container {
border: 1px solid #ddd;
border-radius: 8px;
background: #fff;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
/* Navigation Bar */
.marked-extended-tabs-nav {
display: flex;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
overflow-x: auto;
scrollbar-width: thin;
}
/* Hide radio inputs */
.marked-extended-tabs-input {
position: absolute;
opacity: 0;
pointer-events: none;
}
/* Tab Labels (Buttons) */
.marked-extended-tabs-label {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
cursor: pointer;
border-bottom: 2px solid transparent;
background: transparent;
transition: all 0.2s ease;
font-weight: 500;
color: #666;
white-space: nowrap;
}
.marked-extended-tabs-label:hover {
background: #e8e8e8;
color: #333;
}
/* Active Tab */
.marked-extended-tabs-label[aria-selected="true"] {
background: #fff;
border-bottom-color: #0066cc;
color: #0066cc;
font-weight: 600;
}
/* Tab Icon */
.marked-extended-tabs-icon {
font-size: 1.2rem;
opacity: 0.8;
}
/* Content Area */
.marked-extended-tabs-content {
padding: 1.5rem;
background: #fff;
}
.marked-extended-tabs-content-pane {
display: none;
}
/* Show active pane (controlled by radio input) */
.marked-extended-tabs-input:checked + .marked-extended-tabs-label + * .marked-extended-tabs-content-pane:nth-of-type(1),
#tab-id-0:checked ~ .marked-extended-tabs-content .marked-extended-tabs-content-pane:nth-of-type(1) {
display: block;
animation: tab-fade-in 0.3s ease;
}
/* Fade animation */
@keyframes tab-fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}Variant: Compact Tabs
.marked-extended-tabs-container.tabs-compact .marked-extended-tabs-label {
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
}
.marked-extended-tabs-container.tabs-compact .marked-extended-tabs-content {
padding: 0.75rem;
}Variant: Vertical Tabs
.marked-extended-tabs-container.tabs-vertical {
display: flex;
}
.marked-extended-tabs-container.tabs-vertical .marked-extended-tabs-nav {
flex-direction: column;
min-width: 200px;
border-right: 1px solid #ddd;
border-bottom: none;
}
.marked-extended-tabs-container.tabs-vertical .marked-extended-tabs-label {
border-bottom: none;
border-right: 2px solid transparent;
justify-content: flex-start;
}
.marked-extended-tabs-container.tabs-vertical .marked-extended-tabs-label[aria-selected="true"] {
border-right-color: #0066cc;
border-bottom: none;
}
.marked-extended-tabs-container.tabs-vertical .marked-extended-tabs-content {
flex: 1;
}Variant: Pills Style
.marked-extended-tabs-container.tabs-pills .marked-extended-tabs-nav {
gap: 0.5rem;
padding: 0.5rem;
background: #f8f9fa;
border-bottom: none;
}
.marked-extended-tabs-container.tabs-pills .marked-extended-tabs-label {
border-radius: 20px;
border: none;
padding: 0.5rem 1rem;
}
.marked-extended-tabs-container.tabs-pills .marked-extended-tabs-label:hover {
background: #e9ecef;
}
.marked-extended-tabs-container.tabs-pills .marked-extended-tabs-label[aria-selected="true"] {
background: #0066cc;
color: white;
border: none;
}Dark Mode Support
/* Light theme */
body.light .marked-extended-tabs-container {
background: #ffffff;
border-color: #ddd;
}
body.light .marked-extended-tabs-nav {
background: #f5f5f5;
border-bottom-color: #ddd;
}
body.light .marked-extended-tabs-label {
color: #333;
}
body.light .marked-extended-tabs-label:hover {
background: #e8e8e8;
}
body.light .marked-extended-tabs-content {
background: #ffffff;
}
/* Dark theme */
body.dark .marked-extended-tabs-container {
background: #22272e;
border-color: #444c56;
}
body.dark .marked-extended-tabs-nav {
background: #2d333b;
border-bottom-color: #444c56;
}
body.dark .marked-extended-tabs-label {
color: #d1d5da;
}
body.dark .marked-extended-tabs-label:hover {
background: #444c56;
}
body.dark .marked-extended-tabs-label[aria-selected="true"] {
background: #3b4551;
color: #58a6ff;
}
body.dark .marked-extended-tabs-content {
background: #22272e;
color: #d1d5da;
}Accessibility Enhancements
/* Focus styles for keyboard navigation */
.marked-extended-tabs-input:focus + .marked-extended-tabs-label {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* Remove outline for mouse users */
.marked-extended-tabs-input:focus:not(:focus-visible) + .marked-extended-tabs-label {
outline: none;
}Copy Demo Theme
For complete styling with all variants: tabs-theme.css
Check the demo to see all tab variants in action.
Aliases
The tabs block can be rendered using alternative aliases for start and end blocks:
- Start Aliases
:tbs:tabs
- End Aliases
:tbsend:tabsend
Individual tabs can use these aliases:
- Tab Start Aliases
:tab
- Tab End Aliases
:tabend
Configuration Options
The marked-extended-tabs extension accepts the following configuration options:
className: The base CSS class name for tabs container. Defaults to 'marked-extended-tabs-container'.persistSelection: Whether to remember tab selection across page reloads. Defaults to false.animation: Animation type for tab transitions. Defaults to 'fade'. See Animation Types.autoActivate: Automatically activate the first tab if none is explicitly active. Defaults to true.template: A custom HTML template for the tabs structure. Defaults to the built-in template.customizeToken: A function that allows you to customize the token object before rendering. Defaults to null.enableKeyboardNavigation: Enable arrow key navigation between tabs. Defaults to true.enableFocusManagement: Enable automatic focus management for accessibility. Defaults to true.onBeforeSwitch: Callback function fired before switching tabs (can prevent switch). Defaults to null.onAfterSwitch: Callback function fired after switching tabs. Defaults to null.
Tab syntax parameters:
label: The display text for the tab header. Defaults to 'Tab N' where N is the tab number.active: Whether this tab is active by default ("true" or "false"). Defaults to "false".icon: An optional icon (emoji or Unicode) to display in the tab header. No default.
Enhanced Tab Switching Features
The marked-extended-tabs extension now includes advanced interactivity features that enhance user experience and accessibility:
🎯 Keyboard Navigation
Users can navigate between tabs using keyboard shortcuts:
- Arrow Right / Arrow Down - Move to the next tab
- Arrow Left / Arrow Up - Move to the previous tab
- Home - Jump to the first tab
- End - Jump to the last tab
- Space / Enter - Activate the focused tab
Keyboard navigation is enabled by default. Disable it with:
marked.use(markedExtendedTabs({
enableKeyboardNavigation: false
}));♿ Focus Management
Automatic focus management for keyboard accessibility:
- Tabs are properly tabindexed for keyboard navigation
- Active tab receives focus index 0
- Other tabs have focus index -1
- Focus automatically updates when switching tabs
- Screen reader support with ARIA attributes
Enable/disable with:
marked.use(markedExtendedTabs({
enableFocusManagement: true // Default: true
}));💾 State Persistence
Tab selection is automatically persisted to localStorage:
- Each tab container has a unique storage key
- When the page is reloaded, the previously selected tab is restored
- Multiple tab groups maintain separate states
- Works across browser sessions
Enable/disable with:
marked.use(markedExtendedTabs({
persistSelection: true // Default: false
}));Storage key format: marked-extended-tabs-active-{containerId}
🎪 Custom Events
When a tab is switched, a custom tab-switched event is automatically dispatched:
document.addEventListener('tab-switched', (event) => {
console.log('Tab switched:', event.detail.tabId);
console.log('Timestamp:', event.detail.timestamp);
});Event detail structure:
{
tabId: string; // ID of the newly active tab
timestamp: number; // Timestamp of the switch event (milliseconds)
}🔄 Callback Hooks
Execute custom logic before and after tab switches using callback functions:
marked.use(markedExtendedTabs({
onBeforeSwitch: (event) => {
console.log('Switching from', event.previousTabId, 'to', event.newTabId);
// Return false to prevent the switch
return true;
},
onAfterSwitch: (event) => {
console.log('Switched to tab:', event.newTabId);
}
}));onBeforeSwitch event detail:
{
previousTabId: string | null; // ID of the currently active tab (null on first)
newTabId: string; // ID of the tab being switched to
tabIndex: number; // Index of the new tab
timestamp: number; // Timestamp of the event
}Return false from onBeforeSwitch to prevent the tab switch. Return true or any truthy value to allow it.
onAfterSwitch event detail:
{
previousTabId: string | null; // ID of the previously active tab (null on first)
newTabId: string; // ID of the newly active tab
tabIndex: number; // Index of the new tab
timestamp: number; // Timestamp of the event
}📋 Complete Configuration Example
import {marked} from 'marked';
import markedExtendedTabs from '@fsegurai/marked-extended-tabs';
import '@fsegurai/marked-extended-tabs/styles/tabs.css';
marked.use(markedExtendedTabs({
animation: 'fade',
autoActivate: true,
persistSelection: false,
enableKeyboardNavigation: true,
enableFocusManagement: true,
onBeforeSwitch: (event) => {
// Validate or prevent switches based on your logic
if (someUnsavedChanges) {
console.warn('Cannot switch - unsaved changes');
return false; // Prevent switch
}
return true; // Allow switch
},
onAfterSwitch: (event) => {
// Track analytics or update other UI elements
analytics.trackEvent('tab_switched', {
tab_id: event.newTabId,
tab_index: event.tabIndex
});
}
}));Animation Types
The extension supports three animation types:
'fade': Smooth fade in/out transition between tabs (default)'slide': Horizontal sliding animation between tab contents'none': No animation, instant tab switching
Example with custom animation:
marked.use(markedExtendedTabs({
animation: 'slide',
autoActivate: true
}));Advanced Examples
Tabs with Icons and Mixed Content
::::tabs
:::tab{label="Overview" icon="📋" active="true"}
## Project Overview
This project demonstrates the power of **nested Markdown** within tabs.
- ✅ Code highlighting
- ✅ Tables and lists
- ✅ Images and links
- ✅ Other extensions
[Learn more →](https://example.com)
:::tabend
:::tab{label="API Reference" icon="⚙️"}
### REST API Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/users` | List all users |
| POST | `/api/users` | Create user |
| PUT | `/api/users/{id}` | Update user |
> **Note**: All endpoints require authentication.
:::tabend
:::tab{label="Examples" icon="💻"}
### Code Examples
JavaScript implementation:
\`\`\`javascript
const api = new APIClient({
baseUrl: 'https://api.example.com',
apiKey: 'your-key-here'
});
const users = await api.get('/users');
console.log(users);
\`\`\`
Python implementation:
\`\`\`python
import requests
response = requests.get('https://api.example.com/users')
users = response.json()
print(users)
\`\`\`
:::tabend
::::tabsendConfiguration Example
import {marked} from "marked";
import markedExtendedTabs from "@fsegurai/marked-extended-tabs";
marked.use(markedExtendedTabs({
className: 'my-custom-tabs',
animation: 'slide',
persistSelection: false,
autoActivate: true,
customizeToken: (token) => {
// Add custom logic here
console.log('Processing tabs token:', token);
}
}));Tab Features Summary
The extension provides comprehensive tab functionality:
| Feature | Description | Example | Default |
|-----------------------|--------------------|--------------------------|-----------------------------|
| Labels | Tab header text | label="Overview" | "Tab N" |
| Icons | Emoji or Unicode | icon="📋" | None |
| Active State | Default active tab | active="true" | First tab (if autoActivate) |
| Animations | Transition effects | animation="fade" | "fade" |
| Persist Selection | Remember choice | persistSelection: true | false |
| Auto-Activate | First tab active | autoActivate: true | true |
| Rich Content | Full Markdown | All Markdown syntax | Supported |
| Nested Extensions | Other extensions | Accordions, tables, etc. | Supported |
Best Practices
1. Use Descriptive Labels
<!-- Good: Clear, descriptive labels -->
::::tabs
:::tab{label="Installation Guide"}
:::tab{label="Configuration Options"}
:::tab{label="API Reference"}
::::tabsend
<!-- Avoid: Vague labels -->
::::tabs
:::tab{label="Info"}
:::tab{label="More"}
:::tab{label="Other"}
::::tabsend2. Appropriate Icon Usage
<!-- Good: Relevant icons -->
:::tab{label="JavaScript" icon="📜"}
:::tab{label="Python" icon="🐍"}
:::tab{label="Ruby" icon="💎"}
<!-- Avoid: Confusing or irrelevant -->
:::tab{label="JavaScript" icon="🌮"} <!-- ❌ -->
:::tab{label="Python" icon="🚗"} <!-- ❌ -->3. Limit Number of Tabs
<!-- Good: 3-7 tabs for easy navigation -->
::::tabs
:::tab{label="Overview"}
:::tab{label="Installation"}
:::tab{label="Usage"}
:::tab{label="Examples"}
::::tabsend
<!-- Avoid: Too many tabs -->
::::tabs
<!-- 15+ tabs make navigation difficult -->
::::tabsend4. Consistent Content Structure
<!-- Good: Similar structure across tabs -->
::::tabs
:::tab{label="JavaScript" icon="📜"}
## Installation
\`\`\`bash
npm install
\`\`\`
## Usage
\`\`\`javascript
// code
\`\`\`
:::tabend
:::tab{label="Python" icon="🐍"}
## Installation
\`\`\`bash
pip install
\`\`\`
## Usage
\`\`\`python
# code
\`\`\`
:::tabend
::::tabsend
<!-- Avoid: Inconsistent structure -->
::::tabs
:::tab{label="Tab 1"}
# Heading
Content
:::tabend
:::tab{label="Tab 2"}
Just some text without headings or structure
:::tabend
::::tabsend5. Mark Appropriate Default Tab
<!-- Good: First tab or most relevant active -->
::::tabs
:::tab{label="Quick Start" icon="⚡" active="true"}
Most users start here
:::tabend
:::tab{label="Advanced"}
For experienced users
:::tabend
::::tabsend
<!-- Avoid: Marking wrong tab as active -->
::::tabs
:::tab{label="Quick Start"}
:::tabend
:::tab{label="Advanced Setup" active="true"}
<!-- ❌ New users shouldn't start here -->
:::tabend
::::tabsendUse Cases
1. Multi-Language Code Examples
# Authentication API
## Making Your First Request
::::tabs
:::tab{label="JavaScript" icon="📜" active="true"}
### Using Fetch API
\`\`\`javascript
const response = await fetch('https://api.example.com/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: '[email protected]',
password: 'secure_password'
})
});
const data = await response.json();
console.log('Token:', data.access_token);
// Use token in subsequent requests
const userData = await fetch('https://api.example.com/user/profile', {
headers: {
'Authorization': \`Bearer \${data.access_token}\`
}
});
\`\`\`
### Using Axios
\`\`\`javascript
import axios from 'axios';
const { data } = await axios.post('https://api.example.com/auth/login', {
username: '[email protected]',
password: 'secure_password'
});
console.log('Token:', data.access_token);
// Set default authorization header
axios.defaults.headers.common['Authorization'] = \`Bearer \${data.access_token}\`;
\`\`\`
:::tabend
:::tab{label="Python" icon="🐍"}
### Using Requests
\`\`\`python
import requests
# Login request
response = requests.post(
'https://api.example.com/auth/login',
json={
'username': '[email protected]',
'password': 'secure_password'
}
)
data = response.json()
access_token = data['access_token']
print(f'Token: {access_token}')
# Use token for authenticated requests
user_response = requests.get(
'https://api.example.com/user/profile',
headers={'Authorization': f'Bearer {access_token}'}
)
print(user_response.json())
\`\`\`
### Using HTTPX (Async)
\`\`\`python
import httpx
import asyncio
async def authenticate():
async with httpx.AsyncClient() as client:
# Login
response = await client.post(
'https://api.example.com/auth/login',
json={
'username': '[email protected]',
'password': 'secure_password'
}
)
data = response.json()
return data['access_token']
# Run async function
token = asyncio.run(authenticate())
\`\`\`
:::tabend
:::tab{label="Go" icon="🔷"}
### Using net/http
\`\`\`go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
type LoginRequest struct {
Username string \`json:"username"\`
Password string \`json:"password"\`
}
type LoginResponse struct {
AccessToken string \`json:"access_token"\`
}
func main() {
// Prepare login request
loginData := LoginRequest{
Username: "[email protected]",
Password: "secure_password",
}
jsonData, _ := json.Marshal(loginData)
// Make request
resp, err := http.Post(
"https://api.example.com/auth/login",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Parse response
body, _ := ioutil.ReadAll(resp.Body)
var loginResp LoginResponse
json.Unmarshal(body, &loginResp)
fmt.Println("Token:", loginResp.AccessToken)
}
\`\`\`
:::tabend
:::tab{label="cURL" icon="🔧"}
### Basic Authentication Request
\`\`\`bash
curl -X POST https://api.example.com/auth/login \\
-H "Content-Type: application/json" \\
-d '{
"username": "[email protected]",
"password": "secure_password"
}'
\`\`\`
**Response:**
\`\`\`json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
\`\`\`
### Using the Token
\`\`\`bash
# Save token to variable
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# Make authenticated request
curl -X GET https://api.example.com/user/profile \\
-H "Authorization: Bearer \$TOKEN"
\`\`\`
:::tabend
::::tabsend
## Error Handling
All endpoints return standard error responses:
\`\`\`json
{
"error": {
"code": "INVALID_CREDENTIALS",
"message": "The username or password is incorrect",
"timestamp": "2026-02-17T10:30:00Z"
}
}
\`\`\`2. Platform-Specific Installation
# Installation Guide
## Choose Your Platform
::::tabs
:::tab{label="Windows" icon="🪟" active="true"}
### Using Installer
1. **Download** the Windows installer:
- [Download for Windows (64-bit)](https://example.com/download/windows-x64)
- [Download for Windows (32-bit)](https://example.com/download/windows-x86)
2. **Run** the installer and follow the setup wizard
3. **Verify** installation:
\`\`\`powershell
myapp --version
\`\`\`
### Using Package Manager (Chocolatey)
\`\`\`powershell
choco install myapp
\`\`\`
### Using Package Manager (Winget)
\`\`\`powershell
winget install MyCompany.MyApp
\`\`\`
### System Requirements
- Windows 10 or later
- 4GB RAM minimum (8GB recommended)
- 500MB free disk space
- .NET Runtime 6.0 or later
### Troubleshooting
**"Command not found" error:**
Add installation directory to PATH:
1. Open System Properties → Environment Variables
2. Edit PATH variable
3. Add: \`C:\\Program Files\\MyApp\\bin\`
:::tabend
:::tab{label="macOS" icon="🍎"}
### Using Homebrew (Recommended)
\`\`\`bash
brew install myapp
\`\`\`
### Using DMG Installer
1. **Download** the macOS installer:
- [Download for macOS (Apple Silicon)](https://example.com/download/macos-arm64)
- [Download for macOS (Intel)](https://example.com/download/macos-x64)
2. **Open** the .dmg file
3. **Drag** MyApp to Applications folder
4. **Verify** installation:
\`\`\`bash
myapp --version
\`\`\`
### System Requirements
- macOS 11 (Big Sur) or later
- 4GB RAM minimum
- 500MB free disk space
### Troubleshooting
**"App can't be opened" error:**
Right-click the app → Open → Confirm to bypass Gatekeeper
:::tabend
:::tab{label="Linux" icon="🐧"}
### Using Package Manager
**Ubuntu/Debian:**
\`\`\`bash
sudo apt update
sudo apt install myapp
\`\`\`
**Fedora/RHEL:**
\`\`\`bash
sudo dnf install myapp
\`\`\`
**Arch Linux:**
\`\`\`bash
sudo pacman -S myapp
\`\`\`
### Using Snap
\`\`\`bash
sudo snap install myapp
\`\`\`
### Building from Source
\`\`\`bash
git clone https://github.com/mycompany/myapp.git
cd myapp
./configure
make
sudo make install
\`\`\`
### System Requirements
- Linux kernel 5.0 or later
- 4GB RAM minimum
- 500MB free disk space
- glibc 2.31 or later
### Verify Installation
\`\`\`bash
myapp --version
which myapp
\`\`\`
:::tabend
:::tab{label="Docker" icon="🐳"}
### Using Docker
Pull the official image:
\`\`\`bash
docker pull mycompany/myapp:latest
\`\`\`
Run the container:
\`\`\`bash
docker run -d \\
--name myapp \\
-p 8080:8080 \\
-v \$(pwd)/data:/app/data \\
mycompany/myapp:latest
\`\`\`
### Using Docker Compose
Create \`docker-compose.yml\`:
\`\`\`yaml
version: '3.8'
services:
myapp:
image: mycompany/myapp:latest
ports:
- "8080:8080"
volumes:
- ./data:/app/data
environment:
- APP_ENV=production
- LOG_LEVEL=info
\`\`\`
Start services:
\`\`\`bash
docker-compose up -d
\`\`\`
### Available Tags
- \`latest\` - Latest stable release
- \`2.0.5\` - Specific version
- \`alpine\` - Lightweight Alpine-based image
- \`dev\` - Development build
:::tabend
::::tabsend
## Post-Installation
After installation, configure your environment:
\`\`\`bash
myapp init
myapp config set api-key YOUR_KEY
\`\`\`3. Framework Comparison
# Web Framework Comparison
## React vs Vue vs Angular
::::tabs
:::tab{label="React" icon="⚛️" active="true"}
### Overview
React is a JavaScript library for building user interfaces, focusing on component-based architecture.
**Pros:**
- ✅ Large ecosystem and community
- ✅ Virtual DOM for performance
- ✅ Flexible and unopinionated
- ✅ Excellent for SPAs
- ✅ Strong TypeScript support
**Cons:**
- ❌ Steep learning curve
- ❌ JSX syntax to learn
- ❌ Need additional libraries for routing, state
### Hello World Example
\`\`\`jsx
import React from 'react';
function App() {
const [count, setCount] = React.useState(0);
return (
<div>
<h1>Hello, React!</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
export default App;
\`\`\`
### When to Choose React
- Building complex SPAs
- Need flexibility in architecture
- Large team with React expertise
- Mobile app with React Native
:::tabend
:::tab{label="Vue" icon="💚"}
### Overview
Vue is a progressive framework for building user interfaces with a gentle learning curve.
**Pros:**
- ✅ Easy to learn
- ✅ Great documentation
- ✅ Two-way data binding
- ✅ Smaller bundle size
- ✅ Template syntax familiar to HTML
**Cons:**
- ❌ Smaller ecosystem than React
- ❌ Less corporate backing
- ❌ Fewer job opportunities
### Hello World Example
\`\`\`vue
<template>
<div>
<h1>Hello, Vue!</h1>
<p>Count: {{ count }}</p>
<button @click="count++">
Increment
</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
\`\`\`
### When to Choose Vue
- Quick prototyping
- Small to medium projects
- Team with HTML/CSS background
- Gradual migration from legacy code
:::tabend
:::tab{label="Angular" icon="🅰️"}
### Overview
Angular is a full-featured framework with batteries included for enterprise applications.
**Pros:**
- ✅ Complete solution (routing, HTTP, forms)
- ✅ TypeScript by default
- ✅ Strong corporate backing (Google)
- ✅ Great for large applications
- ✅ CLI tooling
**Cons:**
- ❌ Steeper learning curve
- ❌ Larger bundle size
- ❌ More opinionated
- ❌ Verbose syntax
### Hello World Example
\`\`\`typescript
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: \`
<div>
<h1>Hello, Angular!</h1>
<p>Count: {{ count }}</p>
<button (click)="increment()">
Increment
</button>
</div>
\`
})
export class AppComponent {
count = 0;
increment() {
this.count++;
}
}
\`\`\`
### When to Choose Angular
- Enterprise applications
- Large teams
- Need complete framework
- TypeScript-first development
:::tabend
:::tab{label="Comparison" icon="📊"}
### Side-by-Side Comparison
| Feature | React | Vue | Angular |
|---------|-------|-----|---------|
| **Type** | Library | Framework | Framework |
| **Learning Curve** | Moderate | Easy | Steep |
| **Bundle Size** | Medium | Small | Large |
| **TypeScript** | Optional | Optional | Default |
| **Performance** | Excellent | Excellent | Good |
| **Ecosystem** | Huge | Growing | Large |
| **Best For** | SPAs, Complex UIs | All projects | Enterprise |
| **Mobile** | React Native | Weex, NativeScript | Ionic |
| **State Mgmt** | Redux, Context | Vuex, Pinia | Services, RxJS |
| **Community** | Very Large | Large | Large |
### Performance Benchmarks
| Metric | React | Vue | Angular |
|--------|-------|-----|---------|
| Initial Load | 45 KB | 38 KB | 95 KB |
| Time to Interactive | 1.2s | 0.9s | 1.8s |
| Memory Usage | 12 MB | 9 MB | 18 MB |
| DOM Operations | Fast | Fast | Good |
### Popularity (GitHub Stars)
- React: ⭐ 220K+
- Vue: ⭐ 205K+
- Angular: ⭐ 93K+
*As of February 2026*
:::tabend
::::tabsend
## Making Your Choice
Consider your:
- Team expertise
- Project requirements
- Timeline
- Long-term maintenance4. Configuration Environments
# Environment Configuration
## Setup for Different Environments
::::tabs
:::tab{label="Development" icon="💻" active="true"}
### Development Environment Setup
\`\`\`bash
# Install dependencies
npm install
# Copy environment file
cp .env.example .env.development
# Start dev server
npm run dev
\`\`\`
### Configuration (\`.env.development\`)
\`\`\`env
# API Configuration
API_URL=http://localhost:3000
API_KEY=dev_key_1234567890
# Database
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_dev
DB_USER=dev_user
DB_PASSWORD=dev_password
# Features
ENABLE_DEBUG=true
ENABLE_HOT_RELOAD=true
ENABLE_SOURCE_MAPS=true
# Logging
LOG_LEVEL=debug
LOG_TO_FILE=false
\`\`\`
### Dev Tools
- ✅ Hot module replacement
- ✅ Source maps enabled
- ✅ Verbose logging
- ✅ Mock API responses
- ✅ Local database
:::tabend
:::tab{label="Staging" icon="🧪"}
### Staging Environment Setup
\`\`\`bash
# Build for staging
npm run build:staging
# Deploy to staging
npm run deploy:staging
\`\`\`
### Configuration (\`.env.staging\`)
\`\`\`env
# API Configuration
API_URL=https://staging-api.example.com
API_KEY=staging_key_secure_token
# Database
DB_HOST=staging-db.example.com
DB_PORT=5432
DB_NAME=myapp_staging
DB_USER=staging_user
DB_PASSWORD=staging_secure_password
# Features
ENABLE_DEBUG=false
ENABLE_HOT_RELOAD=false
ENABLE_SOURCE_MAPS=true
# Logging
LOG_LEVEL=info
LOG_TO_FILE=true
LOG_PATH=/var/log/myapp
# Monitoring
SENTRY_DSN=https://[email protected]/staging
ANALYTICS_ID=staging-analytics-id
\`\`\`
### Staging Features
- ✅ Production-like environment
- ✅ Real database (non-production)
- ✅ Error tracking enabled
- ✅ Performance monitoring
- ✅ QA testing environment
:::tabend
:::tab{label="Production" icon="🚀"}
### Production Environment Setup
\`\`\`bash
# Build for production
npm run build:production
# Deploy to production
npm run deploy:production
\`\`\`
### Configuration (\`.env.production\`)
\`\`\`env
# API Configuration
API_URL=https://api.example.com
API_KEY=prod_key_super_secure_token
# Database
DB_HOST=prod-db.example.com
DB_PORT=5432
DB_NAME=myapp_prod
DB_USER=prod_user
DB_PASSWORD=prod_ultra_secure_password
# Features
ENABLE_DEBUG=false
ENABLE_HOT_RELOAD=false
ENABLE_SOURCE_MAPS=false
# Logging
LOG_LEVEL=error
LOG_TO_FILE=true
LOG_PATH=/var/log/myapp
LOG_ROTATION=daily
# Monitoring
SENTRY_DSN=https://[email protected]/production
ANALYTICS_ID=prod-analytics-id
# Performance
ENABLE_CACHING=true
CACHE_TTL=3600
CDN_URL=https://cdn.example.com
# Security
RATE_LIMIT=100
CORS_ORIGIN=https://example.com
SSL_CERT=/path/to/cert.pem
\`\`\`
### Production Requirements
- ✅ SSL/TLS enabled
- ✅ CDN configured
- ✅ Caching enabled
- ✅ Rate limiting active
- ✅ Monitoring and alerts
- ✅ Automated backups
- ✅ Load balancing
:::tabend
:::tab{label="Docker" icon="🐳"}
### Docker Configuration
Create \`docker-compose.yml\` per environment:
**Development:**
\`\`\`yaml
version: '3.8'
services:
app:
build:
context: .
target: development
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
command: npm run dev
\`\`\`
**Production:**
\`\`\`yaml
version: '3.8'
services:
app:
build:
context: .
target: production
ports:
- "3000:3000"
environment:
- NODE_ENV=production
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
\`\`\`
### Multi-stage Dockerfile
\`\`\`dockerfile
# Development stage
FROM node:18-alpine AS development
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
# Production stage
FROM node:18-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
CMD ["npm", "start"]
\`\`\`
:::tabend
::::tabsend
## Environment Variables Best Practices
1. ✅ Never commit secrets to version control
2. ✅ Use different keys for each environment
3. ✅ Rotate credentials regularly
4. ✅ Use secret management tools (Vault, AWS Secrets Manager)
5. ✅ Document all environment variablesTroubleshooting
Tabs Not Switching
Problem: Clicking tabs doesn't change content.
Solutions:
- Import CSS:
import '@fsegurai/marked-extended-tabs/styles/tabs.css';- Check for conflicting styles:
/* Make sure nothing is overriding tab display */
.marked-extended-tabs-content-pane {
display: none; /* Should be hidden by default */
}- Verify tab structure:
<!-- Ensure proper nesting -->
::::tabs
:::tab{label="Tab 1"}
Content
:::tabend
::::tabsendIcons Not Showing
Problem: Tab icons don't display.
Solutions:
- Use valid Unicode/emoji:
<!-- Correct -->
:::tab{label="Code" icon="📜"}
:::tab{label="Python" icon="🐍"}
<!-- Wrong -->
:::tab{label="Code" icon="invalid"} <!-- ❌ -->- Check font support:
/* Ensure emoji font is loaded */
.marked-extended-tabs-icon {
font-family: "Apple Color Emoji", "Segoe UI Emoji", sans-serif;
}Animation Not Working
Problem: No animation between tabs.
Solutions:
- Set animation type:
marked.use(markedExtendedTabs({
animation: 'fade' // or 'slide'
}));- Import theme CSS:
import '@fsegurai/marked-extended-tabs/styles/tabs-theme.css';First Tab Not Active
Problem: No tab is active by default.
Solution:
// Enable auto-activation
marked.use(markedExtendedTabs({
autoActivate: true // Default is true
}));Or mark a tab as active:
:::tab{label="Tab 1" active="true"}Framework Integration
React Integration
// TabsContent.tsx
import {marked} from 'marked';
import markedExtendedTabs from '@fsegurai/marked-extended-tabs';
import '@fsegurai/marked-extended-tabs/styles/tabs.css';
import '@fsegurai/marked-extended-tabs/styles/tabs-theme.css';
import {useEffect, useState} from 'react';
marked.use(markedExtendedTabs({
animation: 'fade',
autoActivate: true
}));
interface Props {
content: string;
animation?: 'fade' | 'slide' | 'none';
}
export function TabsContent({content, animation = 'fade'}: Props) {
const [html, setHtml] = useState('');
useEffect(() => {
marked.use(markedExtendedTabs({animation}));
const parsed = marked.parse(content);
setHtml(parsed);
}, [content, animation]);
return (
<div
className="tabs-wrapper"
dangerouslySetInnerHTML={{__html: html}}
/>
);
}
// Usage:
// <TabsContent content={markdownWithTabs} animation="fade" />Vue 3 Integration
<script setup lang="ts">
import { marked } from 'marked';
import markedExtendedTabs from '@fsegurai/marked-extended-tabs';
import '@fsegurai/marked-extended-tabs/styles/tabs.css';
import '@fsegurai/marked-extended-tabs/styles/tabs-theme.css';
import { computed } from 'vue';
marked.use(markedExtendedTabs({
animation: 'fade'
}));
interface Props {
content: string;
animation?: 'fade' | 'slide' | 'none';
}
const props = withDefaults(defineProps<Props>(), {
animation: 'fade'
});
const html = computed(() => {
marked.use(markedExtendedTabs({ animation: props.animation }));
return marked.parse(props.content);
});
</script>
<template>
<div class="tabs-container" v-html="html" />
</template>Angular Integration
// tabs-content.component.ts
import {Component, Input, OnChanges, SimpleChanges} from '@angular/core';
import {DomSanitizer, SafeHtml} from '@angular/platform-browser';
import {marked} from 'marked';
import markedExtendedTabs from '@fsegurai/marked-extended-tabs';
marked.use(markedExtendedTabs({
animation: 'fade'
}));
@Component({
selector: 'app-tabs-content',
template: \`<div [innerHTML]="parsedContent"></div>\`,
styleUrls: [
'../node_modules/@fsegurai/marked-extended-tabs/styles/tabs.css',
'../node_modules/@fsegurai/marked-extended-tabs/styles/tabs-theme.css'
]
})
export class TabsContentComponent implements OnChanges {
@Input() content: string = '';
@Input() animation: 'fade' | 'slide' | 'none' = 'fade';
parsedContent: SafeHtml = '';
constructor(private sanitizer: DomSanitizer) {}
ngOnChanges(changes: SimpleChanges): void {
if (changes['content'] || changes['animation']) {
marked.use(markedExtendedTabs({ animation: this.animation }));
const html = marked.parse(this.content);
this.parsedContent = this.sanitizer.bypassSecurityTrustHtml(html);
}
}
}Performance Tips
- Limit number of tabs: Keep under 10 tabs for best UX
- Lazy load content: For heavy content, consider lazy loading
- Optimize animations: Use 'none' for instant switching if needed
// Disable animations for better performance
marked.use(markedExtendedTabs({
animation: 'none'
}));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.
