Getting started — KMP
Add Justin to a Kotlin Multiplatform app. Targets Android (API 24+) and iOS (16.0+).
Install
Justin is published to a private Azure DevOps Artifacts Maven feed as com.davidhorn.justin:shared. Point Gradle at the feed in your consumer
app’s settings.gradle.kts:
dependencyResolutionManagement {
repositories {
maven {
name = "DavidhornJustin"
url = uri("https://pkgs.dev.azure.com/<org>/<project>/_packaging/<feed>/maven/v1")
credentials {
username = "DavidhornJustin"
password = providers.gradleProperty("adoToken").orNull
?: error("Missing `adoToken` — add it to ~/.gradle/gradle.properties")
}
content {
includeGroup("com.davidhorn.justin")
}
}
google()
mavenCentral()
}
} The private feed is listed first and scoped to the com.davidhorn.justin group via content { includeGroup(...) } so Gradle never tries to resolve
public coordinates from it (and vice versa) — defence against
dependency-confusion squatting.
Put a Personal Access Token (Packaging: Read) in ~/.gradle/gradle.properties:
adoToken=<your-pat> The username can be any non-empty string — ADO Artifacts uses HTTP Basic
auth with the PAT as the password. (CI publishes to the same feed via Authorization: Bearer ... with an AAD OIDC access token; see .github/workflows/publish-kmp.yml. The feed accepts both schemes —
Basic for PATs, Bearer for AAD tokens.)
Then add the dependency in your consumer app’s composeApp/build.gradle.kts:
commonMain.dependencies {
implementation("com.davidhorn.justin:shared:<version>")
} Replace <version> with the latest version from the feed (current at
the time of writing: 0.2.2). New releases are tagged kmp-v* in the
source repo and published to the feed.
The artifact ships both Android (API 24+) and iOS (iosX64, iosArm64, iosSimulatorArm64) targets.
Wrap your app in AppTheme
AppTheme wraps Material 3’s MaterialTheme and injects Justin’s colors,
typography, and corner radii. Do this once at the root of your composable
tree.
import androidx.compose.runtime.Composable
import androidx.compose.foundation.isSystemInDarkTheme
import com.davidhorn.justin.theme.AppTheme
@Composable
fun App() {
AppTheme(darkTheme = isSystemInDarkTheme()) {
// your app content
}
} Everything below AppTheme has access to Justin tokens via MaterialTheme.colorScheme, AppSpacing, AppRadius, and AppTextStyles.
Use a component
import com.davidhorn.justin.components.button.DhButton
@Composable
fun Submit(onSubmit: () -> Unit) {
DhButton(text = "Submit", onClick = onSubmit)
} All Justin components are prefixed Dh, accept a modifier: Modifier = Modifier first parameter, and have sensible defaults so you rarely need more than one
or two arguments.
Themes, tokens, and icons
- Tokens: see Foundations for the full vocabulary.
Access them in code as
MaterialTheme.colorScheme.primary,AppSpacing.spacing4,AppRadius.md,AppTextStyles.bodyLarge. - Icons: the Davidhorn icon set is exposed via
Res.drawable.ic_*. Prefer these over Material Icons. - Dark mode: handled entirely by
AppTheme; component code never hardcodes color.
See it running
The apps/kmp-demo/ app is a minimal host showcasing every component as it
lands:
bun run kmp:up # builds + launches both Android and iOS targets
bun run kmp:smoke # compile-only smoke test for the shared lib + demo What’s next
- Browse Justin for KMP for every component.
- Read the design philosophy.
- See Foundations for the token vocabulary.