Getting started — KMP
Add Justin to a Kotlin Multiplatform app targeting Android (API 24+) and
iOS (16.0+). Five steps: point Gradle at the feed, add the dependency,
wrap your app in AppTheme, render a component, verify.
1. Point Gradle at the feed
Justin is published to a private Azure DevOps Artifacts Maven feed as com.davidhorn.justin:shared. Add 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 concrete <org>/<project>/<feed> values are on the internal Justin Package Feeds page. The private feed is listed first and scoped to the com.davidhorn.justin group via content { includeGroup(...) }, so Gradle
never asks it for public coordinates. Note the scoping is one-way: for the
full dependency-confusion defence — a guarantee that google() and mavenCentral() never serve the com.davidhorn.justin group — declare the
feed inside an exclusiveContent { } block instead.
Then 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.)
2. 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. 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.
3. 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.
4. Use a component
import com.davidhorn.justin.components.button.DhPrimaryButton
@Composable
fun Submit(onSubmit: () -> Unit) {
DhPrimaryButton(text = "Submit", onClick = onSubmit)
} All Justin components are prefixed Dh, take their content first (here text and onClick) with an optional modifier after, and have sensible
defaults so you rarely need more than one or two arguments. Buttons are
split by role — DhPrimaryButton, DhSecondaryButton, DhTertiaryButton, DhErrorButton — with the rendering surface (filled, tonal, outlined, text)
as a surface parameter.
5. Verify
Sync Gradle and run the app on either target:
- A failed sync with a
401/Could not resolve com.davidhorn.justin:sharederror means the feed credentials from step 1 aren’t in place. - The button renders with Justin’s primary color and rounded corners. A
plain Material button means the composable isn’t inside
AppTheme.
That’s the full setup. Everything below is optional.
Going further
Themes, tokens, and icons
- Tokens: see Foundations for the full vocabulary.
Access them in code as
MaterialTheme.colorScheme.primary,AppSpacing.spacing4,AppRadius.md,AppTextStyles.textNormal(). - Icons: the Justin Lucide pack is exposed via
DhIcon— preferDhIcon(DhIcons.<Name>, contentDescription = "…")for the curated surface, orDhIcon(Lucide.<PascalCase>, …)for any of the ~1,695 Lucide glyphs that aren’t curated. See the icon docs for the live catalog. - Dark mode: handled entirely by
AppTheme; component code never hardcodes color.
See it running
The apps/kmp-demo/ app in the Justin repo 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.