MarkdownView

February 28, 2026 ยท View on GitHub

Swift 6.0 Swift Package Manager compatible

A WKWebView-based Markdown renderer for iOS. Converts Markdown to HTML using markdown-it with syntax highlighting by highlight.js.

GIF

Features

  • Renders Markdown as styled HTML inside a native UIView
  • Syntax highlighting for code blocks via highlight.js
  • Dark mode support (automatic, via prefers-color-scheme)
  • Custom CSS injection
  • markdown-it plugin support (e.g., KaTeX math)
  • External stylesheet loading
  • Intrinsic content size for Auto Layout integration
  • Link tap handling
  • SwiftUI support via MarkdownUI

Requirements

TargetVersion
iOS>= 16.0
Swift>= 6.0

Installation

MarkdownView is available through Swift Package Manager.

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/keitaoouchi/MarkdownView.git", from: "2.1.0")
]

Alternatively, you can add the package directly via Xcode (File > Add Package Dependencies).

Quick Start

UIKit

import MarkdownView

let md = MarkdownView()
md.reconfigure()
md.render(markdown: "# Hello World!")

SwiftUI

import SwiftUI
import MarkdownView

struct ContentView: View {
    var body: some View {
        ScrollView {
            MarkdownUI(body: "# Hello World!")
                .onTouchLink { request in
                    print(request.url ?? "")
                    return false
                }
                .onRendered { height in
                    print(height)
                }
        }
    }
}

API Reference

MarkdownView (UIKit)

Initializers

SignatureDescription
init()Creates a view with default settings. Call reconfigure() then render(markdown:) to display content.
init(css: String?, plugins: [String]?, stylesheets: [URL]? = nil, styled: Bool = true)Pre-configures a web view with CSS, plugins, and stylesheets. Use with render(markdown:) for efficient updates.
init?(coder: NSCoder)Interface Builder support.

Properties

NameTypeDefaultDescription
isScrollEnabledBooltrueControls whether the internal web view scrolls. Set false when embedding in a UIScrollView.
onTouchLink((URLRequest) -> Bool)?nilCalled when a link is tapped. Return true to allow navigation, false to cancel.
onRendered((CGFloat) -> Void)?nilCalled when rendering completes. The parameter is the content height in points.
intrinsicContentSizeCGSizeโ€”Returns the measured content height. Updates automatically after rendering.

Methods

SignatureDescription
reconfigure(css: String? = nil, plugins: [String]? = nil, stylesheets: [URL]? = nil, styled: Bool = true)Creates (or recreates) the internal web view with the given CSS, plugins, and stylesheets.
reconfigure(with: ConfigurationOptions)Same as above using a ConfigurationOptions struct.
render(markdown: String, options: RenderOptions = RenderOptions())Renders Markdown on the current web view. If the web view is still loading, the request is queued and executed automatically when ready.

reconfigure vs render: reconfigure sets up the web view and styling. render sends Markdown to the already-configured web view. For dynamic content that changes frequently, call reconfigure once and then render for each update.

Deprecated: load(markdown:...) and show(markdown:) still work but are deprecated. Use reconfigure + render instead.

MarkdownUI (SwiftUI)

Initializer

MarkdownUI(
    body: String? = nil,
    css: String? = nil,
    plugins: [String]? = nil,
    stylesheets: [URL]? = nil,
    styled: Bool = true
)
ParameterTypeDefaultDescription
bodyString?nilThe Markdown string to render.
cssString?nilCustom CSS to inject.
plugins[String]?nilArray of markdown-it plugin JavaScript strings.
stylesheets[URL]?nilExternal stylesheet URLs to load.
styledBooltrueUse the built-in Bootstrap-based stylesheet.

View Modifiers

ModifierDescription
.onTouchLink(perform: @escaping (URLRequest) -> Bool)Called when a link is tapped. Return true to allow navigation, false to cancel.
.onRendered(perform: @escaping (CGFloat) -> Void)Called when rendering completes with the content height.

Note: MarkdownUI disables internal scrolling. Wrap it in a ScrollView for scrollable content.

Customization

Custom CSS

Inject a CSS string to override the default styles:

// UIKit
let css = "body { background-color: #f0f0f0; } code { font-size: 14px; }"
let md = MarkdownView(css: css, plugins: nil)
md.render(markdown: "# Styled content")

// SwiftUI
MarkdownUI(body: "# Styled content", css: "body { background-color: #f0f0f0; }")

See Example/Example/ViewController/CustomCss.swift for a full example.

Plugins

Add markdown-it compatible plugins by passing the plugin JavaScript as a string. Each plugin must be self-contained with no external dependencies.

let katexPlugin = try! String(contentsOfFile: Bundle.main.path(forResource: "katex", ofType: "js")!)
let md = MarkdownView(css: nil, plugins: [katexPlugin])
md.render(markdown: "Inline math: $E = mc^2$")

See Example/Example/ViewController/Plugins.swift for a full example, and the sample plugin project for building a compatible plugin library.

External Stylesheets

Load CSS from remote URLs:

let url = URL(string: "https://example.com/custom.css")!
let md = MarkdownView(css: nil, plugins: nil, stylesheets: [url])
md.render(markdown: "# Remote-styled content")

Styled vs Non-Styled Mode

By default, styled: true loads a Bootstrap-based stylesheet with highlight.js themes. Set styled: false to start with a blank canvas and apply your own CSS from scratch.

let md = MarkdownView(css: myCustomCSS, plugins: nil, styled: false)

Dark Mode

The built-in stylesheet supports dark mode automatically via prefers-color-scheme. Text and link colors adapt to the system appearance. No additional configuration is needed.

To customize dark mode styles, inject CSS with a prefers-color-scheme media query:

let css = """
@media (prefers-color-scheme: dark) {
    body { background-color: #1a1a1a; }
    code { color: #e06c75; }
}
"""
let md = MarkdownView(css: css, plugins: nil)

Example Project

The Example/ directory contains a full iOS app demonstrating UIKit usage, custom CSS, and plugin integration.

Architecture

See AGENTS.md for a high-level overview of the component architecture and data flow.

License

bootstrap is licensed under the MIT License. highlight.js is licensed under the BSD-3-Clause License. markdown-it is licensed under the MIT License.

MarkdownView is available under the MIT license. See the LICENSE file for more info.