ParagraphStyle is a set of formatting parameters that define the visual appearance of a text paragraph. In mobile development, ParagraphStyle includes alignment (left, center, right, justified), line spacing, first-line indentation, paragraph spacing, and text direction. According to the Material Design Typography Guidelines (2025), consistent use of ParagraphStyle across all app screens ensures a uniform typographic rhythm and reduces visual noise when switching between sections.
Key Takeaways
ParagraphStyle (paragraph style) is a set of formatting rules applicable to one or more paragraphs of text. Unlike character styles (bold, italic, font-size) that operate at the level of individual characters, ParagraphStyle manages macro-properties: text placement within the container, spacing between lines and paragraphs, writing direction, and hyphenation rules.
Historically, the concept of ParagraphStyle came from desktop publishing systems (QuarkXPress, PageMaker, InDesign), where each paragraph had its own set of parameters. In mobile development, this approach has transformed into software APIs: NSMutableParagraphStyle on iOS, ParagraphStyle and LineBreakStyle classes in Android, and parameters like textAlign and textHeightBehavior in Flutter. All of them serve one purpose — managing paragraph formatting through code, without being tied to a specific text engine.
According to Bringhurst — The Elements of Typographic Style (2024), proper ParagraphStyle configuration is the foundation of interface typographic culture. If the typeface and font size define the "voice" of the text, the paragraph style defines its "breathing": rhythm, pauses, and accents. In mobile interfaces where space is limited, proper ParagraphStyle allows fitting more information without losing readability.
ParagraphStyle includes several groups of properties, each responsible for a specific aspect of formatting. Let's look at the key parameters available in mobile APIs. Alignment is the main parameter that determines the horizontal position of text. On iOS, NSTextAlignment is available, on Android — Layout.Alignment, in Flutter — TextAlign.
| Property | iOS (NSMutableParagraphStyle) | Android (Compose) | Flutter (TextStyle) |
|---|---|---|---|
| Alignment | alignment | textAlign | textAlign |
| Left indent | headIndent | textIndent (in Compose) | — |
| Right indent | tailIndent | — | — |
| First-line indent | firstLineHeadIndent | textIndent (firstLine) | — |
| Paragraph spacing | paragraphSpacing | paragraphStyle (lineHeight) | — |
| Spacing before paragraph | paragraphSpacingBefore | — | — |
| Text direction | baseWritingDirection | TextDirection (Compose) | textDirection |
| Line break mode | lineBreakMode | overflow (TextStyle) | overflow (TextStyle) |
It is important to understand that not all properties are available on all platforms to the same extent. For example, first-line indent (firstLineHeadIndent) is fully supported only on iOS through NSMutableParagraphStyle. In Android, SpannableString with LeadingMarginSpan is used for this purpose, and in Flutter — RichText with custom TextSpan and padding. In cross-platform development (Flutter, React Native), some ParagraphStyle properties have to be emulated through containers or Spacer.
In iOS, the main tool for working with paragraph style is the NSMutableParagraphStyle class — a subclass of NSParagraphStyle. It provides a complete set of properties for managing paragraph formatting and is applied through NSAttributedString. NSMutableParagraphStyle is used not only in UILabel and UITextView, but also in Core Text via CTParagraphStyle.
// iOS: full ParagraphStyle configuration
let paragraphStyle = NSMutableParagraphStyle()
// Alignment
paragraphStyle.alignment = .justified // justified
// Indentation
paragraphStyle.headIndent = 16 // left indent
paragraphStyle.tailIndent = -16 // right indent (negative)
paragraphStyle.firstLineHeadIndent = 32 // first line indent
// Paragraph spacing
paragraphStyle.paragraphSpacing = 12 // after paragraph
paragraphStyle.paragraphSpacingBefore = 8 // before paragraph
// Line height
paragraphStyle.minimumLineHeight = 24
paragraphStyle.maximumLineHeight = 24
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 16),
.paragraphStyle: paragraphStyle
]
let label = UILabel()
label.attributedText = NSAttributedString(
string: "This paragraph has a custom ParagraphStyle.\n\nSecond paragraph with 12 pt spacing.",
attributes: attributes
)
In SwiftUI, there is no direct counterpart to NSMutableParagraphStyle — instead, a combination of modifiers is used: .multilineTextAlignment() for alignment, .lineSpacing() for line spacing. For first-line indentation and paragraph spacing in SwiftUI, you have to use Text + padding or AttributedString (available since iOS 15+), which supports NSParagraphStyle through attributes.
// SwiftUI: AttributedString with ParagraphStyle (iOS 15+)
var attributed: AttributedString = """
This is the first paragraph with indent.
This is the second paragraph with 12 pt spacing.
"""
let ps = NSMutableParagraphStyle()
ps.paragraphSpacing = 12
ps.firstLineHeadIndent = 20
let container = AttributeContainer()
.paragraphStyle(ps)
attributed.mergeAttributes(container)
Text(attributed)
.font(.body)
According to Apple — Text Programming Guide (2025), NSMutableParagraphStyle supports up to 14 different formatting parameters. It is important to remember that tailIndent is set as a negative value relative to the container width, while headIndent is positive. This non-obvious behavior often causes errors among novice iOS developers.
In Android, the approach to ParagraphStyle differs between the View system and Jetpack Compose. In the classic View system, paragraph parameters are set through TextView XML attributes (android:textAlignment, android:lineSpacingExtra) or through SpannableString with implementations of the ParagraphStyle interface from the android.text.style package.
// Android View system: ParagraphStyle via SpannableString
val text = "First paragraph with settings.\n\nSecond paragraph."
val spannable = SpannableString(text)
// Justified alignment
spannable.setSpan(
AlignmentSpan.Standard(Layout.Alignment.ALIGN_NORMAL),
0, text.length,
Spannable.SPAN_PARAGRAPH
)
// First line indent via LeadingMarginSpan
spannable.setSpan(
LeadingMarginSpan.Standard(0, 20),
0, text.indexOf('\n'),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = spannable
In Jetpack Compose, ParagraphStyle is managed through TextStyle parameters. textAlign is used for alignment, lineHeight (in sp or em) for line spacing, and textIndent for first-line indentation. Compose also supports LineBreak for fine-grained control of line breaks (Simple, Heading, Paragraph), which is especially useful for multilingual text.
// Jetpack Compose: ParagraphStyle via TextStyle
Text(
text = "First paragraph with custom style.
Second paragraph with indent.",
style = TextStyle(
textAlign = TextAlign.Justify,
lineHeight = 24.sp,
textIndent = TextIndent(firstLine = 16.sp),
lineBreak = LineBreak.Paragraph
)
)
According to Android Developers — Compose ParagraphStyle (2025), when using TextIndent in Compose, it is important to set firstLine in sp (not in dp), as the first-line indent should scale with the text. If the indent is set in dp, when the font size increases, the first-line indent will become visually smaller, disrupting typographic proportions.
In Flutter, the ParagraphStyle paradigm is not separated into a dedicated class — instead, a combination of TextStyle parameters and additional classes is used. Alignment is set through TextAlign, line spacing through height (unitless), and TextHeightBehavior controls visible indents of the first and last lines.
// Flutter: ParagraphStyle via Text parameters
Text(
'Styled paragraph in Flutter.
Second paragraph with indents.',
style: TextStyle(
fontSize: 16,
height: 1.5,
leadingDistribution: TextLeadingDistribution.proportional,
),
textAlign: TextAlign.justify,
textHeightBehavior: TextHeightBehavior(
applyHeightToFirstAscent: true,
applyHeightToLastDescent: true,
),
)
For more complex scenarios, Flutter offers the use of RichText with custom TextSpan and WidgetSpan. This allows combining text with inline widgets (icons, images) in a single paragraph while maintaining uniform formatting rules. However, this approach requires manual control of line breaks and does not support automatic hyphenation.
// Flutter: RichText with custom paragraph formatting
RichText(
text: TextSpan(
style: TextStyle(fontSize: 16, height: 1.5),
children: [
TextSpan(text: 'First paragraph with icon '),
WidgetSpan(
child: Icon(Icons.star, size: 16),
alignment: PlaceholderAlignment.middle,
),
TextSpan(text: '\n\nSecond paragraph after spacing.'),
],
),
)
According to Flutter Documentation — Text Widget (2025), TextHeightBehavior is a key parameter for correct ParagraphStyle display in Flutter. If applyHeightToFirstAscent is set to false, the top indent of the first line is ignored, and the text "sticks" to the top border of the container. This is useful for headings but undesirable for body text.
ParagraphStyle must take into account the writing direction for each locale. In mobile apps with support for Arabic, Hebrew, or Urdu (right-to-left writing, RTL), indentation and alignment should mirror accordingly. iOS supports automatic switching through baseWritingDirection = .natural, Android through TextDirection, Flutter through textDirection.
Alignment configuration for RTL locales requires special attention. If in LTR mode headIndent sets the left indent, in RTL it automatically becomes the right indent. However, not all implementations correctly handle this transition when alignment is explicitly specified (NSTextAlignment.left instead of NSTextAlignment.natural). According to Google Internationalization Guide (2025), for multilingual apps always use natural alignment and check indents in both layouts.
// Android: RTL support in ParagraphStyle
val textView = TextView(context)
// Enable automatic RTL support
textView.textDirection = View.TEXT_DIRECTION_LOCALE
textView.textAlignment = View.TEXT_ALIGNMENT_TEXT_START
// Spannable with RTL support
val spannable = SpannableString(arabicText)
spannable.setSpan(
AlignmentSpan.Standard(Layout.Alignment.ALIGN_NORMAL),
0, arabicText.length,
Spannable.SPAN_PARAGRAPH
)
Regional features also include different rules for first-line indentation. In European typography, the first-line indent (firstLineHeadIndent) is a standard for paragraph separation. In Japanese and Chinese typography, first-line indentation is also used, but its size may differ (usually 1 em versus 1.5 em in the European tradition). For Arabic typography, first-line indentation is rare — paragraphs are more often separated by vertical spacing.
The most common mistake is using ParagraphStyle only through global text field attributes without considering different formatting for individual paragraphs. In a UILabel or TextView with multiple paragraphs separated by , ParagraphStyle is applied to the entire text uniformly unless attributed text with different styles for each paragraph is used.
According to UX Collective — Common Typography Bugs in Mobile Apps (2025), problems with ParagraphStyle make up 18% of all typographic bugs in mobile applications. The most critical cases are when incorrect paragraphSpacing leads to text overlap on different lines or when RTL locales display with LTR indentation. It is recommended to create unit tests for each ParagraphStyle parameter with test strings in different languages.
Frequently Asked Questions
NSMutableParagraphStyle manages paragraph formatting (alignment, indentation, line spacing), while TextStyle manages character attributes (font, color, size). Both are applied through NSAttributedString. ParagraphStyle operates at the paragraph level, TextStyle at the character level. Multiple TextStyle instances can exist in one paragraph, but only one ParagraphStyle per paragraph.
In Jetpack Compose, first-line indent is set through TextIndent inside TextStyle: TextStyle(textIndent = TextIndent(firstLine = 16.sp)). The value should be in sp so that it scales with the text. For multiline text, the indent applies to each line that starts a new paragraph ( character). For the first paragraph, the indent is always applied.
In SwiftUI, there is no direct counterpart to paragraphSpacing. For paragraph spacing, use AttributedString (iOS 15+) with NSMutableParagraphStyle and the paragraphSpacing property. Or separate paragraphs through Text("...\n\n...") and add .padding() between text blocks. For complex typographic scenarios, use NSAttributedString through UILabelRepresentable.
TextView in Android does not have a paragraphSpacing property — it is set through SpannableString with LeadingMarginSpan or through a custom LineBackgroundSpan. For line spacing, use setLineSpacing; for paragraph spacing, separate paragraphs with an additional or use a composite container of multiple TextView instances.
Proper ParagraphStyle improves accessibility: sufficient paragraphSpacing helps users with cognitive impairments better distinguish paragraph boundaries. WCAG 2.2 recommends visual separation of paragraphs (indentation or spacing). ParagraphSpacing should not be less than 1.5 × line-height. For RTL locales, verify that indentation is mirrored correctly.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also