Skip to content
re-ovo edited this page Oct 28, 2026 · 2 revisions

BetterText

BetterText is a Jetpack Compose library for rendering the replies of LLM chat apps: Markdown with math, inline components and highlighted code, streamed in token by token.

It lays out text itself with Android's StaticLayout and ports KaTeX for math, so inline formulas wrap and sit on the text baseline, lines grow to fit tall content instead of overlapping, and a whole conversation can be selected and copied.

Modules

Module Artifact What it does Guide
text com.github.re-ovo.bettertext:text BetterText, a BasicText replacement with inline content, and BetterSelectionContainer BetterText
latex com.github.re-ovo.bettertext:latex A Kotlin port of KaTeX 0.19.1 (with mhchem), as composables and as inline items for BetterText LaTeX
markdown com.github.re-ovo.bettertext:markdown Streaming Markdown (GFM, math, embedded HTML) rendered with BetterText Markdown
highlight com.github.re-ovo.bettertext:highlight A pure Kotlin port of highlight.js 11.11.1, 30 languages, producing an AnnotatedString Highlight

latex and markdown both depend on text. markdown depends on neither latex nor highlight nor Material 3: the app registers formulas, code blocks and other components itself, so apps without math don't ship KaTeX.

All modules require minSdk 24.

Installation

The modules are published on JitPack. Add the repository in settings.gradle.kts:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven("https://jitpack.io")
    }
}

Then add the BOM with a release tag (such as v0.1.3) or a commit hash, and the modules you need:

dependencies {
    implementation(platform("com.github.re-ovo.bettertext:bom:<version>"))
    implementation("com.github.re-ovo.bettertext:text")
    implementation("com.github.re-ovo.bettertext:latex")
    implementation("com.github.re-ovo.bettertext:markdown")
    implementation("com.github.re-ovo.bettertext:highlight")
}

Quick start

A chat reply with math and highlighted code, selectable as a whole:

// Stable components: a top-level value, or remembered.
val chatComponents = MarkdownComponents(
    inlineMath = { formula -> LatexInline.cached(formula, context) },
    mathBlock = { formula ->
        val style = LocalMarkdownTextStyle.current
        LatexBlock(formula, fontSize = style.fontSize, color = style.color)
    },
    codeBlock = { block ->
        val style = LocalMarkdownTextStyle.current
        BetterText(
            text = rememberHighlightedCode(block.code, block.language.orEmpty()),
            modifier = Modifier.horizontalScroll(rememberScrollState()),
            style = style.copy(fontFamily = FontFamily.Monospace, fontFeatureSettings = CODE_FONT_FEATURES),
        )
    },
)

@Composable
fun Reply(text: String, generating: Boolean) {
    BetterSelectionContainer {
        Markdown(
            content = text,
            style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface),
            components = chatComponents,
            streaming = generating,
        )
    }
}

The default highlight palette is Atom One Dark; see Highlight to pick colors for a light background.

Guides

  • BetterText: styled text, links, inline items (drawn and composable), selection across texts and lazy lists.
  • LaTeX: standalone formulas, display blocks, inline formulas in BetterText, settings, macros and chemistry.
  • Markdown: rendering, theming, registering components, streaming, and parsing ahead of time.
  • Highlight: highlighting code, color palettes, supported languages.

Why not BasicText?

Two open Compose issues prevent building on BasicText and SelectionContainer:

  • 427657659: with lineHeight set, every line gets the same height, so a tall inline formula or a span with a larger font overlaps the lines next to it. Material 3 typography always sets lineHeight.
  • 233102278: SelectionRegistrar is internal, so a custom text layout can't take part in SelectionContainer.

BetterText works around both with its own layout and selection, and will switch to the built-in APIs once they are fixed.

Demo app

The :app module is a gallery with one tab per module: LaTeX samples, a line-wrapping demo and a live editor; rich text; Markdown, including a long conversation to select across; and code highlighting. Install it with:

./gradlew :app:installDebug

gallery/markdown/MaterialMarkdown.kt is a complete example of registering Markdown components with Material 3.

Clone this wiki locally