
HTML Converter for Compose Multiplatform
This library provides a simple API to convert HTML to Compose's AnnotatedString, including styling and paragraphs.
It can also be used to convert HTML to unstyled text.
It can be considered as a multiplatform replacement for Android's Html.fromHtml() API with support for more tags and better performance.
| Platform | Supported |
|---|
| JVM (Android & Desktop) | ✅ |
| Native (iOS & macOS) | ✅ |
| Web (JS & WASM) | ✅ |
Running the sample app
Android
- Open the project in Android Studio and click ▶ androidApp (run configuration) to build and run.
- OR from the command line:
./gradlew androidApp:installDebug to install to a connected device.
Desktop (JVM)
- From the command line:
./gradlew sample:run
iOS
- Open
iosApp/Sample.xcodeproj in XCode.
- Click ▶ Sample to build and run.
Desktop (JVM) and iOS with Fleet or Android Studio
If you have a recent version of Fleet installed, the platform targets should automatically be
detected opening the project. Click ▶ to select a target device, build and run.
Alternatively, you can use the Kotlin Multiplatform Plugin
with Android Studio. Follow the Run your application
instructions to select or set up a run configuration for each target.
Download

Add the dependency to your module's build.gradle or build.gradle.kts file:
dependencies {
implementation("be.digitalia.compose.htmlconverter:htmlconverter:1.1.1")
}
For Kotlin Multiplatform projects:
sourceSets {
val commonMain by getting {
dependencies {
implementation("be.digitalia.compose.htmlconverter:htmlconverter:1.1.1")
}
}
}
Usage
To display styled HTML in a Text composable:
Text(text = remember(html) { htmlToAnnotatedString(html) })
If called from inside a @Composable function, in most cases it is recommended to use remember() to cache the result of the conversion, to avoid recomputation on each recomposition.
To convert HTML to unstyled text:
val rawText = htmlToString(html)
Both functions take an optional boolean argument. When set to , all paragraphs will be separated by a single line break instead of two, as it is normally the case for the tags: , , , , , , , , , , , . The default value is .
Custom styling
The htmlToAnnotatedString() function takes an optional style argument of type HtmlStyle which allows to customize styling. The currently provided options are:
textLinkStyles: Optional collection of styles for hyperlinks (content of tags). Default is a simple underline. When set to , hyperlinks will not be styled, which can be useful when they are not clickable (see following section).
For example, here is how to style hyperlinks to use the theme's primary color with no underline:
val linkColor = MaterialTheme.colors.primary
val convertedText = remember(html, linkColor) {
htmlToAnnotatedString(
html,
style = HtmlStyle(
textLinkStyles = TextLinkStyles(
style = SpanStyle(color = linkColor)
)
)
)
}
Text(text = convertedText)
Colored text
Support for CSS colored text is disabled by default in order to guarantee good contrast and respect of the Compose theme colors for unsanitized HTML input. To enable CSS colored text, set the isTextColorEnabled option to true in the HtmlStyle argument:
val convertedText = remember(html) {
htmlToAnnotatedString(
html,
style = HtmlStyle(isTextColorEnabled = true)
)
}
Text(text = convertedText)
Example of supported CSS color styling:
<span style="color: darkred; background-color: #FFFF0099">Red over translucent yellow</span>
Handling hyperlink clicks
Hyperlinks (content of a tags) will be annotated with LinkAnnotation.Url. When clicked, they will automatically be handled by the default UriHandler which will open them using the platform's default browser.
To override that behavior or disable click handling completely, specify a custom linkInteractionListener argument:
val convertedText = remember(html) {
htmlToAnnotatedString(
html,
linkInteractionListener = { link ->
if (link is LinkAnnotation.Url) {
navigateTo(link.url)
}
}
)
}
Text(text = convertedText)
Bug when showing hyperlinks in combination with maxLines in Compose 1.7
Compose UI versions 1.7.0 to 1.7.6 have a bug which triggers a crash when a Text composable using maxLines is displaying an AnnotatedString containing a LinkAnnotation inside a paragraph.
This library is vulnerable to that bug because it uses both LinkAnnotation to display hyperlinks and paragraphs to handle text indentation.
It is recommended to upgrade to Compose 1.8.0 or more recent to avoid the issue.
If you are still using Compose 1.7.x along with an older version of this library, as a workaround, you can disable indentation support in Text composables which require the usage of maxLines:
val convertedText = remember(html) {
htmlToAnnotatedString(
html,
style = HtmlStyle(indentUnit = TextUnit.Unspecified)
)
}
Text(
text = convertedText,
maxLines = 3
)
See related bug reports 374115892, 372390054 on the Google issue tracker.
Custom parsing
The htmlToAnnotatedString() and htmlToString() functions provide an overload that accepts a HTMLParser first argument in place of a String.
HTMLParser is an interface that you may implement to provide your own parser, in case the HTML is not directly available as a String (for example as a character stream or encoded using a binary format).
The default implementation uses the KtXml multiplatform XML parser library combined with extra code to handle HTML entities and invalid HTML.
Supported HTML tags
The following tags are skipped, along with their content: script, head, table, form, fieldset.
Others tags are ignored and replaced by their content, if any.
All HTML entities appearing in the text will be properly decoded as well.
Used libraries
- Compose Multiplatform by JetBrains s.r.o.
- KtXml by Stefan Haustein
What to expect from future versions
- Unit tests
- Support for displaying images as inline content
License
Copyright (C) 2023-2025 Christophe Beyls and contributors
Licensed under the Apache License, Version 2.0 (the );
you may use except compliance the License.
You may obtain a copy of the License at
https:
Unless applicable law agreed to writing, software
distributed under the License distributed an BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
See the License the specific language governing permissions
limitations under the License.