# Seamless Typography in Action: Implementing Custom Fonts in Compose Multiplatform for Android, iOS, and Desktop

I recently needed to use a custom font for my Compose multiplatform project [Sync Sphere](https://github.com/j-roskopf/SyncSphere) and I thought I'd share my solution in case it was helpful to anyone else.

First, we need to add a font to `commonMain/resources/font`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700054612021/e631f444-b386-4f8a-8ea7-8849d0d3f27d.png align="center")

For this example, I have gone with `dancingscript_regular.ttf` The resource name should follow android resource conventions and be all lowercase with underscores if necessary.

I am adding this font to part of an app theme, but this part is optional.

```kotlin
@Composable
fun AppTheme(
    content:
    @Composable()
        () -> Unit,
) {

    MaterialTheme(
        content = content,
        typography = appTypography,
    )
}
```

`appTypography` is defined as a variable that creates a custom typography.

```kotlin
val appTypography : Typography
    @Composable
    get() {
        return Typography(
            defaultFontFamily = dancingScriptRegular
        )
    }
```

`dancingScriptRegular` is a custom font that we use expect/actual to get a different platform implementation.

```kotlin
val dancingScriptRegular: FontFamily
    @Composable
    get() {
        return FontFamily(
            font(
                "DancingScript",
                "dancingscript_regular",
                FontWeight.Normal,
                FontStyle.Normal,
            ),
        )
    }
```

The `font` function is the main bread and butter of our solution here. This is where we use expect/actual to give Android / iOS / Desktop implementations.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700054883905/80b98fae-0078-45e2-be4f-dce76a77540c.png align="center")

Under `shared/src/commonMain/kotlin`, I created a `FontResource.kt` class that looks like

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight

@Composable
expect fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font
```

Under `androidMain`, `iosMain`, and `desktopMain` we will define our actual implemetations.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700054960853/1e33b086-fe5b-4915-947e-1d584cb56aba.png align="center")

`androidMain`:

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight

@Composable
actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font {
    val context = LocalContext.current
    val id = context.resources.getIdentifier(res, "font", context.packageName)
    return Font(id, weight, style)
}
```

`iosMain`:

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import kotlinx.coroutines.runBlocking
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.resource

private val cache: MutableMap<String, Font> = mutableMapOf()

@OptIn(ExperimentalResourceApi::class)
@Composable
actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font {
    // use a cache to store fonts and re-use 
    return cache.getOrPut(res) {
        val byteArray = runBlocking {
            resource("font/$res.ttf").readBytes()
        }
        androidx.compose.ui.text.platform.Font(res, byteArray, weight, style)
    }
}
```

`desktopMain`:

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight

@Composable
actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font =
    androidx.compose.ui.text.platform.Font("font/$res.ttf", weight, style)
```

Lastly, in your shared module `build.gradle.kts` under the `android` block, you'll have to add this line so that the Android app will know to use `commonMain/resources` and a `res` src directory.

```kotlin
    android {
    compileSdk = (findProperty("android.compileSdk") as String).toInt()
    namespace = "com.myapplication.common"

    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    sourceSets["main"].res.srcDirs("src/androidMain/res")
    
    // This is the line to add
    sourceSets["main"].res.srcDirs("src/commonMain/resources") // <=== This is the line to add
    // This is the line to add

    sourceSets["main"].resources.srcDirs("src/commonMain/resources")

    defaultConfig {
        minSdk = (findProperty("android.minSdk") as String).toInt()
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlin {
        jvmToolchain(17)
    }
}
```

And then you should be able to run your project on iOS, Android, and Desktop using a common font.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700055274966/4b32c973-de11-4c47-8264-68b3a8bf4624.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700055287555/061007e4-6e27-4fd0-ad21-1d924187d5db.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700055295920/4ffc2dc2-3e9a-48a3-ac22-bd976d67d29e.png align="center")

# CocoaPods

A finishing note about CocoaPods. When I was writing this article, I was using the most up-to-date version of the [Compose Multiplatform template](https://github.com/JetBrains/compose-multiplatform-template) on Github from JetBrains as a demo. When I was adding this to my project Sync Sphere, I had to do some additional work to get fonts working on iOS. After doing all of the steps listed above, I also had to run `./gradlew :shared:podInstall` to get the fonts copied over to iOS, otherwise I was running into a resource not found exception.

# Conclusion

I hope these series of code snippets can prove helpful to someone implementing custom typography in their Compose Multiplatform app.
