SwiftUI.cc
Snippets
Text & InputRich TextIntermediate

SwiftUI Text with Markdown and AttributedString

Text literals parse Markdown — bold, italics, code, links — while AttributedString unlocks programmatic runs, custom attributes, and styled interpolation.

4 min readUpdated 2026-06
Markdown literal and styled runs
import SwiftUI

struct RichGreeting: View {
    var highlighted: AttributedString {
        var s = AttributedString("Storage almost full: 9.1 GB of 10 GB used.")
        if let range = s.range(of: "9.1 GB") {
            s[range].foregroundColor = .red
            s[range].font = .body.bold()
        }
        return s
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("**Welcome back!** Read the *changelog* or [docs](https://example.com).")

            Text(highlighted)

            Text("Tap \(Image(systemName: "square.and.arrow.up")) to share")
        }
        .padding()
    }
}

Markdown text preview

Literals are special

Text("**Bold** and [link](https://…)") parses because literals become LocalizedStringKey, which interprets Markdown. The same content in a String variable renders as raw asterisks — the most common surprise here. Convert explicitly:

let md = try? AttributedString(markdown: serverString)
Text(md ?? AttributedString(serverString))

This literal behavior is a localization feature: translators see and place the emphasis markers within their sentence structure.

AttributedString for programmatic runs

Find ranges and set attributes — color a price, bold a username, underline a match:

var s = AttributedString("Pay $4.99 now")
if let r = s.range(of: "$4.99") { s[r].foregroundColor = .green }

Attributes cover font, color, kerning, baseline offset, and links; SwiftUI-specific attributes coexist with Foundation ones.

Interpolation tricks

Text("Tap \(Image(systemName: "gear")) to configure") embeds the symbol inline at text size. Interpolating styled Text values composes differently-styled runs without AttributedString when the structure is static.

Common mistakes

  • Expecting String variables to parse Markdown.
  • Rebuilding AttributedString every render for static content — compute once.
  • Markdown tables/headings in Text, which only supports inline-level syntax.

Related reference