BonMot
Beautiful, easy attributed strings in Swift
Install / Use
/learn @Rightpoint/BonMotREADME
BonMot (pronounced Bon Mo, French for good word) is a Swift attributed string library. It abstracts away the complexities of the iOS, macOS, tvOS, and watchOS typography tools, freeing you to focus on making your text beautiful.
To run the example project, run pod try BonMot, or clone the repo, open BonMot.xcodeproj, and run the Example-iOS target.
AttributedString
BonMot has been sherlocked! If you're targeting iOS 15 or higher, you may want to check out AttributedString instead. If you're an existing user of BonMot using Xcode 13, you may want to add the following typealias somewhere in your project to avoid a conflict with Foundation.StringStyle:
typealias StringStyle = BonMot.StringStyle
Usage
In any Swift file where you want to use BonMot, simply import BonMot.
Basics
Use a StringStyle to specify the style of your attributed string. Then, use the styled(with:) method on String to get your attributed string:
let quote = """
I used to love correcting people’s grammar until \
I realized what I loved more was having friends.
-Mara Wilson
"""
let style = StringStyle(
.font(UIFont(name: "AmericanTypewriter", size: 17)!),
.lineHeightMultiple(1.8)
)
let attributedString = quote.styled(with: style)
// You can also get the style’s attributes dictionary
// if you’re using an API that requires it.
let attributes = style.attributes
Glossary
These are the types with which you will most commonly interact when using BonMot to build attributed strings.
StringStyle: a collection of attributes which can be used to style a string. These include basics, like font and color, and more advanced settings like paragraph controls and OpenType features. To get a good idea of the full set of features that BonMot supports, look at the interface for this struct.StringStyle.Part: an enum which can be used to concisely construct aStringStyle. You will typically interact with these, rather than constructingStringStyles attribute by attribute.Composable: a protocol defining any type that knows how to append itself to an attributed string. BonMot provides functions, such as the one in this example, to join together multipleComposablevalues.NamedStyles: use this to register custom, reusable styles in a global namespace.Special: a utility to include special, ambiguous, and non-printing characters in your strings without making your code unreadable.
Style Inheritance
Styles can inherit from each other, which lets you create multiple styles that share common attributes:
let baseStyle = StringStyle(
.lineHeightMultiple(1.2),
.font(UIFont.systemFont(ofSize: 17))
)
let redStyle = baseStyle.byAdding(.color(.red))
let blueStyle = baseStyle.byAdding(.color(.blue))
let redBirdString = "bird".styled(with: redStyle)
let blueBirdString = "bird".styled(with: blueStyle)
Styling Parts of Strings with XML
Are you trying to style just part of a string, perhaps even a localized string which is different depending on the locale of the app? No problem! BonMot can turn custom XML tags and simple HTML into attributed strings:
// This would typically be a localized string
let string = "one fish, two fish, <red>red fish</red>,<BON:noBreakSpace/><blue>blue fish</blue>"
let redStyle = StringStyle(.color(.red))
let blueStyle = StringStyle(.color(.blue))
let fishStyle = StringStyle(
.font(UIFont.systemFont(ofSize: 17)),
.lineHeightMultiple(1.8),
.color(.darkGray),
.xmlRules([
.style("red", redStyle),
.style("blue", blueStyle),
])
)
let attributedString = string.styled(with: fishStyle)
This will produce:
<img width=227 src="Resources/readme-images/fish-with-black-comma.png" />Note the use of
<BON:noBreakSpace/>to specify a special character within the string. This is a great way to add special characters to localized strings, since localizers might not know to look for special characters, and many of them are invisible or ambiguous when viewed in a normal text editor. You can use any characters in theSpecialenum, or use<BON:unicode value='A1338'/>or&#a1338;
XML Parsing with Error Handling
If the above method encounters invalid XML, the resulting string will be the entire original string, tags and all. If you are parsing XML that is out of your control, e.g. variable content from a server, you may want to use this alternate parsing mechanism, which allows you to handle errors encountered while parsing:
let rules: [XMLStyleRule] = [
.style("strong", strongStyle),
.style("em", emStyle),
]
let xml = // some XML from a server
do {
let attrString = try NSAttributedString.composed(ofXML: xml, rules: rules)
}
catch {
// Handle errors encountered by Foundation's XMLParser,
// which is used by BonMot to parse XML.
}
Image Attachments
BonMot uses NSTextAttachment to embed images in strings. You can use BonMot’s NSAttributedString.composed(of:) API to chain images and text together in the same string:
let someImage = ... // some UIImage or NSImage
let attributedString = NSAttributedString.composed(of: [
someImage.styled(with: .baselineOffset(-4)), // shift vertically if needed
Special.noBreakSpace, // a non-breaking space between image and text
"label with icon", // raw or attributed string
])
Note the use of the
Specialtype, which gives you easy access to Unicode characters that are commonly used in UIs, such as spaces, dashes, and non-printing characters.
Outputs:
<img width=116 height=22 src="Resources/readme-images/label-with-icon.png" />If you need to wrap multiple lines of text after an image, use Tab.headIndent(...) to align the whole paragraph after the image:
let attributedString = NSAttributedString.composed(of: [
someImage.styled(with: .baselineOffset(-4.0)), // shift vertically if needed
Tab.headIndent(10), // horizontal space between image and text
"This is some text that goes on and on and spans multiple lines, and it all ends up left-aligned",
])
Outputs:
<img width=285 src="Resources/readme-images/wrapped-label-with-icon.png" />Dynamic Type
You can easily make any attributed string generated by BonMot respond to the system text size control. Simply add .adapt to any style declaration, and specify whether you want the style to scale like a .control or like .body text.
let style = StringStyle(
.adapt(.control)
// other style parts can go here as needed
)
someLabel.attributedText = "Label".styled(with: style).adapted(to: traitCollection)
If you want an attributed string to adapt to the current content size category, when setting it on a UI element, use .adapted(to: traitCollection) as in the above example.
Responding to Content Size Category Changes
If you call UIApplication.shared.enableAdaptiveContentSizeMonitor() at some point in your app setup code, BonMot will update common UI elements as the preferred content size category changes. You can opt your custom controls into automatic updating by conforming them to the AdaptableTextContainer protocol.
If you want more manual control over the adaptation process and are targeting iOS 10+, skip enabling the adaptive content size monitor, and call .adapted(to: traitCollection) inside traitCollectionDidChange(_:). iOS 10 introduced a preferredContentSizeCategory property on UITraitCollection.
Scaling Behaviors
The .control and .body behaviors both scale the same, except that when enabling the "Larger Dynamic Type" accessibility setting, .body grows unbounded. Here is a graph of the default behaviors of the system Dynamic Type styles:
Storyboard and XIB Integration
You can register global named styles, and use them in Storyboards and XIBs via IBInspectable:
let style = StringStyle(
.font(UIFont(name: "Avenir-Roman", size: 24)!),
.color(.red),
.underline(.styleSingle, .red)
)
NamedStyles.shared.registerStyle(forName: "MyHeadline", style: style)
You can then use MyHeadline in Interface Builder’s Attributes Inspector on common UIKit controls such as buttons and labels:
<img width=294 src="Resources/readme-images/bon-mot-style-attributes-inspector.png" alt="Editing the Bon Mot Styl
Related Skills
taskflow
350.8kname: taskflow description: Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layer
claude-opus-4-5-migration
110.4kMigrate prompts and code from Claude Sonnet 4.0, Sonnet 4.5, or Opus 4.1 to Opus 4.5
frontend-design
110.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
Agent Development
110.4kThis skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
