Skip to content
Demo: the plan picker morphs between plans, predictive back returns to the gallery, and the limited-time offer sheet counts down

Material 3 Expressive paywalls for Jetpack Compose

Six subscription screens with springy motion, shape morphing and the Google Play disclosures already in place. Pass your plans, get callbacks, keep your own billing.

GitHub stars JitPack version material3 1.5.0-beta02 MIT license

Add one line to your app module (JitPack, minSdk 26):

implementation("com.github.sanikantv:m3-expressive-paywall:v1.0.0")

material3 gives you parts, not a paywall

Compose has buttons, cards and sheets. A paywall also needs plan selection, a trial explanation, the auto-renew text and a restore path. These templates put all of that on one screen, already wired together.

Expressive, without the research

ButtonGroup, MaterialShapes morphing, LinearWavyProgressIndicator and spring motion are new in material3 1.5. The paywalls use them, so you see working examples instead of API reference pages.

Disclosures come with every screen

The price, billing period, trial terms and auto-renew line sit next to the button, which is what Google Play’s subscription policy asks for. Restore, Terms and Privacy links are in the same footer.

No billing lock-in

Each paywall takes a list of PaywallPlans and three callbacks. Connect it to Google Play Billing, RevenueCat or your own backend. No API keys, no dashboard.

Add JitPack and the dependency, then show a paywall:

UpgradeScreen.kt
@Composable
fun UpgradeScreen(onBuy: (planId: String) -> Unit, onRestore: () -> Unit, onDismiss: () -> Unit) {
ClassicPlansPaywall(
plans = listOf(
PaywallPlan(id = "monthly", title = "Monthly", price = "$4.99", period = "per month"),
PaywallPlan(
id = "yearly", title = "Yearly", price = "$29.99", period = "per year",
trialText = "7-day free trial", badge = "Best value",
),
),
features = listOf(
PaywallFeature("Unlimited projects", icon = PaywallIcons.Sparkle),
PaywallFeature("No ads", icon = PaywallIcons.Block),
),
legal = PaywallLegal(termsUrl = "https://example.com/terms", privacyUrl = "https://example.com/privacy"),
onPurchase = { plan -> onBuy(plan.id) },
onRestore = onRestore,
onClose = onDismiss,
)
}

Prices are strings, so you pass the localized price from the store. Every snippet on this site is compiled against the library on each build.

See all guides

  • Does it work with stable material3? It needs 1.5.0-beta02 or newer, because the Expressive APIs it uses are not in 1.4. Read the FAQ
  • Do I need RevenueCat or another SDK? No. Any billing source works, as long as you can turn its products into PaywallPlans. Read the FAQ
  • Does it support dark mode and dynamic color? Yes. Every color comes from your MaterialTheme. Read the FAQ
  • Is it free for commercial apps? Yes, it is MIT licensed. Read the FAQ
implementation("com.github.sanikantv:m3-expressive-paywall:v1.0.0")
Get started Star on GitHub

Built by Sanikant. Set in Google Sans Flex (SIL Open Font License). Not affiliated with Google.