Markdown Line Numbers in Code Blocks: Complete Guide for Enhanced Code Documentation
Line numbers in Markdown code blocks transform simple code examples into professional, navigable documentation that enhances readability and enables precise reference communication. While standard Markdown doesn’t include built-in line numbering, various platforms, extensions, and custom solutions provide sophisticated approaches to adding numbered lines that improve code comprehension and collaborative development workflows.
Why Use Line Numbers in Code Blocks?
Line numbers provide significant advantages for technical documentation and code sharing:
- Precise Reference: Enable exact line references in discussions and code reviews
- Enhanced Navigation: Help readers locate specific sections in lengthy code examples
- Professional Presentation: Create publication-quality code documentation
- Improved Debugging: Facilitate error reporting and troubleshooting discussions
- Educational Value: Support step-by-step code explanations and tutorials
Platform-Specific Line Number Implementations
GitHub Flavored Markdown
GitHub supports line number highlighting through URL anchors:
## JavaScript Authentication Example
```javascript
// Complete authentication system with error handling
class AuthenticationManager {
constructor(config) {
this.config = {
tokenExpiry: config.tokenExpiry || 3600000, // 1 hour default
maxLoginAttempts: config.maxAttempts || 5,
lockoutDuration: config.lockoutDuration || 300000, // 5 minutes
secretKey: config.secretKey || process.env.JWT_SECRET
};
this.loginAttempts = new Map(); // Track failed attempts
this.lockedAccounts = new Map(); // Track account lockouts
}
async authenticate(email, password) {
// Check if account is currently locked
if (this.isAccountLocked(email)) {
throw new Error('Account temporarily locked due to multiple failed attempts');
}
try {
// Validate input parameters
if (!this.isValidEmail(email) || !password) {
this.recordFailedAttempt(email);
throw new Error('Invalid email or password format');
}
// Attempt user authentication
const user = await this.validateCredentials(email, password);
if (!user) {
this.recordFailedAttempt(email);
throw new Error('Authentication failed: Invalid credentials');
}
// Generate access token for authenticated user
const token = this.generateToken(user);
this.clearFailedAttempts(email); // Reset failed attempts on success
return {
success: true,
token: token,
user: this.sanitizeUser(user),
expiresAt: new Date(Date.now() + this.config.tokenExpiry)
};
} catch (error) {
console.error('Authentication error:', error.message);
throw error;
}
}
}
```
**Reference specific lines in discussions:**
- Line 15: Account lockout check prevents brute force attacks
- Lines 25-28: Input validation with attempt tracking
- Lines 35-42: Token generation and success response
GitLab Markdown Extensions
GitLab provides enhanced code block features with line number support:
## Python Data Processing Pipeline
```python
# Advanced data processing pipeline with error handling and logging
import pandas as pd
import numpy as np
import logging
from typing import Dict, List, Optional, Tuple
from pathlib import Path
import json
class DataProcessor:
"""
Comprehensive data processing pipeline for CSV and JSON data sources.
Includes validation, transformation, and export capabilities.
"""
def __init__(self, config_path: str = "config.json"):
self.logger = self._setup_logging()
self.config = self._load_config(config_path)
self.processed_data: Optional[pd.DataFrame] = None
self.processing_stats: Dict = {}
def _setup_logging(self) -> logging.Logger:
"""Configure logging for data processing operations"""
logger = logging.getLogger('DataProcessor')
logger.setLevel(logging.INFO)
if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
def _load_config(self, config_path: str) -> Dict:
"""Load processing configuration from JSON file"""
try:
with open(config_path, 'r') as file:
config = json.load(file)
self.logger.info(f"Configuration loaded from {config_path}")
return config
except FileNotFoundError:
self.logger.warning(f"Config file not found: {config_path}, using defaults")
return self._get_default_config()
except json.JSONDecodeError as e:
self.logger.error(f"Invalid JSON in config file: {e}")
raise ValueError(f"Configuration file contains invalid JSON: {e}")
def process_csv_data(self, file_path: str, delimiter: str = ',') -> pd.DataFrame:
"""
Process CSV data with comprehensive validation and cleaning.
Args:
file_path: Path to CSV file
delimiter: CSV delimiter character
Returns:
Processed DataFrame with cleaned and validated data
"""
try:
# Load CSV data with error handling
self.logger.info(f"Loading CSV data from {file_path}")
raw_data = pd.read_csv(file_path, delimiter=delimiter)
# Record initial data statistics
initial_rows = len(raw_data)
self.processing_stats['initial_rows'] = initial_rows
self.logger.info(f"Loaded {initial_rows} rows from CSV")
# Data validation and cleaning pipeline
cleaned_data = self._validate_data_types(raw_data)
cleaned_data = self._handle_missing_values(cleaned_data)
cleaned_data = self._remove_duplicates(cleaned_data)
cleaned_data = self._apply_transformations(cleaned_data)
# Record final statistics
final_rows = len(cleaned_data)
self.processing_stats['final_rows'] = final_rows
self.processing_stats['rows_removed'] = initial_rows - final_rows
self.processing_stats['data_quality_score'] = self._calculate_quality_score(cleaned_data)
self.processed_data = cleaned_data
self.logger.info(f"Processing complete: {final_rows} clean rows")
return cleaned_data
except Exception as e:
self.logger.error(f"CSV processing failed: {e}")
raise RuntimeError(f"Failed to process CSV data: {e}")
```
**Line-specific documentation:**
- Lines 15-19: Constructor initializes core components and statistics tracking
- Lines 21-33: Logging setup with proper formatting and level configuration
- Lines 50-78: CSV processing pipeline with comprehensive error handling
- Lines 65-69: Data cleaning operations applied sequentially
Prism.js Line Number Plugin
Enable client-side line numbering with Prism.js:
<!-- Prism.js setup with line numbers -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Documentation with Line Numbers</title>
<!-- Prism.js core CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" />
<!-- Line numbers plugin CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.css" rel="stylesheet" />
<!-- Line highlight plugin CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-highlight/prism-line-highlight.min.css" rel="stylesheet" />
</head>
<body>
<!-- Code block with line numbers and highlighting -->
<pre class="line-numbers" data-line="5-8,12,15-17"><code class="language-javascript">
// React component with hooks and state management
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { debounce } from 'lodash';
// Custom hook for API data fetching with caching
function useAPIData(endpoint, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [cache, setCache] = useState(new Map());
// Memoized fetch function with caching logic
const fetchData = useCallback(async (url, fetchOptions) => {
const cacheKey = `${url}_${JSON.stringify(fetchOptions)}`;
// Check cache first to avoid unnecessary requests
if (cache.has(cacheKey)) {
const cachedData = cache.get(cacheKey);
if (Date.now() - cachedData.timestamp < 300000) { // 5 minute cache
setData(cachedData.data);
return cachedData.data;
}
}
setLoading(true);
setError(null);
try {
const response = await fetch(url, fetchOptions);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// Update cache with timestamp
setCache(prev => new Map(prev.set(cacheKey, {
data: result,
timestamp: Date.now()
})));
setData(result);
return result;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, [cache]);
// Debounced API call to prevent excessive requests
const debouncedFetch = useMemo(
() => debounce(fetchData, 300),
[fetchData]
);
// Effect for initial data loading
useEffect(() => {
if (endpoint) {
fetchData(endpoint, options);
}
}, [endpoint, fetchData, options]);
return {
data,
loading,
error,
refetch: () => fetchData(endpoint, options),
debouncedRefetch: () => debouncedFetch(endpoint, options)
};
}
</code></pre>
<!-- Prism.js core JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
<!-- Line numbers plugin -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
<!-- Line highlight plugin -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-highlight/prism-line-highlight.min.js"></script>
</body>
</html>
CSS-Based Line Number Solutions
Pure CSS Implementation
Create line numbers using CSS counters and pseudo-elements:
/* CSS-only line numbers for code blocks */
.code-with-lines {
position: relative;
background-color: #f8f8f8;
border: 1px solid #e1e1e1;
border-radius: 6px;
padding: 0;
margin: 1.5rem 0;
overflow-x: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
line-height: 1.4;
}
.code-with-lines pre {
margin: 0;
padding: 1rem 1rem 1rem 3.5rem;
counter-reset: line-counter;
white-space: pre;
overflow: visible;
}
.code-with-lines code {
display: block;
counter-increment: line-counter;
position: relative;
}
.code-with-lines code::before {
content: counter(line-counter);
position: absolute;
left: -3rem;
width: 2.5rem;
text-align: right;
color: #666;
font-size: 12px;
line-height: inherit;
padding-right: 0.5rem;
border-right: 1px solid #ddd;
background-color: #f0f0f0;
user-select: none;
}
/* Syntax highlighting integration */
.code-with-lines .token.comment {
color: #6a9955;
font-style: italic;
}
.code-with-lines .token.string {
color: #ce9178;
}
.code-with-lines .token.keyword {
color: #569cd6;
font-weight: bold;
}
.code-with-lines .token.function {
color: #dcdcaa;
}
.code-with-lines .token.number {
color: #b5cea8;
}
/* Dark theme support */
@media (prefers-color-scheme: dark) {
.code-with-lines {
background-color: #1e1e1e;
border-color: #404040;
color: #d4d4d4;
}
.code-with-lines code::before {
background-color: #2d2d2d;
color: #858585;
border-right-color: #404040;
}
.code-with-lines .token.comment {
color: #6a9955;
}
.code-with-lines .token.string {
color: #ce9178;
}
.code-with-lines .token.keyword {
color: #569cd6;
}
}
/* Responsive adjustments */
@media (max-width: 768px) {
.code-with-lines {
font-size: 12px;
}
.code-with-lines pre {
padding-left: 2.5rem;
}
.code-with-lines code::before {
left: -2.5rem;
width: 2rem;
font-size: 10px;
}
}
Advanced Styling with Highlighting
Create sophisticated line number displays with highlighting capabilities:
/* Advanced line numbers with highlighting and selection */
.advanced-code-block {
position: relative;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
padding: 2px;
margin: 2rem 0;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.code-container {
background-color: #1e1e1e;
border-radius: 8px;
position: relative;
overflow: hidden;
}
.code-header {
background: linear-gradient(90deg, #2d2d2d 0%, #404040 100%);
padding: 0.75rem 1rem;
border-bottom: 1px solid #505050;
display: flex;
align-items: center;
justify-content: space-between;
}
.code-title {
color: #ffffff;
font-weight: 600;
font-size: 0.9rem;
margin: 0;
}
.copy-button {
background: #007acc;
color: white;
border: none;
padding: 0.25rem 0.75rem;
border-radius: 4px;
font-size: 0.8rem;
cursor: pointer;
transition: background-color 0.2s;
}
.copy-button:hover {
background: #005a9e;
}
.line-numbers-wrapper {
display: flex;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
line-height: 1.5;
}
.line-numbers {
background-color: #2d2d2d;
color: #858585;
padding: 1rem 0;
min-width: 3rem;
text-align: right;
border-right: 1px solid #505050;
user-select: none;
counter-reset: line-counter;
}
.line-numbers .line-number {
display: block;
counter-increment: line-counter;
padding: 0 0.75rem 0 0.5rem;
position: relative;
}
.line-numbers .line-number::before {
content: counter(line-counter);
}
/* Highlighted line numbers */
.line-numbers .line-number.highlighted {
background-color: #3c4043;
color: #ffffff;
}
.line-numbers .line-number.highlighted::after {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: linear-gradient(45deg, #ff6b6b, #ffd93d);
}
.code-content {
flex: 1;
padding: 1rem;
color: #d4d4d4;
background-color: #1e1e1e;
overflow-x: auto;
white-space: pre;
}
.code-line {
display: block;
min-height: 1.5em;
position: relative;
}
.code-line.highlighted {
background-color: rgba(255, 255, 0, 0.1);
margin: 0 -1rem;
padding: 0 1rem;
}
/* Error line highlighting */
.code-line.error {
background-color: rgba(255, 107, 107, 0.2);
border-left: 3px solid #ff6b6b;
margin: 0 -1rem;
padding: 0 1rem;
padding-left: calc(1rem - 3px);
}
/* Warning line highlighting */
.code-line.warning {
background-color: rgba(255, 193, 7, 0.2);
border-left: 3px solid #ffc107;
margin: 0 -1rem;
padding: 0 1rem;
padding-left: calc(1rem - 3px);
}
/* Success/added line highlighting */
.code-line.added {
background-color: rgba(76, 175, 80, 0.2);
border-left: 3px solid #4caf50;
margin: 0 -1rem;
padding: 0 1rem;
padding-left: calc(1rem - 3px);
}
/* Deleted line highlighting */
.code-line.removed {
background-color: rgba(244, 67, 54, 0.2);
border-left: 3px solid #f44336;
margin: 0 -1rem;
padding: 0 1rem;
padding-left: calc(1rem - 3px);
text-decoration: line-through;
opacity: 0.7;
}
JavaScript-Enhanced Line Numbers
Dynamic Line Number Generation
Create interactive line numbers with JavaScript:
// Advanced line number system with interactive features
class CodeLineNumbers {
constructor(options = {}) {
this.options = {
startLine: options.startLine || 1,
highlightedLines: options.highlightedLines || [],
showCopyButton: options.showCopyButton !== false,
enableLineSelection: options.enableLineSelection !== false,
syntaxHighlighting: options.syntaxHighlighting !== false,
...options
};
this.selectedLines = new Set();
this.observers = new Map();
this.init();
}
init() {
// Find all code blocks and enhance them
const codeBlocks = document.querySelectorAll('pre code, .code-block code');
codeBlocks.forEach(codeBlock => this.enhanceCodeBlock(codeBlock));
// Set up global event listeners
this.setupGlobalEventHandlers();
}
enhanceCodeBlock(codeElement) {
const preElement = codeElement.closest('pre') || codeElement.parentElement;
// Skip if already enhanced
if (preElement.classList.contains('enhanced-code-block')) {
return;
}
// Create enhanced structure
const wrapper = this.createWrapper(codeElement);
const lineNumbers = this.createLineNumbers(codeElement);
const codeContent = this.createCodeContent(codeElement);
// Add copy functionality
if (this.options.showCopyButton) {
const copyButton = this.createCopyButton(codeElement);
wrapper.appendChild(copyButton);
}
// Replace original element
preElement.parentNode.replaceChild(wrapper, preElement);
// Set up line selection if enabled
if (this.options.enableLineSelection) {
this.setupLineSelection(wrapper, lineNumbers, codeContent);
}
// Apply syntax highlighting if enabled
if (this.options.syntaxHighlighting && window.Prism) {
Prism.highlightElement(codeContent.querySelector('code'));
}
// Set up intersection observer for lazy loading
this.setupIntersectionObserver(wrapper);
}
createWrapper(codeElement) {
const wrapper = document.createElement('div');
wrapper.className = 'enhanced-code-block';
wrapper.setAttribute('data-language', this.getLanguage(codeElement));
return wrapper;
}
createLineNumbers(codeElement) {
const lines = this.getCodeLines(codeElement);
const lineNumbersContainer = document.createElement('div');
lineNumbersContainer.className = 'line-numbers-container';
const lineNumbersList = document.createElement('div');
lineNumbersList.className = 'line-numbers-list';
lines.forEach((line, index) => {
const lineNumber = document.createElement('div');
const actualLineNumber = this.options.startLine + index;
lineNumber.className = 'line-number';
lineNumber.textContent = actualLineNumber;
lineNumber.setAttribute('data-line', actualLineNumber);
// Highlight specific lines
if (this.options.highlightedLines.includes(actualLineNumber)) {
lineNumber.classList.add('highlighted');
}
// Add click handler for line selection
if (this.options.enableLineSelection) {
lineNumber.addEventListener('click', (e) => {
this.toggleLineSelection(actualLineNumber, e.ctrlKey || e.metaKey);
});
}
lineNumbersList.appendChild(lineNumber);
});
lineNumbersContainer.appendChild(lineNumbersList);
return lineNumbersContainer;
}
createCodeContent(codeElement) {
const codeContainer = document.createElement('div');
codeContainer.className = 'code-content-container';
const codeContent = document.createElement('pre');
codeContent.className = 'code-content';
const newCodeElement = document.createElement('code');
newCodeElement.className = codeElement.className;
newCodeElement.textContent = codeElement.textContent;
codeContent.appendChild(newCodeElement);
codeContainer.appendChild(codeContent);
return codeContainer;
}
createCopyButton(codeElement) {
const copyButton = document.createElement('button');
copyButton.className = 'copy-code-button';
copyButton.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
<span>Copy</span>
`;
copyButton.addEventListener('click', async () => {
const selectedText = this.getSelectedLinesText() || codeElement.textContent;
try {
await navigator.clipboard.writeText(selectedText);
this.showCopyFeedback(copyButton, 'Copied!');
} catch (err) {
console.error('Failed to copy code:', err);
this.showCopyFeedback(copyButton, 'Error');
}
});
return copyButton;
}
getCodeLines(codeElement) {
return codeElement.textContent.split('\n').filter(line => line !== '' || line === '\n');
}
getLanguage(codeElement) {
const className = codeElement.className;
const languageMatch = className.match(/language-(\w+)/);
return languageMatch ? languageMatch[1] : 'text';
}
setupLineSelection(wrapper, lineNumbers, codeContent) {
const codeLines = codeContent.querySelectorAll('.code-line');
// Add line selection to both line numbers and code lines
[...lineNumbers.querySelectorAll('.line-number')].forEach((lineNumber, index) => {
const codeLine = codeLines[index];
[lineNumber, codeLine].forEach(element => {
if (element) {
element.addEventListener('click', (e) => {
const lineNum = parseInt(lineNumber.getAttribute('data-line'));
this.toggleLineSelection(lineNum, e.ctrlKey || e.metaKey);
});
}
});
});
}
toggleLineSelection(lineNumber, multiSelect = false) {
if (!multiSelect) {
this.selectedLines.clear();
// Clear all previously selected lines
document.querySelectorAll('.line-number.selected, .code-line.selected')
.forEach(el => el.classList.remove('selected'));
}
const lineElements = document.querySelectorAll(`[data-line="${lineNumber}"]`);
if (this.selectedLines.has(lineNumber)) {
this.selectedLines.delete(lineNumber);
lineElements.forEach(el => el.classList.remove('selected'));
} else {
this.selectedLines.add(lineNumber);
lineElements.forEach(el => el.classList.add('selected'));
}
// Update copy button state
this.updateCopyButtonState();
}
getSelectedLinesText() {
if (this.selectedLines.size === 0) return null;
const sortedLines = Array.from(this.selectedLines).sort((a, b) => a - b);
const codeContent = document.querySelector('.code-content code');
const lines = codeContent.textContent.split('\n');
return sortedLines
.map(lineNum => lines[lineNum - this.options.startLine])
.join('\n');
}
updateCopyButtonState() {
const copyButtons = document.querySelectorAll('.copy-code-button span');
copyButtons.forEach(span => {
span.textContent = this.selectedLines.size > 0
? `Copy ${this.selectedLines.size} lines`
: 'Copy';
});
}
showCopyFeedback(button, message) {
const span = button.querySelector('span');
const originalText = span.textContent;
span.textContent = message;
button.classList.add('copied');
setTimeout(() => {
span.textContent = originalText;
button.classList.remove('copied');
}, 2000);
}
setupIntersectionObserver(wrapper) {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
wrapper.classList.add('visible');
}
});
},
{ threshold: 0.1 }
);
observer.observe(wrapper);
this.observers.set(wrapper, observer);
}
setupGlobalEventHandlers() {
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + A to select all lines in focused code block
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
const focusedCodeBlock = document.activeElement.closest('.enhanced-code-block');
if (focusedCodeBlock) {
e.preventDefault();
this.selectAllLines(focusedCodeBlock);
}
}
// Escape to clear selection
if (e.key === 'Escape') {
this.clearSelection();
}
});
}
selectAllLines(codeBlock) {
const lineNumbers = codeBlock.querySelectorAll('.line-number');
lineNumbers.forEach(lineNumber => {
const lineNum = parseInt(lineNumber.getAttribute('data-line'));
this.selectedLines.add(lineNum);
lineNumber.classList.add('selected');
});
this.updateCopyButtonState();
}
clearSelection() {
this.selectedLines.clear();
document.querySelectorAll('.line-number.selected, .code-line.selected')
.forEach(el => el.classList.remove('selected'));
this.updateCopyButtonState();
}
destroy() {
// Clean up observers and event listeners
this.observers.forEach(observer => observer.disconnect());
this.observers.clear();
this.selectedLines.clear();
}
}
// Initialize enhanced code blocks
document.addEventListener('DOMContentLoaded', () => {
new CodeLineNumbers({
startLine: 1,
highlightedLines: [5, 8, 12, 15],
showCopyButton: true,
enableLineSelection: true,
syntaxHighlighting: true
});
});
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = CodeLineNumbers;
}
Hugo Shortcode Implementation
Create reusable shortcodes for consistent line-numbered code blocks:
<!-- layouts/shortcodes/code-with-lines.html -->
{{ $language := .Get "language" | default "text" }}
{{ $title := .Get "title" | default "" }}
{{ $startLine := .Get "start" | default 1 | int }}
{{ $highlightLines := .Get "highlight" | default "" }}
{{ $showCopy := .Get "copy" | default true }}
<div class="code-block-wrapper" data-language="{{ $language }}">
{{ if $title }}
<div class="code-block-header">
<h4 class="code-title">{{ $title }}</h4>
{{ if $showCopy }}
<button class="copy-button" onclick="copyCodeBlock(this)">
<span>Copy</span>
</button>
{{ end }}
</div>
{{ end }}
<div class="line-numbered-code"
data-start="{{ $startLine }}"
data-highlight="{{ $highlightLines }}">
<div class="line-numbers" aria-hidden="true"></div>
<pre class="code-content"><code class="language-{{ $language }}">{{ .Inner | htmlEscape }}</code></pre>
</div>
</div>
<script>
// Initialize line numbers for this code block
(function() {
const codeBlock = document.currentScript.previousElementSibling;
const lineNumberContainer = codeBlock.querySelector('.line-numbers');
const codeContent = codeBlock.querySelector('code');
const startLine = parseInt(codeBlock.querySelector('.line-numbered-code').dataset.start);
const highlightLines = codeBlock.querySelector('.line-numbered-code').dataset.highlight.split(',').map(n => parseInt(n.trim())).filter(n => !isNaN(n));
// Generate line numbers
const lines = codeContent.textContent.split('\n');
lines.forEach((line, index) => {
const lineNum = startLine + index;
const lineElement = document.createElement('div');
lineElement.className = 'line-number';
lineElement.textContent = lineNum;
if (highlightLines.includes(lineNum)) {
lineElement.classList.add('highlighted');
}
lineNumberContainer.appendChild(lineElement);
});
// Syntax highlighting
if (window.Prism) {
Prism.highlightElement(codeContent);
}
})();
function copyCodeBlock(button) {
const codeBlock = button.closest('.code-block-wrapper');
const codeContent = codeBlock.querySelector('code');
navigator.clipboard.writeText(codeContent.textContent).then(() => {
const span = button.querySelector('span');
const originalText = span.textContent;
span.textContent = 'Copied!';
setTimeout(() => span.textContent = originalText, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
});
}
</script>
Usage in Hugo content:
## Database Connection Example
{{< code-with-lines language="python" title="Database Manager" start="1" highlight="5,12,18-20" >}}
import psycopg2
from contextlib import contextmanager
import logging
class DatabaseManager:
def __init__(self, connection_string):
self.connection_string = connection_string
self.logger = logging.getLogger(__name__)
@contextmanager
def get_connection(self):
conn = None
try:
conn = psycopg2.connect(self.connection_string)
yield conn
except Exception as e:
if conn:
conn.rollback()
self.logger.error(f"Database error: {e}")
raise
finally:
if conn:
conn.close()
{{< /code-with-lines >}}
Jekyll Plugin Implementation
Create a Jekyll plugin for automatic line numbering:
# _plugins/line_numbers.rb
module Jekyll
class LineNumbersTag < Liquid::Block
def initialize(tag_name, markup, tokens)
super
@attributes = parse_attributes(markup)
end
def render(context)
site = context.registers[:site]
converter = site.find_converter_instance(Jekyll::Converters::Markdown)
# Get code content
code_content = super.strip
# Parse attributes
language = @attributes['language'] || 'text'
start_line = (@attributes['start'] || '1').to_i
title = @attributes['title']
highlight_lines = parse_highlight_lines(@attributes['highlight'])
# Generate HTML
generate_line_numbered_html(code_content, language, start_line, title, highlight_lines)
end
private
def parse_attributes(markup)
attributes = {}
markup.scan(/(\w+)=["']([^"']+)["']/) do |key, value|
attributes[key] = value
end
attributes
end
def parse_highlight_lines(highlight_string)
return [] unless highlight_string
lines = []
highlight_string.split(',').each do |part|
part = part.strip
if part.include?('-')
range_start, range_end = part.split('-').map(&:to_i)
lines.concat((range_start..range_end).to_a)
else
lines << part.to_i
end
end
lines
end
def generate_line_numbered_html(code_content, language, start_line, title, highlight_lines)
lines = code_content.split("\n")
html = ['<div class="jekyll-line-numbered-code">']
# Add title if provided
if title
html << "<div class=\"code-header\">"
html << "<h4 class=\"code-title\">#{title}</h4>"
html << "<button class=\"copy-button\" onclick=\"copyJekyllCode(this)\">Copy</button>"
html << "</div>"
end
html << '<div class="code-container">'
html << '<div class="line-numbers">'
# Generate line numbers
lines.each_with_index do |line, index|
line_num = start_line + index
css_classes = ['line-number']
css_classes << 'highlighted' if highlight_lines.include?(line_num)
html << "<div class=\"#{css_classes.join(' ')}\" data-line=\"#{line_num}\">#{line_num}</div>"
end
html << '</div>'
html << "<div class=\"code-content\">"
html << "<pre><code class=\"language-#{language}\">#{Jekyll::Utils.xml_escape(code_content)}</code></pre>"
html << "</div>"
html << '</div>'
html << '</div>'
# Add JavaScript for copy functionality
html << generate_copy_script
html.join("\n")
end
def generate_copy_script
<<~JAVASCRIPT
<script>
function copyJekyllCode(button) {
const codeBlock = button.closest('.jekyll-line-numbered-code');
const codeContent = codeBlock.querySelector('code');
navigator.clipboard.writeText(codeContent.textContent).then(() => {
button.textContent = 'Copied!';
setTimeout(() => button.textContent = 'Copy', 2000);
}).catch(err => {
console.error('Copy failed:', err);
button.textContent = 'Error';
setTimeout(() => button.textContent = 'Copy', 2000);
});
}
</script>
JAVASCRIPT
end
end
end
Liquid::Template.register_tag('line_numbers', Jekyll::LineNumbersTag)
Usage in Jekyll posts:
## Authentication System Implementation
{% line_numbers language="typescript" title="User Authentication Service" start="1" highlight="8,15-18,25" %}
interface AuthenticationConfig {
tokenExpiry: number;
maxAttempts: number;
lockoutDuration: number;
secretKey: string;
}
class AuthService {
private config: AuthenticationConfig;
private failedAttempts = new Map<string, number>();
private lockedAccounts = new Map<string, Date>();
constructor(config: AuthenticationConfig) {
this.config = config;
this.validateConfiguration();
}
async authenticate(email: string, password: string): Promise<AuthResult> {
// Check account lock status
if (this.isAccountLocked(email)) {
throw new Error('Account temporarily locked');
}
try {
// Validate credentials
const user = await this.validateUser(email, password);
if (!user) {
this.recordFailedAttempt(email);
throw new Error('Invalid credentials');
}
// Generate token and return success
const token = this.generateToken(user);
this.clearFailedAttempts(email);
return {
success: true,
token,
user: this.sanitizeUser(user)
};
} catch (error) {
this.logger.error('Authentication failed:', error);
throw error;
}
}
}
{% endline_numbers %}
Accessibility and Screen Reader Support
ARIA Labels and Semantic Markup
Ensure line numbers are accessible to all users:
<!-- Accessible line-numbered code block -->
<div class="accessible-code-block"
role="region"
aria-label="Code example with line numbers">
<div class="code-header" role="banner">
<h3 id="code-title-1">Python Data Validation Function</h3>
<button class="copy-button"
aria-label="Copy code to clipboard"
aria-describedby="code-title-1">
Copy
</button>
</div>
<div class="code-container" role="main">
<!-- Line numbers are decorative and hidden from screen readers -->
<div class="line-numbers" aria-hidden="true" role="presentation">
<div class="line-number">1</div>
<div class="line-number">2</div>
<div class="line-number">3</div>
<div class="line-number highlighted" aria-label="Line 4: Important line">4</div>
<div class="line-number">5</div>
</div>
<!-- Code content with proper labeling -->
<pre class="code-content"
role="code"
aria-labelledby="code-title-1"
tabindex="0"><code class="language-python">def validate_email(email: str) -> bool:
"""Validate email address format and domain"""
if not email or '@' not in email:
return False # Basic format check
username, domain = email.rsplit('@', 1)
return len(username) > 0 and '.' in domain</code></pre>
</div>
<!-- Screen reader instructions -->
<div class="sr-only">
<p>This code block shows a Python function for email validation.
Use arrow keys to navigate through the code.
Line 4 contains the main validation logic.</p>
</div>
</div>
CSS for Screen Reader Support
/* Screen reader and accessibility support */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles for keyboard navigation */
.accessible-code-block .code-content:focus {
outline: 2px solid #007acc;
outline-offset: 2px;
}
.accessible-code-block .copy-button:focus {
outline: 2px solid #007acc;
outline-offset: 2px;
background-color: #005a9e;
}
/* High contrast mode support */
@media (prefers-contrast: high) {
.line-numbers {
border-right: 2px solid;
}
.line-number.highlighted {
background-color: #ffff00;
color: #000000;
font-weight: bold;
}
.code-content {
border: 1px solid;
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
.copy-button,
.line-number {
transition: none;
}
}
/* Font size preferences */
@media (prefers-font-size: large) {
.accessible-code-block {
font-size: 1.2em;
}
}
Performance Optimization Techniques
Lazy Loading for Large Code Blocks
Implement efficient rendering for extensive code examples:
// Performance-optimized line number rendering
class OptimizedLineNumbers {
constructor(options = {}) {
this.options = {
virtualScrollThreshold: options.virtualScrollThreshold || 100,
renderBuffer: options.renderBuffer || 10,
debounceDelay: options.debounceDelay || 16,
...options
};
this.intersectionObserver = new IntersectionObserver(
this.handleIntersection.bind(this),
{ threshold: 0.1 }
);
this.resizeObserver = new ResizeObserver(
this.debounce(this.handleResize.bind(this), this.options.debounceDelay)
);
this.init();
}
init() {
// Process existing code blocks
document.querySelectorAll('pre code').forEach(codeElement => {
this.processCodeBlock(codeElement);
});
// Watch for dynamically added code blocks
this.setupMutationObserver();
}
processCodeBlock(codeElement) {
const lines = codeElement.textContent.split('\n');
// Use virtual scrolling for large code blocks
if (lines.length > this.options.virtualScrollThreshold) {
this.createVirtualScrollCodeBlock(codeElement, lines);
} else {
this.createStaticCodeBlock(codeElement, lines);
}
}
createVirtualScrollCodeBlock(codeElement, lines) {
const wrapper = document.createElement('div');
wrapper.className = 'virtual-scroll-code-block';
wrapper.style.cssText = `
height: 400px;
overflow: auto;
position: relative;
border: 1px solid #ddd;
border-radius: 4px;
`;
const viewport = document.createElement('div');
viewport.className = 'virtual-viewport';
viewport.style.cssText = `
height: ${lines.length * 20}px;
position: relative;
`;
const visibleArea = document.createElement('div');
visibleArea.className = 'visible-area';
visibleArea.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
display: flex;
`;
const lineNumbers = document.createElement('div');
lineNumbers.className = 'virtual-line-numbers';
lineNumbers.style.cssText = `
width: 60px;
background: #f5f5f5;
border-right: 1px solid #ddd;
font-family: monospace;
font-size: 12px;
text-align: right;
padding: 0 8px;
`;
const codeContent = document.createElement('pre');
codeContent.className = 'virtual-code-content';
codeContent.style.cssText = `
flex: 1;
margin: 0;
padding: 0 12px;
font-family: monospace;
font-size: 14px;
line-height: 20px;
background: white;
overflow: hidden;
`;
visibleArea.appendChild(lineNumbers);
visibleArea.appendChild(codeContent);
viewport.appendChild(visibleArea);
wrapper.appendChild(viewport);
// Virtual scrolling logic
let startIndex = 0;
let visibleLines = Math.ceil(400 / 20) + this.options.renderBuffer;
const updateVisible = () => {
const scrollTop = wrapper.scrollTop;
startIndex = Math.max(0, Math.floor(scrollTop / 20) - this.options.renderBuffer);
const endIndex = Math.min(lines.length, startIndex + visibleLines);
// Update line numbers
const visibleLineNumbers = [];
for (let i = startIndex; i < endIndex; i++) {
visibleLineNumbers.push(`<div style="height: 20px; line-height: 20px;">${i + 1}</div>`);
}
lineNumbers.innerHTML = visibleLineNumbers.join('');
// Update code content
const visibleCode = lines.slice(startIndex, endIndex);
const codeHTML = visibleCode.map((line, index) =>
`<div style="height: 20px; line-height: 20px;" data-line="${startIndex + index + 1}">${this.escapeHtml(line)}</div>`
).join('');
codeContent.innerHTML = `<code>${codeHTML}</code>`;
// Position the visible area
visibleArea.style.transform = `translateY(${startIndex * 20}px)`;
};
// Throttled scroll handler
wrapper.addEventListener('scroll', this.throttle(updateVisible, 16));
// Initial render
updateVisible();
// Replace original code block
codeElement.closest('pre').parentNode.replaceChild(wrapper, codeElement.closest('pre'));
}
createStaticCodeBlock(codeElement, lines) {
// Standard static line numbers for smaller code blocks
const wrapper = document.createElement('div');
wrapper.className = 'static-line-numbered-code';
const container = document.createElement('div');
container.className = 'line-numbered-container';
container.style.display = 'flex';
const lineNumbers = document.createElement('div');
lineNumbers.className = 'static-line-numbers';
lineNumbers.style.cssText = `
background: #f8f8f8;
border-right: 1px solid #ddd;
padding: 1rem 0.5rem;
text-align: right;
user-select: none;
font-family: monospace;
font-size: 12px;
line-height: 1.4;
color: #666;
min-width: 2.5rem;
`;
// Generate line numbers efficiently
const lineNumberHTML = lines.map((_, index) =>
`<div class="line-num">${index + 1}</div>`
).join('');
lineNumbers.innerHTML = lineNumberHTML;
const codeContainer = document.createElement('div');
codeContainer.className = 'static-code-content';
codeContainer.style.cssText = `
flex: 1;
overflow-x: auto;
`;
const pre = document.createElement('pre');
pre.style.cssText = `
margin: 0;
padding: 1rem;
background: white;
font-family: monospace;
font-size: 14px;
line-height: 1.4;
`;
const code = document.createElement('code');
code.className = codeElement.className;
code.textContent = codeElement.textContent;
pre.appendChild(code);
codeContainer.appendChild(pre);
container.appendChild(lineNumbers);
container.appendChild(codeContainer);
wrapper.appendChild(container);
// Replace original
codeElement.closest('pre').parentNode.replaceChild(wrapper, codeElement.closest('pre'));
// Apply syntax highlighting if available
if (window.Prism && code.className.includes('language-')) {
Prism.highlightElement(code);
}
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const codeBlock = entry.target;
// Lazy load syntax highlighting or other features
this.enhanceVisibleCodeBlock(codeBlock);
}
});
}
enhanceVisibleCodeBlock(codeBlock) {
// Add syntax highlighting, copy buttons, etc. only when visible
const codeElement = codeBlock.querySelector('code');
if (codeElement && window.Prism && !codeElement.classList.contains('highlighted')) {
Prism.highlightElement(codeElement);
codeElement.classList.add('highlighted');
}
}
setupMutationObserver() {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
const codeElements = node.querySelectorAll('pre code');
codeElements.forEach(codeElement => {
this.processCodeBlock(codeElement);
});
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// Utility functions
debounce(func, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
throttle(func, delay) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= delay) {
func.apply(this, args);
lastCall = now;
}
};
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
handleResize() {
// Recalculate virtual scroll parameters on resize
this.updateVirtualScrollDimensions();
}
updateVirtualScrollDimensions() {
const virtualBlocks = document.querySelectorAll('.virtual-scroll-code-block');
virtualBlocks.forEach(block => {
// Recalculate visible area and update rendering
const event = new Event('scroll');
block.dispatchEvent(event);
});
}
destroy() {
// Cleanup observers
if (this.intersectionObserver) {
this.intersectionObserver.disconnect();
}
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
}
}
// Initialize optimized line numbers
document.addEventListener('DOMContentLoaded', () => {
window.optimizedLineNumbers = new OptimizedLineNumbers({
virtualScrollThreshold: 50,
renderBuffer: 5,
debounceDelay: 16
});
});
Integration with Documentation Workflows
Line numbers in code blocks work seamlessly with other advanced Markdown features to create comprehensive technical documentation. When combined with syntax highlighting, line numbers enable precise code discussions while maintaining beautiful visual presentation of different programming languages.
For complex documentation requiring both detailed code examples and structured explanations, line numbers integrate effectively with custom containers and admonitions to create comprehensive tutorials with clear visual organization and specific line references.
When creating interactive documentation that includes both code examples and user tasks, line numbering complements task lists and checkboxes by enabling readers to reference specific implementation steps while tracking their progress through development workflows.
Troubleshooting Common Issues
Line Number Alignment Problems
Problem: Line numbers don’t align properly with code content
Solutions:
/* Fix alignment issues */
.line-numbers, .code-content {
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
font-size: 14px;
line-height: 1.5;
font-feature-settings: normal;
font-variant-ligatures: none;
}
/* Ensure consistent spacing */
.line-numbers .line-number,
.code-content .code-line {
height: 1.5em;
box-sizing: border-box;
}
/* Fix sub-pixel rendering issues */
.line-numbered-code {
transform: translateZ(0); /* Force hardware acceleration */
}
Performance Issues with Large Files
Problem: Browser becomes sluggish with very long code files
Solutions:
// Implement efficient rendering strategies
const PERFORMANCE_THRESHOLDS = {
SMALL: 100, // Lines - full rendering
MEDIUM: 500, // Lines - chunked rendering
LARGE: 1000 // Lines - virtual scrolling
};
function optimizeCodeBlock(codeElement) {
const lineCount = codeElement.textContent.split('\n').length;
if (lineCount < PERFORMANCE_THRESHOLDS.SMALL) {
// Full static rendering
renderStaticLineNumbers(codeElement);
} else if (lineCount < PERFORMANCE_THRESHOLDS.MEDIUM) {
// Chunked rendering with intersection observer
renderChunkedLineNumbers(codeElement);
} else {
// Virtual scrolling for maximum performance
renderVirtualScrollLineNumbers(codeElement);
}
}
Cross-Browser Compatibility
Problem: Line numbers display differently across browsers
Solutions:
/* Cross-browser consistent line numbers */
.universal-line-numbers {
/* Reset browser defaults */
box-sizing: border-box;
-webkit-font-feature-settings: "liga" 0;
font-feature-settings: "liga" 0;
/* Consistent fonts across platforms */
font-family:
'SFMono-Regular',
'Monaco',
'Inconsolata',
'Roboto Mono',
'Source Code Pro',
'Menlo',
'Consolas',
'DejaVu Sans Mono',
monospace;
/* Disable text selection and interactions */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
/* Consistent text rendering */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
/* IE11 fallbacks */
@media screen and (-ms-high-contrast: active),
screen and (-ms-high-contrast: none) {
.universal-line-numbers {
font-family: 'Consolas', monospace;
}
}
SEO and Content Structure Benefits
Enhanced Code Documentation SEO
Line numbers significantly improve technical content discoverability:
- Precise Referencing: Enable specific line citations that improve content authority
- Enhanced Navigation: Help search engines understand code structure and importance
- Professional Presentation: Demonstrate attention to detail that builds content credibility
- User Engagement: Longer time-on-page as users can easily navigate complex code examples
Structured Data for Code Examples
<!-- Enhanced markup for code blocks with line numbers -->
<div itemscope itemtype="https://schema.org/SoftwareSourceCode">
<div class="code-header">
<h3 itemprop="name">User Authentication Implementation</h3>
<meta itemprop="programmingLanguage" content="TypeScript">
<meta itemprop="codeRepository" content="https://github.com/example/auth-system">
</div>
<div class="line-numbered-code" itemprop="text">
<div class="line-numbers" role="presentation" aria-hidden="true">
<!-- Line numbers -->
</div>
<pre><code class="language-typescript">
<!-- Code content with proper indentation -->
</code></pre>
</div>
<div itemprop="description">
Complete authentication system with error handling and token management.
Key implementation details on lines 15-28 and 35-42.
</div>
</div>
Conclusion
Markdown line numbers transform code documentation from simple text blocks into professional, navigable technical resources that enhance both reader comprehension and collaborative development workflows. By mastering platform-specific implementations, CSS styling techniques, and JavaScript enhancements, you can create code documentation that not only presents information clearly but actively improves the developer experience.
The key to successful line number implementation lies in choosing appropriate techniques for your content complexity, ensuring accessibility across all users and devices, and optimizing performance for varying code block sizes. Whether you’re creating API documentation, tutorial content, or technical specifications, the techniques covered in this guide provide the foundation for professional code presentation that serves developers effectively.
Remember to test line number implementations across different platforms and devices, consider performance implications for large code examples, and always prioritize accessibility in your styling choices. With proper implementation, line numbers become powerful tools for creating precise, professional technical documentation that enhances code comprehension and facilitates effective technical communication.