Skip to content

Classic plans paywall for Jetpack Compose

ClassicPlansPaywall is design 1 of M3 Expressive Paywall. A familiar layout that works for most apps: an animated hero built from MaterialShapes, the plan cards right under the title so prices are visible without scrolling, then your feature list.

Classic plans paywall, light theme
Light
Classic plans paywall, dark theme
Dark
implementation("com.github.sanikantv:m3-expressive-paywall:v1.0.0")
  • You sell two or three plans, such as monthly, yearly and lifetime.
  • You want the safest, most familiar paywall to start with.
ClassicPlansPaywall(
plans = Sample.plans,
features = Sample.features,
legal = Sample.legal,
onPurchase = { plan -> buy(plan) },
onRestore = restore,
onClose = close,
)
ClassicPlansPaywall(
plans = Sample.plans,
features = Sample.features,
legal = Sample.legal,
onPurchase = buy,
onRestore = restore,
onClose = close,
title = "Unlock Lumen Pro",
subtitle = "Everything you need to think clearly.",
initialPlanId = "pro_lifetime",
heroIcon = PaywallIcons.Star,
)
Parameter Type Default Description
plans List<PaywallPlan> required Plans to offer, in display order. Must not be empty.
features List<PaywallFeature> required Benefits listed under the plans.
legal PaywallLegal required Terms and Privacy URLs for the footer links.
onPurchase (PaywallPlan) -> Unit required Called with the selected plan when the button is tapped. Start your billing flow here.
onRestore () -> Unit required Called from the Restore link.
onClose () -> Unit required Called from the close button.
modifier Modifier Modifier Applied to the paywall’s root.
title String “Unlock everything” Headline.
subtitle String? “One upgrade. Every feature. No ads, ever.” Line under the headline. null hides it.
initialPlanId String? null Plan to preselect. Defaults to the first plan with a badge, else the first plan.
heroIcon ImageVector PaywallIcons.Sparkle Icon in the center of the hero art.

Full signature: ClassicPlansPaywall in the API reference.

Colors come from MaterialTheme.colorScheme, so dynamic color and dark mode just work. Selection animations follow MaterialTheme.motionScheme: MotionScheme.expressive() gives the springy corner and radio morph shown above, MotionScheme.standard() a calmer version.

  • Each plan card is selectable with Role.RadioButton, so TalkBack reads it as a radio option with its selected state.
  • The close button and the footer links are at least 48dp tall.
  • Text uses the theme’s type scale and wraps at large font sizes; the body scrolls while the button stays fixed.
  • Hard-coding prices. Always pass the localized formattedPrice from the store.
  • Badging a plan and then passing initialPlanId for another one: the badge no longer marks the preselected plan, which confuses users.
  • Showing the yearly plan only as a monthly figure. Put the real yearly price in price and use priceDetail for “$2.49/month”.

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