MarkdownView: CommonMark in SwiftUI, one view per block
Display markdown content with SwiftUI.
At a glance
- What is it?
- MarkdownView parses with swift-markdown and renders the result as native SwiftUI views rather than a web view, with streaming support, LaTeX math and pluggable element renderers. The catch is that some features are platform-gated, and text selection behaves differently depending on which of its two rendering paths you pick.
- Who is it for?
- Adopt MarkdownView if you are building a SwiftUI app that needs to show Markdown as native views, especially if the content arrives in chunks from a model or a network stream, and you can accept that text selection, math and some font customisation behave differently across platforms and OS versions. Do not adopt it if you need a single consistent text selection behaviour everywhere, since MarkdownText falls back to the platform body font before OS 26.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Native views, not a web view, and that is the whole point
MarkdownView renders Markdown in SwiftUI with native views, configurable styling and extensible renderers, and it is powered by swift-markdown, the parsing library from the Swift project, which the README says makes it fully compliant with the CommonMark standard. That last point decides the parsing behaviour for you. Rather than shipping a fork of a CommonMark parser, the package delegates to swift-markdown for parsing and tree traversal, so the document model is the one Apple's tooling produces, and the package's job is to walk that tree and map each node onto a SwiftUI view. The alternative approaches trade off differently. A WKWebView rendering Markdown client side gives you pixel-perfect control over CSS and full HTML, at the cost of an embedded browser, no native text selection, awkward dark mode handling and a memory cost that a chat interface with hundreds of messages will feel. Rendering Markdown into a single attributed string is cheap and selects text natively, but it cannot do a table, a code block with syntax highlighting, or an inline image layout that reflows. MarkdownView sits in the middle: real views, real layout, real accessibility, with a per-block style surface.
Two rendering paths, and they differ in more than the API
There are two components, MarkdownView and MarkdownText, and the README is clear that they are not interchangeable. MarkdownView is the full view-based layout, and it always respects the SwiftUI.Font you set. MarkdownText is the text-based path, to be used when text selection behaviour matters more than full view-based layout, and it respects SwiftUI.Font only on OS 26 and later. On earlier OS versions it falls back to the platform body font because of API limitations, and the README recommends using CustomCTFontConvertible conforming types such as UIFont or NSFont if you need to support those earlier versions. MarkdownText also depends on an external framework, RichText, and is available only where that framework is available, currently iOS and macOS. The static string version is a one-liner:
MarkdownText("Hello **MarkdownText**")That dependency on a sibling package for one of its two rendering paths is the detail that decides whether the package fits your project, and it is stated in the README rather than discovered at compile time.
Streaming is the feature that justifies the package
StreamingMarkdownReader is described as optimized for rendering streaming markdown, with support for scheduled document processing, incremental parsing as new content arrives, and background document parsing. The API shape reflects that:
@State private var markdownSource = StreamingMarkdownSource()
StreamingMarkdownReader(markdownSource) { parseResult in
// Use MarkdownView or MarkdownText based on your needs.
MarkdownView(parseResult)
}The usage rule is specific and easy to get wrong. You create one StreamingMarkdownSource for each response and keep it alive for the lifetime of that stream. You update markdownSource.text as new chunks arrive, and call finishStreaming() when the stream finishes. The reason to care is that a model streaming tokens into a chat UI produces a document that is syntactically incomplete for most of its life. A parser that only handles a finished document either shows nothing until the last token arrives, or flickers as the text is reparsed from scratch. Incremental parsing is the mechanism that avoids both, and it is the reason this package is more than a Markdown-to-SwiftUI converter. The same state object also lets a view choose MarkdownView or MarkdownText per use, since the reader hands you a parse result and the rendering component is your choice.
Styling is per component, and the font caveat is real
The styling surface is a set of modifiers that name the Markdown component they affect, so a font for a heading level or a tint for an inline code block is set by passing the component alongside the value:
MarkdownView("# H1 title")
.font(.largeTitle.weight(.black), for: .h1)MarkdownView("> Quote and `inline code`")
.tint(.pink, for: .inlineCodeBlock)Tables, block quotes and code blocks each have their own style modifier, with named styles such as .github available, and code block styles take a light and dark theme, for example .default(lightTheme: "xcode", darkTheme: "dark"). Lists are customised separately, with an indent value and a choice of unordered list marker such as .bullet. This is the shape of a design system API rather than a set of one-off flags, and it means the styling of a document is declared in one place instead of being threaded through every element type. The limitation from earlier returns here, because the font modifier for MarkdownText is the case where the two paths diverge on OS versions before 26.
Math and SVG images come from separate dependencies
LaTeX math is enabled with a single modifier, and both inline and display math are supported:
MarkdownView("Inline math: $E = mc^2$")
.markdownMathRenderingEnabled()The catch is availability. Math rendering works on iOS and macOS when the LaTeX trait is enabled, and the trait is enabled by default, so the common case just works, but the trait is a build-level switch rather than a runtime one. The dependency doing the work is SwiftMath by mgriebling. Syntax highlighting of code blocks comes from Highlightr by raspu, and the text-based rendering path comes from RichText by the same author as MarkdownView. SVG image support is listed as a built-in feature, which is notable because SVG is the one image format that does not decode into a bitmap on Apple platforms without extra work. The dependency list is short and each entry has a clear job, but it is four packages, one of which, RichText, gates a documented component.
Custom renderers replace elements by scheme and directive name
The extension mechanism is the most interesting part of the design. You register a renderer for images, links or block directives, and the registration is scoped to a view hierarchy and keyed by a URL scheme for images and links or by a name for block directives:
MarkdownView(markdownText)
.markdownElementRenderer(.image(CustomImageRenderer(), urlScheme: "my-image"))MarkdownView(markdownText)
.markdownElementRenderer(.link(CustomLinkRenderer(), urlScheme: "app"))
.markdownElementRenderer(.blockDirective(CustomBlockDirectiveRenderer(), name: "note"))A custom image renderer conforms to MarkdownImageRenderer and provides a makeBody method, so it returns any SwiftUI view, which in the README example is an AsyncImage with a loading state, a success case and a failure case. The scoping rule is stated plainly: registering another renderer with the same block directive name or URL scheme replaces the previous registration in the same view hierarchy. That is a useful property, because it means a detail view can override the app-wide renderer for one screen without the two fighting. The block directive support is the more unusual half, since it lets a document carry structured annotations that map to native UI rather than to text.
Sharing one parse across several views
When more than one view needs the same document, MarkdownReader exists so the parse happens once:
MarkdownReader(markdownText) { parseResult in
MarkdownView(parseResult)
MarkdownTableOfContentReader(parseResult) { headings in
ForEach(headings.indices, id: \.self) { index in
Text(headings[index].plainText)
}
}
}The parse result is the shared currency. MarkdownView takes it directly, and MarkdownTableOfContentReader takes the same result and hands back a list of headings, so a table of contents and the document body stay consistent with each other and only one parse is performed. The headings carry a plainText property, which is what you would use to build a navigation list. This is a smaller feature than it looks, because it only pays off when a screen shows both a document and something derived from it, and in that case doing two independent parses is the mistake that produces a table of contents that disagrees with the body.
MIT licensed, a 3.0 release, and a wide platform floor
The licence is MIT, with a LICENSE file at the root. The repository is not archived and the last push was on 2026-09-29. The release history shows active work: 2.6.1 on 2026-03-14, 2.7.0 on 2026-06-02 and 3.0.0 on 2026-07-05, so the project has crossed a major version, and a major version bump in a UI library is where you should read the migration notes rather than assume source compatibility. The supported platforms are wide for a SwiftUI package: macOS 13.0 or later, iOS 16.0 or later, tvOS 16.0 or later, watchOS 9.0 or later and visionOS 1.0 or later. That includes tvOS and watchOS, which is more than most Markdown renderers claim, and it is consistent with a package built out of native SwiftUI views rather than around a platform-specific renderer. The package is distributed through Swift Package Manager, with a Package.swift at the root, and it is indexed on Swift Package Index, which is where you would look for platform and Swift version support badges.
Editorial conclusion
Adopt MarkdownView if you are building a SwiftUI app that needs to show Markdown as native views, especially if the content arrives in chunks from a model or a network stream, and you can accept that text selection, math and some font customisation behave differently across platforms and OS versions. Do not adopt it if you need a single consistent text selection behaviour everywhere, since MarkdownText falls back to the platform body font before OS 26. Before you commit, read the platform and trait notes in the README, check the RichText availability for your target, and confirm swift-markdown covers the CommonMark constructs your content actually uses.
Frequently asked questions
What is MarkdownView?
It is an MIT licensed SwiftUI package that renders Markdown as native views, powered by swift-markdown and fully compliant with the CommonMark standard. It supports macOS 13, iOS 16, tvOS 16, watchOS 9 and visionOS 1 and later.
How do I add MarkdownView to a Swift project?
Add the package dependency with .package(url: "https://github.com/LiYanan2004/MarkdownView.git", branch: "main") and add the product to your target with dependencies: ["MarkdownView"] in the target definition.
How does MarkdownView handle streaming Markdown?
StreamingMarkdownReader is built for it, with scheduled document processing, incremental parsing as new content arrives and background document parsing. You create one StreamingMarkdownSource per response, keep it alive for the stream, update markdownSource.text as chunks arrive and call finishStreaming() at the end.
What is the difference between MarkdownView and MarkdownText?
MarkdownView is the view-based layout and always respects the SwiftUI.Font you set. MarkdownText is the text-based path for when text selection matters more, and it respects SwiftUI.Font only on OS 26 and later, falling back to the platform body font earlier, and it requires the RichText framework, so it is available on iOS and macOS.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/liyanan2004-markdownview)Was this useful?