Skip to main content
MarkdownView provides a comprehensive theming system that goes beyond basic color and font customization. This guide covers advanced theming techniques including creating theme presets, font scaling, and fine-grained control over every visual element.

Theme Architecture

The MarkdownTheme struct is the central configuration object for all visual styling:
public struct MarkdownTheme: Equatable {
    public var fonts: Fonts = .init()
    public var colors: Colors = .init()
    public var spacings: Spacings = .init()
    public var sizes: Sizes = .init()
    public var table: Table = .init()
    public var image: Image = .init()
    
    public init() {}
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:22
All theme properties are platform-adaptive, automatically using UIKit types on iOS/visionOS and AppKit types on macOS through the PlatformFont and PlatformColor type aliases.

Theme Properties

Fonts

The Fonts struct controls typography throughout the markdown document:
public struct Fonts: Equatable {
    public var body: PlatformFont
    public var codeInline: PlatformFont
    public var bold: PlatformFont
    public var italic: PlatformFont
    public var code: PlatformFont
    public var largeTitle: PlatformFont
    public var title: PlatformFont
    public var footnote: PlatformFont
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:23-54 Default values:
  • body: System body font
  • codeInline: Monospaced at body size
  • code: Monospaced at 85% of body size
  • bold: Bold variant of body font
  • italic: Italic variant of body font
  • largeTitle, title: Bold body font (scaled by heading level)
  • footnote: System footnote/small font

Colors

The Colors struct defines the color palette:
public struct Colors: Equatable {
    public var body: PlatformColor
    public var highlight: PlatformColor
    public var emphasis: PlatformColor
    public var code: PlatformColor
    public var codeBackground: PlatformColor
    public var selectionBackground: PlatformColor?
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:59-93 Default values:
  • body: System label color (adapts to light/dark mode)
  • highlight: Accent color or orange fallback
  • emphasis: Same as highlight
  • code: System label color
  • codeBackground: Gray with 25% alpha
  • selectionBackground: Accent color with 20% alpha

Spacings

Control margins and padding between elements:
public struct Spacings: Equatable {
    public var final: CGFloat = 16
    public var general: CGFloat = 8
    public var list: CGFloat = 8
    public var cell: CGFloat = 32
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:97-102
  • final: Spacing after the last block
  • general: Default spacing between blocks
  • list: Spacing between list items
  • cell: Minimum width for table cells

Sizes

Control the size of decorative elements:
public struct Sizes: Equatable {
    public var bullet: CGFloat = 4
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:106-108

Table Styling

Comprehensive table appearance configuration:
public struct Table: Equatable {
    public var cornerRadius: CGFloat = 8
    public var borderWidth: CGFloat = 1
    public var borderColor: PlatformColor
    public var headerBackgroundColor: PlatformColor
    public var cellBackgroundColor: PlatformColor
    public var stripeCellBackgroundColor: PlatformColor
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:112-126

Image Rendering

Control how inline images are displayed:
public struct Image: Equatable {
    public var cornerRadius: CGFloat = 4
    public var maxWidthFraction: CGFloat = 1.0
    public var placeholderColor: PlatformColor
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:130-138
  • cornerRadius: Rounded corners on images
  • maxWidthFraction: Maximum width as a fraction of available width (1.0 = 100%)
  • placeholderColor: Color shown while images load

Font Scaling System

MarkdownView provides a built-in font scaling system through the FontScale enum:
public enum FontScale: String, CaseIterable {
    case tiny
    case small
    case middle
    case large
    case huge
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:168-174

Scale Offsets

Each scale level applies a point size offset:
var offset: Int {
    switch self {
    case .tiny: -4
    case .small: -2
    case .middle: 0
    case .large: 2
    case .huge: 4
    }
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:178-186

The scaleFont(by:) Method

Apply a font scale to all theme fonts:
mutating func scaleFont(by scale: FontScale)
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:195
var theme = MarkdownTheme.default
theme.scaleFont(by: .large)  // All fonts increase by 2pt
markdownView.theme = theme
This method scales all font properties (body, code, bold, italic, etc.) by the specified offset.
The scaleFont(by:) method resets fonts to their default values before applying the scale, ensuring consistent results.

The align(to:) Method

Align all theme fonts to a specific point size:
mutating func align(to pointSize: CGFloat)
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:206
var theme = MarkdownTheme.default
theme.align(to: 18.0)  // All fonts use 18pt as base size
markdownView.theme = theme
This method:
  • Sets body font to the specified point size
  • Maintains font characteristics (bold, italic, monospace)
  • Scales code fonts to 85% of the base size
  • Preserves relative proportions between font types
Use align(to:) when you need precise control over absolute font sizes, and scaleFont(by:) when you want to adjust fonts relative to system defaults.

Creating Theme Presets

Basic Custom Theme

extension MarkdownTheme {
    static var oceanic: MarkdownTheme {
        var theme = MarkdownTheme()
        
        // Fonts
        theme.fonts.body = .systemFont(ofSize: 16)
        theme.fonts.code = .monospacedSystemFont(ofSize: 14, weight: .regular)
        
        // Colors
        theme.colors.body = UIColor(red: 0.9, green: 0.9, blue: 0.95, alpha: 1.0)
        theme.colors.highlight = UIColor(red: 0.2, green: 0.7, blue: 0.9, alpha: 1.0)
        theme.colors.code = UIColor(red: 0.95, green: 0.95, blue: 0.7, alpha: 1.0)
        theme.colors.codeBackground = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 1.0)
        
        return theme
    }
}

// Usage
markdownView.theme = .oceanic

Dark Mode Adaptive Theme

extension MarkdownTheme {
    static var adaptiveProfessional: MarkdownTheme {
        var theme = MarkdownTheme()
        
        // Dynamic colors that adapt to appearance
        theme.colors.body = UIColor { traitCollection in
            traitCollection.userInterfaceStyle == .dark 
                ? UIColor(white: 0.95, alpha: 1.0)
                : UIColor(white: 0.1, alpha: 1.0)
        }
        
        theme.colors.codeBackground = UIColor { traitCollection in
            traitCollection.userInterfaceStyle == .dark
                ? UIColor(red: 0.15, green: 0.15, blue: 0.17, alpha: 1.0)
                : UIColor(red: 0.96, green: 0.96, blue: 0.97, alpha: 1.0)
        }
        
        return theme
    }
}

High Contrast Theme

extension MarkdownTheme {
    static var highContrast: MarkdownTheme {
        var theme = MarkdownTheme()
        
        // Increase font sizes
        theme.align(to: 20.0)
        
        // High contrast colors
        theme.colors.body = .black
        theme.colors.code = .black
        theme.colors.codeBackground = .white
        theme.colors.highlight = UIColor(red: 0.0, green: 0.3, blue: 0.8, alpha: 1.0)
        
        // Increased spacing
        theme.spacings.general = 16
        theme.spacings.list = 12
        
        // More prominent table borders
        theme.table.borderWidth = 2
        theme.table.borderColor = .black
        
        return theme
    }
}

Minimalist Theme

extension MarkdownTheme {
    static var minimalist: MarkdownTheme {
        var theme = MarkdownTheme()
        
        // Serif fonts for a classic look
        let serifFont = UIFont(name: "Georgia", size: 17) ?? .systemFont(ofSize: 17)
        theme.fonts.body = serifFont
        theme.fonts.bold = UIFont(name: "Georgia-Bold", size: 17) ?? serifFont
        theme.fonts.italic = UIFont(name: "Georgia-Italic", size: 17) ?? serifFont
        
        // Subtle colors
        theme.colors.body = UIColor(white: 0.2, alpha: 1.0)
        theme.colors.highlight = UIColor(white: 0.4, alpha: 1.0)
        theme.colors.codeBackground = UIColor(white: 0.95, alpha: 1.0)
        
        // Clean table style
        theme.table.cornerRadius = 0
        theme.table.borderColor = UIColor(white: 0.8, alpha: 1.0)
        theme.table.stripeCellBackgroundColor = .clear
        
        // Sharp image corners
        theme.image.cornerRadius = 0
        
        return theme
    }
}

Advanced Customization

Dynamic Theme Switching

class DocumentViewController: UIViewController {
    let markdownView = MarkdownTextView()
    
    enum ThemeStyle {
        case light, dark, sepia, highContrast
    }
    
    func applyTheme(_ style: ThemeStyle) {
        let theme: MarkdownTheme
        
        switch style {
        case .light:
            theme = .default
        case .dark:
            theme = .dark
        case .sepia:
            theme = .sepia
        case .highContrast:
            theme = .highContrast
        }
        
        UIView.animate(withDuration: 0.3) {
            self.markdownView.theme = theme
        }
    }
}

User-Adjustable Font Size

class ReadingViewController: UIViewController {
    let markdownView = MarkdownTextView()
    var currentScale: MarkdownTheme.FontScale = .middle
    
    @objc func increaseFontSize() {
        switch currentScale {
        case .tiny: currentScale = .small
        case .small: currentScale = .middle
        case .middle: currentScale = .large
        case .large: currentScale = .huge
        case .huge: return
        }
        updateTheme()
    }
    
    @objc func decreaseFontSize() {
        switch currentScale {
        case .tiny: return
        case .small: currentScale = .tiny
        case .middle: currentScale = .small
        case .large: currentScale = .middle
        case .huge: currentScale = .large
        }
        updateTheme()
    }
    
    private func updateTheme() {
        var theme = MarkdownTheme.default
        theme.scaleFont(by: currentScale)
        markdownView.theme = theme
    }
}

Accessibility-Aware Themes

extension MarkdownTheme {
    static func accessible(preferredSize: UIFont.TextStyle = .body) -> MarkdownTheme {
        var theme = MarkdownTheme()
        
        // Use Dynamic Type
        theme.fonts.body = .preferredFont(forTextStyle: preferredSize)
        theme.fonts.code = UIFont.monospacedSystemFont(
            ofSize: UIFont.preferredFont(forTextStyle: preferredSize).pointSize,
            weight: .regular
        )
        
        // High contrast colors
        theme.colors.body = .label
        theme.colors.code = .label
        theme.colors.codeBackground = .secondarySystemBackground
        
        // Increased spacing for better readability
        theme.spacings.general = 12
        theme.spacings.list = 10
        
        return theme
    }
}

// Respond to Dynamic Type changes
NotificationCenter.default.addObserver(
    forName: UIContentSizeCategory.didChangeNotification,
    object: nil,
    queue: .main
) { _ in
    markdownView.theme = .accessible()
}

Platform-Specific Customization

Use conditional compilation for platform-specific themes:
extension MarkdownTheme {
    static var platformOptimized: MarkdownTheme {
        var theme = MarkdownTheme()
        
        #if os(iOS) || os(visionOS)
        // Touch-optimized spacing on iOS
        theme.spacings.general = 12
        theme.spacings.list = 10
        theme.table.cornerRadius = 10
        
        #elseif os(macOS)
        // Denser layout for desktop
        theme.spacings.general = 8
        theme.spacings.list = 6
        theme.table.cornerRadius = 6
        #endif
        
        return theme
    }
}

Code Scale Constant

The default code font scaling factor is available as a constant:
public static let codeScale = 0.85
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:19 Code fonts are typically rendered at 85% of the body font size for better visual hierarchy.

Theme Equality

MarkdownTheme conforms to Equatable, allowing you to compare themes:
let theme1 = MarkdownTheme.default
let theme2 = MarkdownTheme.default

if theme1 == theme2 {
    print("Themes are identical")
}
This is useful for optimizing rendering when themes haven’t changed.

Best Practices

1. Create Reusable Presets

Define theme presets as static properties on MarkdownTheme extension:
extension MarkdownTheme {
    static var myCustomTheme: MarkdownTheme { ... }
}

2. Use Dynamic Colors

Always use dynamic colors that adapt to light/dark mode for better user experience:
UIColor { traitCollection in
    traitCollection.userInterfaceStyle == .dark ? darkColor : lightColor
}

3. Test Across Platforms

Verify your custom themes work correctly on all target platforms (iOS, macOS, visionOS).

4. Respect Accessibility Settings

When possible, use Dynamic Type and respect user preferences for text size and contrast.

5. Cache Theme Objects

Theme creation can involve font lookups and color creation. Cache reusable theme objects:
class ThemeManager {
    static let shared = ThemeManager()
    
    private(set) lazy var light = MarkdownTheme.light
    private(set) lazy var dark = MarkdownTheme.dark
}

See Also

Build docs developers (and LLMs) love