Skip to main content

Overview

MarkdownTheme provides comprehensive control over the visual appearance of rendered markdown, including fonts, colors, spacing, and component-specific styling. All theme properties use UIKit/AppKit native types for seamless integration.

Basic Theme Customization

Start with the default theme and customize specific properties:
var theme = MarkdownTheme()

// Customize fonts
theme.fonts.body = .preferredFont(forTextStyle: .body)
theme.fonts.code = .monospacedSystemFont(ofSize: 14, weight: .regular)

// Customize colors
theme.colors.body = .label
theme.colors.code = .secondaryLabel
theme.colors.codeBackground = .secondarySystemBackground

// Scale all fonts
theme.scaleFont(by: .large)

markdownView.theme = theme

Theme Structure

MarkdownTheme is organized into five main categories:

Fonts

Control typography for body text, code, headings, and emphasis

Colors

Define colors for text, code, highlights, and selections

Spacings

Adjust padding and margins between elements

Sizes

Configure UI element dimensions like bullets

Component Styles

Customize tables, images, and other components

Fonts Configuration

Available Font Properties

public struct Fonts {
    public var body: UIFont          // Main text font
    public var codeInline: UIFont    // Inline code (`code`)
    public var bold: UIFont          // Bold text
    public var italic: UIFont        // Italic text
    public var code: UIFont          // Code blocks
    public var largeTitle: UIFont    // H1 headings
    public var title: UIFont         // H2-H6 headings
    public var footnote: UIFont      // Small text
}

Custom Font Example

var theme = MarkdownTheme()

// Use custom fonts
theme.fonts.body = UIFont(name: "Georgia", size: 17)!
theme.fonts.bold = UIFont(name: "Georgia-Bold", size: 17)!
theme.fonts.italic = UIFont(name: "Georgia-Italic", size: 17)!

// Use SF Mono for code
if let sfMono = UIFont(name: "SFMono-Regular", size: 14) {
    theme.fonts.code = sfMono
    theme.fonts.codeInline = sfMono
}

// Larger headings
theme.fonts.largeTitle = UIFont.systemFont(ofSize: 32, weight: .bold)
theme.fonts.title = UIFont.systemFont(ofSize: 24, weight: .bold)

Font Scaling

Use built-in scaling to adjust all fonts proportionally:
var theme = MarkdownTheme()

// Scale by preset size
theme.scaleFont(by: .tiny)   // -4 points
theme.scaleFont(by: .small)  // -2 points
theme.scaleFont(by: .middle) //  0 points (default)
theme.scaleFont(by: .large)  // +2 points
theme.scaleFont(by: .huge)   // +4 points

Align to Specific Point Size

Set all fonts to scale from a base point size:
var theme = MarkdownTheme()

// All fonts scale from 16pt base
theme.align(to: 16)

// Code blocks automatically scale to 85% of base
// code font will be ~13.6pt
The codeScale constant is set to 0.85, meaning code fonts are always 85% the size of body text for better readability.

Colors Configuration

Available Color Properties

public struct Colors {
    public var body: UIColor               // Main text color
    public var highlight: UIColor          // Links and highlighted text
    public var emphasis: UIColor           // Emphasized text
    public var code: UIColor               // Code text color
    public var codeBackground: UIColor     // Code block background
    public var selectionBackground: UIColor? // Text selection color
}

Dark Mode Support

Colors automatically adapt to light and dark mode when using dynamic colors:
var theme = MarkdownTheme()

// Semantic colors adapt automatically
theme.colors.body = .label
theme.colors.code = .secondaryLabel
theme.colors.codeBackground = .secondarySystemBackground

// Custom dynamic colors
theme.colors.highlight = UIColor { traitCollection in
    traitCollection.userInterfaceStyle == .dark 
        ? UIColor(red: 0.4, green: 0.7, blue: 1.0, alpha: 1.0)
        : UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
}

Custom Color Schemes

var theme = MarkdownTheme()

// Nord color palette
theme.colors.body = UIColor(red: 0.216, green: 0.255, blue: 0.318, alpha: 1.0)
theme.colors.code = UIColor(red: 0.141, green: 0.165, blue: 0.208, alpha: 1.0)
theme.colors.codeBackground = UIColor(red: 0.925, green: 0.937, blue: 0.957, alpha: 1.0)
theme.colors.highlight = UIColor(red: 0.357, green: 0.608, blue: 0.835, alpha: 1.0)
theme.colors.emphasis = UIColor(red: 0.741, green: 0.380, blue: 0.416, alpha: 1.0)

Spacing Configuration

Available Spacing Properties

public struct Spacings {
    public var final: CGFloat = 16    // Bottom margin of last element
    public var general: CGFloat = 8   // General spacing between blocks
    public var list: CGFloat = 8      // Spacing between list items
    public var cell: CGFloat = 32     // Width of list item indent
}

Compact vs Spacious Layouts

// Compact layout
var compactTheme = MarkdownTheme()
compactTheme.spacings.general = 4
compactTheme.spacings.list = 4
compactTheme.spacings.final = 8

// Spacious layout
var spaciousTheme = MarkdownTheme()
spaciousTheme.spacings.general = 16
spaciousTheme.spacings.list = 12
spaciousTheme.spacings.final = 24
spaciousTheme.spacings.cell = 40

Size Configuration

Available Size Properties

public struct Sizes {
    public var bullet: CGFloat = 4  // Bullet point diameter
}

Customizing Bullet Size

var theme = MarkdownTheme()

// Larger bullets for readability
theme.sizes.bullet = 6

// Smaller, subtle bullets
theme.sizes.bullet = 3

Table Styling

Available Table Properties

public struct Table {
    public var cornerRadius: CGFloat = 8
    public var borderWidth: CGFloat = 1
    public var borderColor: UIColor = .separator
    public var headerBackgroundColor: UIColor = .systemGray6
    public var cellBackgroundColor: UIColor = .clear
    public var stripeCellBackgroundColor: UIColor = .systemGray.withAlphaComponent(0.03)
}

Custom Table Styling

var theme = MarkdownTheme()

// Modern rounded tables
theme.table.cornerRadius = 12
theme.table.borderWidth = 2
theme.table.borderColor = .systemBlue
theme.table.headerBackgroundColor = .systemBlue.withAlphaComponent(0.1)
theme.table.stripeCellBackgroundColor = .systemGray.withAlphaComponent(0.05)

// Borderless tables
theme.table.borderWidth = 0
theme.table.cornerRadius = 0
theme.table.cellBackgroundColor = .clear

Image Styling

Available Image Properties

public struct Image {
    public var cornerRadius: CGFloat = 4
    public var maxWidthFraction: CGFloat = 1.0
    public var placeholderColor: UIColor = .systemGray5
}

Custom Image Styling

var theme = MarkdownTheme()

// Rounded corners
theme.image.cornerRadius = 12

// Limit image width to 80% of container
theme.image.maxWidthFraction = 0.8

// Custom placeholder color
theme.image.placeholderColor = .systemGray4

Complete Theme Examples

Reader Mode Theme

func createReaderTheme() -> MarkdownTheme {
    var theme = MarkdownTheme()
    
    // Comfortable reading fonts
    theme.fonts.body = UIFont(name: "Georgia", size: 18) ?? .systemFont(ofSize: 18)
    theme.fonts.bold = theme.fonts.body.bold
    theme.fonts.italic = theme.fonts.body.italic
    theme.fonts.largeTitle = UIFont(name: "Georgia-Bold", size: 36) ?? .boldSystemFont(ofSize: 36)
    theme.fonts.title = UIFont(name: "Georgia-Bold", size: 28) ?? .boldSystemFont(ofSize: 28)
    
    // Sepia colors
    theme.colors.body = UIColor(red: 0.2, green: 0.18, blue: 0.15, alpha: 1.0)
    theme.colors.highlight = UIColor(red: 0.65, green: 0.45, blue: 0.2, alpha: 1.0)
    theme.colors.codeBackground = UIColor(red: 0.96, green: 0.95, blue: 0.92, alpha: 1.0)
    
    // Generous spacing
    theme.spacings.general = 16
    theme.spacings.list = 12
    theme.spacings.final = 32
    
    return theme
}

Code Documentation Theme

func createCodeDocsTheme() -> MarkdownTheme {
    var theme = MarkdownTheme()
    
    // Technical fonts
    theme.fonts.body = .systemFont(ofSize: 15)
    theme.fonts.code = .monospacedSystemFont(ofSize: 13, weight: .medium)
    theme.fonts.codeInline = .monospacedSystemFont(ofSize: 14, weight: .regular)
    
    // Developer-friendly colors
    theme.colors.codeBackground = UIColor(red: 0.95, green: 0.97, blue: 0.98, alpha: 1.0)
    theme.colors.code = UIColor(red: 0.2, green: 0.3, blue: 0.4, alpha: 1.0)
    theme.colors.highlight = .systemBlue
    
    // Compact spacing for dense content
    theme.spacings.general = 6
    theme.spacings.list = 6
    
    return theme
}

Accessing Default Values

You can access default values for each theme category:
let defaultFonts = MarkdownTheme.defaultValueFont
let defaultColors = MarkdownTheme.defaultValueColor
let defaultSpacings = MarkdownTheme.defaultValueSpacing
let defaultSizes = MarkdownTheme.defaultValueSize
let defaultTable = MarkdownTheme.defaultValueTable

// Use default as starting point
var theme = MarkdownTheme()
theme.fonts = defaultFonts
theme.fonts.body = .preferredFont(forTextStyle: .body)

Dynamic Type Support

Use Dynamic Type for accessibility:
var theme = MarkdownTheme()

// Preferred fonts respect user's text size settings
theme.fonts.body = .preferredFont(forTextStyle: .body)
theme.fonts.title = .preferredFont(forTextStyle: .title1)
theme.fonts.largeTitle = .preferredFont(forTextStyle: .largeTitle)
theme.fonts.footnote = .preferredFont(forTextStyle: .footnote)

// Code can use preferred monospace
if #available(iOS 13.0, *) {
    theme.fonts.code = .monospacedSystemFont(
        ofSize: UIFont.preferredFont(forTextStyle: .body).pointSize * 0.85,
        weight: .regular
    )
}
When using Dynamic Type, remember to observe UIContentSizeCategory changes and update your theme accordingly for the best accessibility experience.

Platform Differences

MarkdownTheme uses #if canImport(UIKit) and #elseif canImport(AppKit) for cross-platform compatibility:
  • iOS/visionOS: Uses UIFont and UIColor
  • macOS: Uses NSFont and NSColor
The API remains identical across platforms, just use the appropriate platform types:
#if canImport(UIKit)
import UIKit
var theme = MarkdownTheme()
theme.colors.body = UIColor.label
#elseif canImport(AppKit)
import AppKit
var theme = MarkdownTheme()
theme.colors.body = NSColor.labelColor
#endif

Build docs developers (and LLMs) love