Skip to content

Text Formatting Reference

Use when looking up NSAttributedString.Key values, underline styles, shadows, lists, tables, or view-compatibility rules.

Reference Skills

Use when looking up NSAttributedString.Key values, underline styles, shadows, lists, tables, or view-compatibility rules.

Family: Rich Text And Formatting

Use this skill when you already know the formatting problem and need the exact attribute or compatibility rules.

  • You need an NSAttributedString.Key reference.
  • You are checking which formatting works in which view.
  • You need paragraph-style, underline, shadow, or table-formatting details.
  • Need type choice or custom attribute scopes -> /skill apple-text-attributed-string
  • Need exact formatting keys and compatibility -> stay here
  • Need semantic text colors rather than general formatting -> /skill apple-text-colors

Character-Level Attributes (NSAttributedString.Key)

Section titled “Character-Level Attributes (NSAttributedString.Key)”
KeyValue TypeEffectMin OS
.fontUIFont / NSFontTypeface, size, weightAll
.kernNSNumber (CGFloat)Inter-character spacing (points)All
.trackingNSNumber (CGFloat)Tracking (scales with font size)iOS 14+
.ligatureNSNumber (Int)0=none, 1=default, 2=all (macOS only)All
.baselineOffsetNSNumber (CGFloat)Vertical shift from baselineAll
.obliquenessNSNumber (CGFloat)Synthetic italic (0=none, positive=right lean)All
.expansionNSNumber (CGFloat)Horizontal stretch (0=normal, positive=wider)All
.verticalGlyphFormNSNumber (Int)0=horizontal, 1=vertical (CJK)All
KeyValue TypeEffectMin OS
.foregroundColorUIColor / NSColorText colorAll
.backgroundColorUIColor / NSColorBackground behind textAll
.strokeColorUIColor / NSColorText outline colorAll
.strokeWidthNSNumber (CGFloat)Outline width. Negative = fill + strokeAll
.shadowNSShadowDrop shadowAll
KeyValue TypeEffectMin OS
.underlineStyleNSNumber (NSUnderlineStyle)Underline patternAll
.underlineColorUIColor / NSColorUnderline color (nil = foreground color)All
.strikethroughStyleNSNumber (NSUnderlineStyle)Strikethrough patternAll
.strikethroughColorUIColor / NSColorStrikethrough colorAll
KeyValue TypeEffectMin OS
.paragraphStyleNSParagraphStyleParagraph formatting (see below)All
.writingDirection[NSNumber]Embedding/override directionAll
.textEffectNSAttributedString.TextEffectStyleVisual effectiOS 7+
KeyValue TypeEffectMin OS
.attachmentNSTextAttachmentInline image/viewAll
.linkURL or StringHyperlinkAll
.textAlternativesNSTextAlternativesAlternative text interpretationsiOS 18+
.adaptiveImageGlyphNSAdaptiveImageGlyphGenmoji/stickersiOS 18+
KeyValue TypeEffect
.superscriptNSNumber (Int)Superscript (positive) or subscript (negative)
.cursorNSCursorMouse cursor when hovering
.toolTipStringHover tooltip
.markedClauseSegmentNSNumber (Int)CJK input clause segment
.spellingStateNSNumber (Int)Spelling/grammar error indicator
.glyphInfoNSGlyphInfoGlyph substitution
.textBlockNSTextBlockText block (table cell, etc.)

Combine with bitwise OR for compound styles:

StyleVisual
.singleSingle line
.thickThick line
.doubleDouble line
PatternVisual
(none)Solid line
.patternDotDotted
.patternDashDashed
.patternDashDotDash-dot
.patternDashDotDotDash-dot-dot
ModifierEffect
.byWordOnly under words, not spaces
// Thick dashed underline, words only
let style: NSUnderlineStyle = [.thick, .patternDash, .byWord]
// Double solid underline
let style: NSUnderlineStyle = [.double]
// Single dotted strikethrough
let attrs: [NSAttributedString.Key: Any] = [
.strikethroughStyle: NSUnderlineStyle.single.union(.patternDot).rawValue,
.strikethroughColor: UIColor.red
]
let shadow = NSShadow()
shadow.shadowOffset = CGSize(width: 2, height: -2) // Right and up
shadow.shadowBlurRadius = 4.0
shadow.shadowColor = UIColor.black.withAlphaComponent(0.3)
let attrs: [NSAttributedString.Key: Any] = [.shadow: shadow]

Note: SwiftUI Text ignores the .shadow attribute. Use .shadow() modifier instead (applies to entire view).

// The only public text effect
attrs[.textEffect] = NSAttributedString.TextEffectStyle.letterpressStyle

Letterpress style: Embossed appearance with light source. Purely visual. Only one public effect exists.

let style = NSMutableParagraphStyle()
// Alignment
style.alignment = .natural // .left, .right, .center, .justified, .natural
// Line Spacing
style.lineSpacing = 4 // Extra space between lines (after leading)
style.minimumLineHeight = 20 // Floor for line height
style.maximumLineHeight = 30 // Ceiling for line height
style.lineHeightMultiple = 1.2 // Multiplier on natural line height
// Paragraph Spacing
style.paragraphSpacing = 12 // Space AFTER paragraph
style.paragraphSpacingBefore = 8 // Space BEFORE paragraph
// Indentation
style.firstLineHeadIndent = 20 // First line indent
style.headIndent = 10 // Subsequent lines indent
style.tailIndent = -10 // Trailing margin (negative = from right edge)
// Line Breaking
style.lineBreakMode = .byWordWrapping // .byCharWrapping, .byClipping,
// .byTruncatingHead/Tail/Middle
style.lineBreakStrategy = .standard // .pushOut, .hangulWordPriority
// Hyphenation
style.hyphenationFactor = 0.5 // 0.0 (none) to 1.0 (max)
style.usesDefaultHyphenation = true // iOS 15+ (system-determined)
// Writing Direction
style.baseWritingDirection = .natural // .leftToRight, .rightToLeft
// Tabs
style.tabStops = [
NSTextTab(textAlignment: .left, location: 100),
NSTextTab(textAlignment: .decimal, location: 200),
]
style.defaultTabInterval = 36
// Tightening
style.allowsDefaultTighteningForTruncation = true // Reduce spacing before truncating
// Lists (AppKit + UIKit iOS 16+)
style.textLists = [NSTextList(markerFormat: .disc, options: 0)]
FormatExamplePlatform
.discAll
.circleAll
.squareAll
.decimal1. 2. 3.All
.lowercaseAlphaa. b. c.All
.uppercaseAlphaA. B. C.All
.lowercaseRomani. ii. iii.All
.uppercaseRomanI. II. III.All
.lowercaseLatina. b. c.macOS
.uppercaseLatinA. B. C.macOS
.lowercaseHexadecimal1. 2. … a. b.macOS
.uppercaseHexadecimal1. 2. … A. B.macOS
.octal1. 2. … 10.macOS
.hyphen-macOS
.checkmacOS

NSTextTable / NSTextTableBlock (AppKit Only)

Section titled “NSTextTable / NSTextTableBlock (AppKit Only)”
let table = NSTextTable()
table.numberOfColumns = 3
table.collapsesBorders = true
let cell = NSTextTableBlock(
table: table,
startingRow: 0, rowSpan: 1,
startingColumn: 0, columnSpan: 1
)
cell.backgroundColor = .secondarySystemBackground
cell.setWidth(1.0, type: .absoluteValueType, for: .border)
cell.setWidth(4.0, type: .absoluteValueType, for: .padding)
let style = NSMutableParagraphStyle()
style.textBlocks = [cell]
// Apply to the paragraph's attributed string

Triggers TextKit 1 fallback. No UIKit equivalent.

Negative stroke width creates fill + stroke (outlined text):

// Outlined text (stroke only)
attrs[.strokeWidth] = 3.0
attrs[.strokeColor] = UIColor.blue
// Filled + outlined (negative width)
attrs[.strokeWidth] = -3.0
attrs[.strokeColor] = UIColor.blue
attrs[.foregroundColor] = UIColor.white
AttributeSwiftUI TextTextEditor (iOS 26+)UITextViewUILabelNSTextView
font
foregroundColor
backgroundColor
paragraphStyle✅ (alignment, lineHeight)
kern/tracking
underlineStyle
strikethroughStyle
shadow
strokeColor/Width
link
attachment✅ (display)
baselineOffset
obliqueness
expansion
textEffect
superscriptN/AN/AN/AN/A✅ (AppKit)
toolTipN/AN/AN/AN/A✅ (AppKit)
textTableN/AN/AN/AN/A✅ (AppKit, TK1)
textList✅ (iOS 17+)
textView.allowsEditingTextAttributes = true

When enabled, the text selection menu shows a BIU button:

  • B — Bold (toggles font weight)
  • I — Italic (toggles font style)
  • U — Underline (toggles underline style)

That’s the only built-in formatting UI. For more (font size, color, alignment), you must build custom UI.

Attributes that survive RTF archiving:

  • font, foregroundColor, backgroundColor, paragraphStyle
  • underline, strikethrough, kern, baselineOffset
  • link, attachment, shadow, strokeColor/Width
  • superscript (AppKit), textList, textTable

Attributes that are lost in RTF:

  • obliqueness, expansion (stored as font descriptors — may survive if font supports)
  • textEffect (letterpress)
  • custom attributes (unless you handle RTF custom tags)
  1. Negative strokeWidth is fill+stroke, positive is stroke only — counterintuitive but important for outlined text.
  2. NSMutableParagraphStyle is required for changes — NSParagraphStyle is immutable. Always create mutable variant.
  3. paragraphStyle applies to entire paragraph — Even if you set it on a sub-range, fixAttributes extends it to the full paragraph.
  4. SwiftUI Text ignores most attributes — Only ~10 work. The rest are silently dropped.
  5. NSTextTable triggers TK1 fallback — AppKit-only, and forces TextKit 1 mode.
  6. kern vs tracking — Kern is absolute (points). Tracking scales with font size. Use tracking for proportional spacing.

This page documents the apple-text-formatting-ref reference skill. Use it when the subsystem is already known and you need mechanics, behavior, or API detail.

  • apple-text-attributed-string: Use when choosing between AttributedString and NSAttributedString, defining custom attributes, or converting between them.
  • apple-text-colors: Use when choosing text colors, semantic colors, dark mode adaptation, or wide-color/HDR text across UIKit, AppKit, and SwiftUI.
  • apple-text-attachments-ref: Use when embedding images, custom views, Genmoji, or other non-text content inline — NSTextAttachment, view providers, bounds, baseline.
Full SKILL.md source
SKILL.md
---
name: apple-text-formatting-ref
description: Use when looking up NSAttributedString.Key values, underline styles, shadows, lists, tables, or view-compatibility rules
license: MIT
---
# Text Formatting Reference
Use this skill when you already know the formatting problem and need the exact attribute or compatibility rules.
## When to Use
- You need an `NSAttributedString.Key` reference.
- You are checking which formatting works in which view.
- You need paragraph-style, underline, shadow, or table-formatting details.
## Quick Decision
- Need type choice or custom attribute scopes -> `/skill apple-text-attributed-string`
- Need exact formatting keys and compatibility -> stay here
- Need semantic text colors rather than general formatting -> `/skill apple-text-colors`
## Core Guidance
## Character-Level Attributes (NSAttributedString.Key)
### Typography
| Key | Value Type | Effect | Min OS |
|-----|-----------|--------|--------|
| `.font` | UIFont / NSFont | Typeface, size, weight | All |
| `.kern` | NSNumber (CGFloat) | Inter-character spacing (points) | All |
| `.tracking` | NSNumber (CGFloat) | Tracking (scales with font size) | iOS 14+ |
| `.ligature` | NSNumber (Int) | 0=none, 1=default, 2=all (macOS only) | All |
| `.baselineOffset` | NSNumber (CGFloat) | Vertical shift from baseline | All |
| `.obliqueness` | NSNumber (CGFloat) | Synthetic italic (0=none, positive=right lean) | All |
| `.expansion` | NSNumber (CGFloat) | Horizontal stretch (0=normal, positive=wider) | All |
| `.verticalGlyphForm` | NSNumber (Int) | 0=horizontal, 1=vertical (CJK) | All |
### Color
| Key | Value Type | Effect | Min OS |
|-----|-----------|--------|--------|
| `.foregroundColor` | UIColor / NSColor | Text color | All |
| `.backgroundColor` | UIColor / NSColor | Background behind text | All |
| `.strokeColor` | UIColor / NSColor | Text outline color | All |
| `.strokeWidth` | NSNumber (CGFloat) | Outline width. Negative = fill + stroke | All |
| `.shadow` | NSShadow | Drop shadow | All |
### Decoration
| Key | Value Type | Effect | Min OS |
|-----|-----------|--------|--------|
| `.underlineStyle` | NSNumber (NSUnderlineStyle) | Underline pattern | All |
| `.underlineColor` | UIColor / NSColor | Underline color (nil = foreground color) | All |
| `.strikethroughStyle` | NSNumber (NSUnderlineStyle) | Strikethrough pattern | All |
| `.strikethroughColor` | UIColor / NSColor | Strikethrough color | All |
### Layout & Structure
| Key | Value Type | Effect | Min OS |
|-----|-----------|--------|--------|
| `.paragraphStyle` | NSParagraphStyle | Paragraph formatting (see below) | All |
| `.writingDirection` | [NSNumber] | Embedding/override direction | All |
| `.textEffect` | NSAttributedString.TextEffectStyle | Visual effect | iOS 7+ |
### Content
| Key | Value Type | Effect | Min OS |
|-----|-----------|--------|--------|
| `.attachment` | NSTextAttachment | Inline image/view | All |
| `.link` | URL or String | Hyperlink | All |
| `.textAlternatives` | NSTextAlternatives | Alternative text interpretations | iOS 18+ |
| `.adaptiveImageGlyph` | NSAdaptiveImageGlyph | Genmoji/stickers | iOS 18+ |
### AppKit-Only
| Key | Value Type | Effect |
|-----|-----------|--------|
| `.superscript` | NSNumber (Int) | Superscript (positive) or subscript (negative) |
| `.cursor` | NSCursor | Mouse cursor when hovering |
| `.toolTip` | String | Hover tooltip |
| `.markedClauseSegment` | NSNumber (Int) | CJK input clause segment |
| `.spellingState` | NSNumber (Int) | Spelling/grammar error indicator |
| `.glyphInfo` | NSGlyphInfo | Glyph substitution |
| `.textBlock` | NSTextBlock | Text block (table cell, etc.) |
## NSUnderlineStyle
Combine with bitwise OR for compound styles:
### Line Style
| Style | Visual |
|-------|--------|
| `.single` | Single line |
| `.thick` | Thick line |
| `.double` | Double line |
### Pattern (Combine with style)
| Pattern | Visual |
|---------|--------|
| (none) | Solid line |
| `.patternDot` | Dotted |
| `.patternDash` | Dashed |
| `.patternDashDot` | Dash-dot |
| `.patternDashDotDot` | Dash-dot-dot |
### Modifier
| Modifier | Effect |
|----------|--------|
| `.byWord` | Only under words, not spaces |
### Examples
```swift
// Thick dashed underline, words only
let style: NSUnderlineStyle = [.thick, .patternDash, .byWord]
// Double solid underline
let style: NSUnderlineStyle = [.double]
// Single dotted strikethrough
let attrs: [NSAttributedString.Key: Any] = [
.strikethroughStyle: NSUnderlineStyle.single.union(.patternDot).rawValue,
.strikethroughColor: UIColor.red
]
```
## NSShadow
```swift
let shadow = NSShadow()
shadow.shadowOffset = CGSize(width: 2, height: -2) // Right and up
shadow.shadowBlurRadius = 4.0
shadow.shadowColor = UIColor.black.withAlphaComponent(0.3)
let attrs: [NSAttributedString.Key: Any] = [.shadow: shadow]
```
**Note:** SwiftUI Text ignores the `.shadow` attribute. Use `.shadow()` modifier instead (applies to entire view).
## Text Effects
```swift
// The only public text effect
attrs[.textEffect] = NSAttributedString.TextEffectStyle.letterpressStyle
```
**Letterpress style:** Embossed appearance with light source. Purely visual. Only one public effect exists.
## NSParagraphStyle (Complete)
### All Properties
```swift
let style = NSMutableParagraphStyle()
// Alignment
style.alignment = .natural // .left, .right, .center, .justified, .natural
// Line Spacing
style.lineSpacing = 4 // Extra space between lines (after leading)
style.minimumLineHeight = 20 // Floor for line height
style.maximumLineHeight = 30 // Ceiling for line height
style.lineHeightMultiple = 1.2 // Multiplier on natural line height
// Paragraph Spacing
style.paragraphSpacing = 12 // Space AFTER paragraph
style.paragraphSpacingBefore = 8 // Space BEFORE paragraph
// Indentation
style.firstLineHeadIndent = 20 // First line indent
style.headIndent = 10 // Subsequent lines indent
style.tailIndent = -10 // Trailing margin (negative = from right edge)
// Line Breaking
style.lineBreakMode = .byWordWrapping // .byCharWrapping, .byClipping,
// .byTruncatingHead/Tail/Middle
style.lineBreakStrategy = .standard // .pushOut, .hangulWordPriority
// Hyphenation
style.hyphenationFactor = 0.5 // 0.0 (none) to 1.0 (max)
style.usesDefaultHyphenation = true // iOS 15+ (system-determined)
// Writing Direction
style.baseWritingDirection = .natural // .leftToRight, .rightToLeft
// Tabs
style.tabStops = [
NSTextTab(textAlignment: .left, location: 100),
NSTextTab(textAlignment: .decimal, location: 200),
]
style.defaultTabInterval = 36
// Tightening
style.allowsDefaultTighteningForTruncation = true // Reduce spacing before truncating
// Lists (AppKit + UIKit iOS 16+)
style.textLists = [NSTextList(markerFormat: .disc, options: 0)]
```
### NSTextList Marker Formats
| Format | Example | Platform |
|--------|---------|----------|
| `.disc` | • | All |
| `.circle` | ○ | All |
| `.square` | ■ | All |
| `.decimal` | 1. 2. 3. | All |
| `.lowercaseAlpha` | a. b. c. | All |
| `.uppercaseAlpha` | A. B. C. | All |
| `.lowercaseRoman` | i. ii. iii. | All |
| `.uppercaseRoman` | I. II. III. | All |
| `.lowercaseLatin` | a. b. c. | macOS |
| `.uppercaseLatin` | A. B. C. | macOS |
| `.lowercaseHexadecimal` | 1. 2. ... a. b. | macOS |
| `.uppercaseHexadecimal` | 1. 2. ... A. B. | macOS |
| `.octal` | 1. 2. ... 10. | macOS |
| `.hyphen` | - | macOS |
| `.check` | ✓ | macOS |
### NSTextTable / NSTextTableBlock (AppKit Only)
```swift
let table = NSTextTable()
table.numberOfColumns = 3
table.collapsesBorders = true
let cell = NSTextTableBlock(
table: table,
startingRow: 0, rowSpan: 1,
startingColumn: 0, columnSpan: 1
)
cell.backgroundColor = .secondarySystemBackground
cell.setWidth(1.0, type: .absoluteValueType, for: .border)
cell.setWidth(4.0, type: .absoluteValueType, for: .padding)
let style = NSMutableParagraphStyle()
style.textBlocks = [cell]
// Apply to the paragraph's attributed string
```
**Triggers TextKit 1 fallback.** No UIKit equivalent.
## Stroke Width Trick
Negative stroke width creates fill + stroke (outlined text):
```swift
// Outlined text (stroke only)
attrs[.strokeWidth] = 3.0
attrs[.strokeColor] = UIColor.blue
// Filled + outlined (negative width)
attrs[.strokeWidth] = -3.0
attrs[.strokeColor] = UIColor.blue
attrs[.foregroundColor] = UIColor.white
```
## Where Formatting Works
| Attribute | SwiftUI Text | TextEditor (iOS 26+) | UITextView | UILabel | NSTextView |
|-----------|-------------|---------------------|------------|---------|------------|
| font | ✅ | ✅ | ✅ | ✅ | ✅ |
| foregroundColor | ✅ | ✅ | ✅ | ✅ | ✅ |
| backgroundColor | ✅ | ✅ | ✅ | ✅ | ✅ |
| paragraphStyle | ❌ | ✅ (alignment, lineHeight) | ✅ | ✅ | ✅ |
| kern/tracking | ✅ | ✅ | ✅ | ✅ | ✅ |
| underlineStyle | ✅ | ✅ | ✅ | ✅ | ✅ |
| strikethroughStyle | ✅ | ✅ | ✅ | ✅ | ✅ |
| shadow | ❌ | ❌ | ✅ | ✅ | ✅ |
| strokeColor/Width | ❌ | ❌ | ✅ | ✅ | ✅ |
| link | ✅ | ✅ | ✅ | ❌ | ✅ |
| attachment | ❌ | ❌ | ✅ | ✅ (display) | ✅ |
| baselineOffset | ✅ | ✅ | ✅ | ✅ | ✅ |
| obliqueness | ❌ | ❌ | ✅ | ✅ | ✅ |
| expansion | ❌ | ❌ | ✅ | ✅ | ✅ |
| textEffect | ❌ | ❌ | ✅ | ✅ | ✅ |
| superscript | N/A | N/A | N/A | N/A | ✅ (AppKit) |
| toolTip | N/A | N/A | N/A | N/A | ✅ (AppKit) |
| textTable | N/A | N/A | N/A | N/A | ✅ (AppKit, TK1) |
| textList | ❌ | ❌ | ✅ (iOS 17+) | ✅ | ✅ |
## UITextView Built-in Formatting UI
```swift
textView.allowsEditingTextAttributes = true
```
When enabled, the text selection menu shows a **BIU** button:
- **B** — Bold (toggles font weight)
- **I** — Italic (toggles font style)
- **U** — Underline (toggles underline style)
That's the only built-in formatting UI. For more (font size, color, alignment), you must build custom UI.
## RTF Round-Trip
Attributes that **survive** RTF archiving:
- font, foregroundColor, backgroundColor, paragraphStyle
- underline, strikethrough, kern, baselineOffset
- link, attachment, shadow, strokeColor/Width
- superscript (AppKit), textList, textTable
Attributes that are **lost** in RTF:
- obliqueness, expansion (stored as font descriptors — may survive if font supports)
- textEffect (letterpress)
- custom attributes (unless you handle RTF custom tags)
## Common Pitfalls
1. **Negative strokeWidth is fill+stroke, positive is stroke only** — counterintuitive but important for outlined text.
2. **NSMutableParagraphStyle is required for changes** — NSParagraphStyle is immutable. Always create mutable variant.
3. **paragraphStyle applies to entire paragraph** — Even if you set it on a sub-range, `fixAttributes` extends it to the full paragraph.
4. **SwiftUI Text ignores most attributes** — Only ~10 work. The rest are silently dropped.
5. **NSTextTable triggers TK1 fallback** — AppKit-only, and forces TextKit 1 mode.
6. **kern vs tracking** — Kern is absolute (points). Tracking scales with font size. Use tracking for proportional spacing.
## Related Skills
- Use `/skill apple-text-attributed-string` for AttributedString vs NSAttributedString decisions.
- Use `/skill apple-text-colors` when the formatting question is mostly about semantic color behavior.
- Use `/skill apple-text-attachments-ref` for inline non-text content instead of pure formatting attributes.