Skip to content

Repository files navigation

Material Symbols

Material Symbols for Compose Multiplatform

Google's Material Symbols, packaged as a Compose library. Drop in any of the 3905 icons by name — no downloading SVGs one by one, no per-platform asset folders.

Maven Central Kotlin Kotlin Multiplatform Compose Multiplatform License

Live Showcase
Every icon, filterable by weight, grade, optical size, style and category — running in the browser


The same app, everywhere

Android, Desktop and Web, running the exact same Kotlin — light and dark, side by side.

Light Dark
Desktop
JVM, native window
Desktop, light theme Desktop, dark theme
Web
wasmJs, in the browser
Web, light theme Web, dark theme
Android Android, light theme Android, dark theme
More screens — the filter panel and the per-icon preview, on every platform

Filters — fill, weight, grade, optical size, style and category, all live:

Desktop filters, light theme Desktop filters, dark theme Android filters, light theme Android filters, dark theme

Preview — size, a hex-validated color field and a Google-Fonts-style color picker:

Desktop preview, light theme Desktop preview, dark theme Android preview, light theme Android preview, dark theme

Web has the same two panels — see them live instead of a screenshot.

The problem

Google's Material Symbols aren't a fixed set of vector drawables — they're variable fonts. A single glyph carries 4 tunable axes (fill, weight, grade, optical size), and Google ships 3 separate font families (Outlined, Rounded, Sharp) on top of that. Wiring this up by hand in Compose means juggling FontFamilys, FontVariation.Settings, raw icon-name strings that silently render blank on a typo, and — on web specifically — a font that loads asynchronously, which without care means a flash of unstyled text before the glyph appears.

This library collapses all of that into one composable and one generated, compile-time-checked catalog.

Usage

MaterialSymbolsRenderingScope {
    MaterialSymbol(
        iconName = MaterialSymbols.SETTINGS,
        contentDescription = "Settings",
        style = MaterialSymbolStyle.ROUNDED,
        filled = true
    )
}
  • MaterialSymbolsRenderingScope loads the 3 icon fonts once and provides them to every MaterialSymbol composed underneath it — wrap the root of your UI tree in it, once.
  • MaterialSymbols is generated from Google's live icon catalog — MaterialSymbols.SETTINGS, MaterialSymbols.HOME, and so on for every one of the 3905 icons Material Symbols publishes across all 3 families. Typo a name and the build fails, instead of silently rendering a blank glyph at runtime.

Configuring how icons look

Weight, grade and optical size are real OpenType variable-font axes (wght, GRAD, opsz), not cosmetic props — they're the same mechanism variable text fonts use, applied to icons instead of letterforms. MaterialSymbolFontsConfig exposes them:

MaterialSymbolsRenderingScope(
    config = MaterialSymbolFontsConfig(
        weight = FontWeight(500),  // 100 (thin) .. 700 (bold) — stroke thickness
        grade = 200,               // -25 .. 200   — fine weight trim that doesn't reflow layout
        opticalSize = 24.sp        // 20 .. 48      — redraws detail for the size it's shown at
    )
) {
    MaterialSymbol(
        iconName = MaterialSymbols.FAVORITE,
        contentDescription = "Favorite",
        filled = true
    )
}
Axis Range What it actually does
weight 100 – 700 Stroke thickness, like a text font's weight.
grade -25 – 200 A weight trim that keeps the glyph's box size fixed — use it to compensate contrast (e.g. slightly bolder on a dark background) without anything else in the layout shifting.
optical size 20 – 48 The glyph is redrawn for the size it renders at, not just scaled — small icons stay legible, large icons pick up detail a naive scale-up would blur.
filled on / off Toggles the FILL axis — outlined vs. solid glyph.
style Outlined / Rounded / Sharp Which of the 3 font families to draw from — this one is a separate font, not an axis.

Play with all 5 live in the showcase — the sliders there map 1:1 to this config.

Install

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        mavenCentral()
    }
}
// build.gradle.kts
dependencies {
    implementation("dev.catbit:material-symbols:1.0.1")
}

Targets Android, iOS (iosArm64/iosSimulatorArm64), JVM/Desktop and Web (wasmJs) — pick whichever of those your Compose Multiplatform module already targets, nothing extra to configure per-platform.

Under the hood

Two things in this repo that go a bit further than "wrap a font in a composable":

  • The icon catalog is generated, not hand-maintained. A custom Gradle plugin (build-logic/material-symbols-codegen) fetches Google's live icon metadata endpoint at build time, filters it down to the exactly 3905 icons available in all 3 font families, and emits MaterialSymbols as plain const val Strings (an enum blew past the JVM's 64KB-per-method bytecode limit at this size). A bundled snapshot is the fallback if the network call fails, so the build never breaks offline.
  • Font loading is a single, platform-agnostic path. Font() returns synchronously on every target, including Wasm/JS, where the returned FontFamily resolves its actual typeface asynchronously behind the scenes — Compose's own FontFamily.Resolver re-triggers the affected draw once it's ready, so no per-platform handling is needed. An earlier version used Compose's preloadFont() on web to track loading progress explicitly and avoid a brief flash of fallback text, but once these fonts moved into this published library that API's state never resolved past null on web, freezing every glyph on the fallback font. Plain Font() trades that one-time flash for correctness.

Running the sample

material-symbols-sample is the icon browser behind the live showcase — search, filter by weight/grade/optical size/style/category, and preview any icon, built as a small MVI app (State/Event/Effect + a ViewModel) on top of the library.

# Desktop
./gradlew :material-symbols-sample:desktopApp:run

# Web (wasmJs)
./gradlew :material-symbols-sample:webApp:wasmJsBrowserDevelopmentRun

# Android — open the project in Android Studio and run the androidApp configuration, or:
./gradlew :material-symbols-sample:androidApp:installDebug

License

Apache License 2.0 — see LICENSE.

About

A Kotlin Multiplatform Compose wrapper library for Google's Material Symbols variable icon fonts.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages