MarkdownView
Markdown View for iOS.
Install / Use
/learn @keitaoouchi/MarkdownViewREADME
MarkdownView
A WKWebView-based Markdown renderer for iOS. Converts Markdown to HTML using markdown-it with syntax highlighting by highlight.js.

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
| Target | Version | |--------|---------| | 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
| Signature | Description |
|-----------|-------------|
| 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
| Name | Type | Default | Description |
|------|------|---------|-------------|
| isScrollEnabled | Bool | true | Controls whether the internal web view scrolls. Set false when embedding in a UIScrollView. |
| onTouchLink | ((URLRequest) -> Bool)? | nil | Called when a link is tapped. Return true to allow navigation, false to cancel. |
| onRendered | ((CGFloat) -> Void)? | nil | Called when rendering completes. The parameter is the content height in points. |
| intrinsicContentSize | CGSize | — | Returns the measured content height. Updates automatically after rendering. |
Methods
| Signature | Description |
|-----------|-------------|
| 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:...)andshow(markdown:)still work but are deprecated. Usereconfigure+renderinstead.
MarkdownUI (SwiftUI)
Initializer
MarkdownUI(
body: String? = nil,
css: String? = nil,
plugins: [String]? = nil,
stylesheets: [URL]? = nil,
styled: Bool = true
)
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| body | String? | nil | The Markdown string to render. |
| css | String? | nil | Custom CSS to inject. |
| plugins | [String]? | nil | Array of markdown-it plugin JavaScript strings. |
| stylesheets | [URL]? | nil | External stylesheet URLs to load. |
| styled | Bool | true | Use the built-in Bootstrap-based stylesheet. |
View Modifiers
| Modifier | Description |
|----------|-------------|
| .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:
MarkdownUIdisables internal scrolling. Wrap it in aScrollViewfor 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.
