Complete Guide to Tabs in Astro MDX

Oct 13, 2025
tutorial astromdxreacttabsmarkdownexpressive-codetutorial
8   Minutes
1467   Words

Complete Guide to Tabs in Astro MDX

Create beautiful, feature-rich tabs in your Astro MDX files with full Markdown support, syntax highlighting, callouts, tables, and more. This component works seamlessly with Astro’s React integration and supports both light and dark themes.

Key Features

  • 🎨 Real-time theme switching (light/dark mode)
  • 📝 Full Markdown support (headers, lists, links, tables)
  • 💻 Expressive Code integration with syntax highlighting
  • 🔔 Callouts/Admonitions (:::tip, :::warning, etc.)
  • 🎯 Default tab selection
  • 🏷️ Auto-generated tab names when labels aren’t provided
  • 🔄 Backward compatibility (use Tab or TabItem)
  • Zero configuration - just import and use

1. Import the Components

import Tabs, { TabItem } from "@/react/Tabs/Tabs.jsx";
// or use backward compatible import
import Tabs, { Tab } from "@/react/Tabs/Tabs.jsx";

or with relative path:

import Tabs, { TabItem } from "../../react/Tabs/Tabs.jsx";
// or use backward compatible import
import Tabs, { Tab } from "../../react/Tabs/Tabs.jsx";

Important: Use .mdx extension (not .md) when working with React components.

2. Basic Usage

Your First Tabs

This is how easy it is to create tabs! You can use:

  • Bold and italic text
  • inline code
  • Links
  • Lists and more Markdown features

💡 Pro Tip: Always include client:load for interactivity in Astro!

Use Tab or TabItem

Both of these work exactly the same:

New way (recommended):

<Tabs defaultValue="example" client:load>
<TabItem value="example" label="My Tab">
Content here...
</TabItem>
<TabItem value="basic" label="My Tab 2">
Content here...
</TabItem>
</Tabs>

Old way (still supported):

<Tabs defaultValue="example" client:load>
<Tab value="example" label="My Tab">
Content here...
</Tab>
<Tab value="basic" label="My Tab 2">
Content here...
</Tab>
</Tabs>

What You Need

For Tabs component:

  • client:load - Makes tabs interactive in Astro
  • defaultValue - Which tab to show first (optional)

For each TabItem:

  • value - Unique ID for the tab
  • label - Text shown on the tab button
  • Content goes between opening/closing tags

That’s it! Super simple to get started.

📚 Complete Feature Guide

3. Code Syntax Highlighting with Expressive Code

Our tabs work seamlessly with Astro’s Expressive Code for beautiful syntax highlighting:

async-fetch.js
async function fetchUserData(userId) {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json(); // Added error handling
if (!response.ok) {
throw new Error('Failed to fetch user data'); // Remove this old approach
}
return data;
}
// Usage example
try {
const user = await fetchUserData(123);
console.log('User:', user);
3 collapsed lines
} catch (error) {
console.error('Error:', error.message);
}
user-types.ts
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
createdAt: Date;
}
interface ApiResponse<T> {
data: T;
status: 'success' | 'error';
message?: string;
}
// Generic function with proper typing
7 collapsed lines
async function apiCall<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
return await response.json() as ApiResponse<T>;
}
// Usage with type safety
const userResponse = await apiCall<User[]>('/api/users');
data_processor.py
5 collapsed lines
import asyncio
import aiohttp
from typing import List, Dict, Optional
from dataclasses import dataclass
from datetime import datetime
@dataclass
class User:
id: int
name: str
email: str
is_active: bool = True
created_at: datetime = datetime.now()
async def fetch_users(session: aiohttp.ClientSession) -> List[User]:
14 collapsed lines
"""Fetch users from API asynchronously."""
async with session.get('/api/users') as response:
data = await response.json()
return [User(**user_data) for user_data in data['users']]
# Example usage
async def main():
async with aiohttp.ClientSession() as session:
users = await fetch_users(session)
active_users = [u for u in users if u.is_active]
print(f"Found {len(active_users)} active users")
if __name__ == "__main__":
asyncio.run(main())

4. Markdown Features Inside Tabs

Full Markdown support means you can use everything from headers to tables:

Text Styling Options

You can use all standard Markdown formatting:

  • Bold text for emphasis
  • Italic text for subtle emphasis
  • inline code for technical terms
  • Strikethrough for corrections
  • Links to anywhere

Lists Work Great

Unordered lists:

  • First item
  • Second item
    • Nested items work too
    • Multiple levels supported
  • Third item

Ordered lists:

  1. Step one
  2. Step two
  3. Step three

Task lists:

  • Completed task
  • Pending task
  • Another completed task

Blockquotes and Callouts

This is a standard blockquote. Great for highlighting important information or featuring quotes from users and documentation.

Using Callouts (if supported by your setup):

Success
You’ve successfully created interactive tabs with full Markdown support!

Tables and Complex Content

Feature Comparison

FeatureBasic HTMLOur TabsNotes
Markdown Support No YesFull MDX compatibility
Syntax Highlighting No YesExpressive Code integration
Theme Switching No YesReal-time, no refresh needed
Mobile Responsive⚠️ Manual YesBuilt-in responsive design
Copy Code Button No YesAutomatic with Expressive Code
Line Numbers No YesConfigurable per code block

5. Smart Tab Configuration

Tabs come with intelligent defaults and automatic features:

Setting Default Active Tab

Use the defaultValue prop to specify which tab opens first:

<Tabs defaultValue="second" client:load>
<TabItem value="first" label="First Tab">
This won't be shown initially
</TabItem>
<TabItem value="second" label="Second Tab">
This tab will be active when the page loads! 🎉
</TabItem>
<TabItem value="third" label="Third Tab">
This is the third option
</TabItem>
</Tabs>

Pro Tips:

  • If no defaultValue is specified, the first tab will be active
  • Make sure your defaultValue matches one of your TabItem value props
  • This works great for highlighting the most important content first

Tabs Without Manual Labels & Values

For quick prototyping, you can omit labels & values and get auto-generated names:

<Tabs client:load>
<TabItem>
## Getting Started
This tab will automatically be labeled "Tab 1"
</TabItem>
<TabItem>
## Advanced Usage
This becomes "Tab 2" automatically
</TabItem>
<TabItem>
## Live Examples
And this becomes "Tab 3"
</TabItem>
</Tabs>

When to use auto-names:

  • Quick prototyping and testing
  • When content is self-explanatory
  • Production apps (users need clear labels)
  • Accessibility-critical applications

Accessibility Note: Always provide meaningful labels for production use to ensure screen readers can properly navigate your tabs.

🚀 Live Examples

Try these interactive examples to see tabs in action!

Multi-Language Code Examples

conditional-logic.js
let score = 85;
const grade = getGrade(score);
function getGrade(score) {
if (score >= 90) {
return "A - Excellent!";
} else if (score >= 80) {
return "B - Good job!";
} else if (score >= 70) {
return "C - Satisfactory";
} else {
return "F - Needs improvement";
}
}
2 collapsed lines
console.log(`Score: ${score}, Grade: ${grade}`);
// Output: Score: 85, Grade: B - Good job!
number_checker.py
def check_number_type(num):
"""Check if a number is even or odd."""
if num % 2 == 0:
return f"{num} is an even number"
else:
return f"{num} is an odd number"
# Interactive example
try:
user_input = int(input("Enter a number: "))
result = check_number_type(user_input)
print(result)
except ValueError:
print("Please enter a valid integer!")
4 collapsed lines
# Test with multiple numbers
test_numbers = [1, 2, 3, 4, 5, 10, 15, 20]
for num in test_numbers:
print(check_number_type(num))
HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
// Simple greeting program
String name = "World";
String greeting = generateGreeting(name);
System.out.println(greeting);
System.out.println("Welcome to Java programming!");
}
private static String generateGreeting(String name) {
return "Hello, " + name + "!";
}
}
3 collapsed lines
// Expected output:
// Hello, World!
// Welcome to Java programming!
type-safe-calculator.ts
interface Calculator {
add(a: number, b: number): number;
subtract(a: number, b: number): number;
multiply(a: number, b: number): number;
divide(a: number, b: number): number;
}
class BasicCalculator implements Calculator {
add(a: number, b: number): number {
return a + b;
}
subtract(a: number, b: number): number {
return a - b;
}
17 collapsed lines
multiply(a: number, b: number): number {
return a * b;
}
divide(a: number, b: number): number {
if (b === 0) {
throw new Error("Division by zero is not allowed");
}
return a / b;
}
}
// Usage example
const calc = new BasicCalculator();
console.log(calc.add(10, 5)); // 15
console.log(calc.multiply(4, 3)); // 12

Tabs with Auto-Generated Names

These tabs don’t have manual labels - they get “Tab 1”, “Tab 2”, etc. automatically:

First Auto-Named Tab

This tab automatically gets labeled as “Tab 1” because no label prop was provided.

Great for:

  • Quick prototyping
  • Testing content layout
  • When content is self-explanatory

Note: For production apps, always provide meaningful labels for better accessibility.

Second Auto-Named Tab

This becomes “Tab 2” automatically.

Features demonstrated:

  • Auto-numbering system
  • Fallback label generation
  • Same functionality as labeled tabs
// This is how it works:
<TabItem value="tab2">
Content here... (no label prop)
</TabItem>
// Results in: "Tab 2"

Third Auto-Named Tab

This becomes “Tab 3” and demonstrates:

Consistency: All tabs get sequential numbering regardless of content length or complexity.

Flexibility: You can mix auto-named and manually labeled tabs in the same set, though it’s not recommended for production.

Testing: Perfect for rapid prototyping when you’re focusing on content rather than navigation.

Fourth Example

Even without any label prop, this tab gets “Tab 4” automatically.

The auto-naming system ensures users can always distinguish between different tabs, even when developers forget to add labels during development.

// Minimal tab setup for testing:
<Tabs client:load>
<TabItem value="one">Content 1</TabItem>
<TabItem value="two">Content 2</TabItem>
<TabItem value="three">Content 3</TabItem>
</Tabs>
// Results in: "Tab 1", "Tab 2", "Tab 3"

🎯 Summary & Next Steps

Congratulations! You now have a complete understanding of the tabs system. Here’s what you’ve learned:

Key Features Covered

  • Basic Usage - Import, setup, and essential props
  • Expressive Code Integration - Syntax highlighting, line numbers, and code features
  • Full Markdown Support - Text formatting, lists, tables, quotes, and callouts
  • Smart Configuration - Default tabs, auto-generated names, responsive design
  • Backward Compatibility - Both Tab and TabItem work identically
  • Best Practices - Performance tips, accessibility, and customization

🚀 Ready to Use

You’re now ready to create beautiful, interactive tabs in your Astro MDX blog posts! Start with the basic example and gradually add more advanced features as needed.

💡 Pro Tips for Success

  1. Always include client:load for interactivity in Astro
  2. Provide meaningful labels for better user experience
  3. Use defaultValue to highlight your most important content
  4. Test your tabs in both light and dark themes
  5. Keep tab content focused and well-organized

Happy coding! 🎉

Article title Complete Guide to Tabs in Astro MDX
Article author Anand Raja
Release time Oct 13, 2025
Copyright 2025
RSS Feed
Sitemap