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.


implementation("com.github.sanikantv:m3-expressive-paywall:v1.0.0")When to use it
Section titled “When to use it”- You sell two or three plans, such as monthly, yearly and lifetime.
- You want the safest, most familiar paywall to start with.
Basic usage
Section titled “Basic usage”ClassicPlansPaywall( plans = Sample.plans, features = Sample.features, legal = Sample.legal, onPurchase = { plan -> buy(plan) }, onRestore = restore, onClose = close,)Custom copy, preselected plan and icon
Section titled “Custom copy, preselected plan and icon”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,)Parameters
Section titled “Parameters”| 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.
Customization
Section titled “Customization”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.
Accessibility
Section titled “Accessibility”- Each plan card is
selectablewithRole.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.
Common mistakes
Section titled “Common mistakes”- Hard-coding prices. Always pass the localized
formattedPricefrom the store. - Badging a plan and then passing
initialPlanIdfor 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
priceand usepriceDetailfor “$2.49/month”.
Related
Section titled “Related”Built by Sanikant. Set in Google Sans Flex (SIL Open Font License). Not affiliated with Google.