Blocks

Memrizz Paywall

A subscription paywall with a Lite/Pro toggle, feature list, three selectable billing plans and a subscribe CTA.

Upgrade to

Memrizz Premium

LitePro
Everything in Memrizz Lite
Image Occlusion and Cloze decks
Process documents of up to 50 Mb
Generate cards in any language
Monthly
$14.99/ month
Semester
$44.97$42.00/ semester
Annual
$179.88$119.00/ year

Cancel anytime on the App Store

Installation

caveui components are copy-paste Jetpack Compose built entirely on Material 3 — there's no caveui dependency to add. Make sure Material 3 is on your classpath (it ships with the Compose BOM), then copy the Usage snippet below into your project.

kotlin
// build.gradle.kts (module)
dependencies {
    implementation(platform("androidx.compose:compose-bom:2025.06.00"))
    implementation("androidx.compose.material3:material3")
}

Usage

kotlin
data class Plan(
    val label: String,
    val price: String,
    val unit: String,
    val originalPrice: String? = null,
)

@Composable
fun MemrizzPaywallScreen(
    plans: List<Plan>,
    selectedPlan: Int,
    onSelectPlan: (Int) -> Unit,
    onSubscribe: () -> Unit,
    onDismiss: () -> Unit,
) {
    val violet = Color(0xFF7C3AED)
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colorScheme.background)
            .verticalScroll(rememberScrollState())
            .padding(horizontal = 20.dp)
            .padding(bottom = 24.dp),
    ) {
        // Close
        Box(Modifier.fillMaxWidth().padding(top = 16.dp)) {
            IconButton(
                onClick = onDismiss,
                modifier = Modifier.align(Alignment.TopEnd).size(36.dp),
            ) {
                Icon(Icons.Filled.Close, contentDescription = "Close")
            }
        }
        // Headline
        Text(
            "Upgrade to",
            style = MaterialTheme.typography.headlineSmall,
            color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
        )
        Text(
            "Memrizz Premium",
            style = MaterialTheme.typography.headlineSmall,
            fontWeight = FontWeight.ExtraBold,
        )
        Spacer(Modifier.height(20.dp))

        // Lite / Pro segmented toggle
        Surface(
            shape = CircleShape,
            border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline),
            modifier = Modifier.fillMaxWidth(),
        ) {
            Row(Modifier.padding(4.dp)) {
                Text(
                    "Lite",
                    modifier = Modifier.weight(1f).padding(vertical = 8.dp),
                    textAlign = TextAlign.Center,
                    style = MaterialTheme.typography.bodyMedium,
                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                )
                Surface(
                    color = MaterialTheme.colorScheme.onBackground,
                    shape = CircleShape,
                    modifier = Modifier.weight(1f),
                ) {
                    Text(
                        "Pro",
                        modifier = Modifier.padding(vertical = 8.dp),
                        textAlign = TextAlign.Center,
                        style = MaterialTheme.typography.bodyMedium,
                        fontWeight = FontWeight.SemiBold,
                        color = MaterialTheme.colorScheme.background,
                    )
                }
            }
        }
        Spacer(Modifier.height(20.dp))

        // Feature list
        val features = listOf(
            "Everything in Memrizz Lite",
            "Image Occlusion and Cloze decks",
            "Process documents of up to 50 Mb",
            "Generate cards in any language",
        )
        features.forEach { feature ->
            Row(
                modifier = Modifier.padding(vertical = 6.dp),
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Surface(
                    shape = RoundedCornerShape(10.dp),
                    border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline),
                    modifier = Modifier.size(36.dp),
                ) {
                    Box(contentAlignment = Alignment.Center) {
                        Icon(
                            Icons.Outlined.Star,
                            contentDescription = null,
                            modifier = Modifier.size(18.dp),
                        )
                    }
                }
                Spacer(Modifier.width(12.dp))
                Text(feature, style = MaterialTheme.typography.bodyMedium)
            }
        }
        Spacer(Modifier.height(16.dp))

        // Plan cards
        plans.forEachIndexed { i, plan ->
            OutlinedCard(
                onClick = { onSelectPlan(i) },
                modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
                border = if (i == selectedPlan)
                    BorderStroke(1.5.dp, violet)
                else
                    CardDefaults.outlinedCardBorder(),
                colors = CardDefaults.outlinedCardColors(
                    containerColor = if (i == selectedPlan)
                        violet.copy(alpha = 0.06f)
                    else
                        MaterialTheme.colorScheme.surface,
                ),
            ) {
                Row(
                    modifier = Modifier.fillMaxWidth().padding(14.dp),
                    horizontalArrangement = Arrangement.SpaceBetween,
                    verticalAlignment = Alignment.CenterVertically,
                ) {
                    Text(
                        plan.label,
                        style = MaterialTheme.typography.bodyLarge,
                        fontWeight = FontWeight.SemiBold,
                        color = if (i == selectedPlan) violet
                            else MaterialTheme.colorScheme.onSurface,
                    )
                    Row(verticalAlignment = Alignment.Baseline) {
                        plan.originalPrice?.let {
                            Text(
                                it,
                                style = MaterialTheme.typography.bodySmall,
                                color = MaterialTheme.colorScheme.onSurfaceVariant,
                                textDecoration = TextDecoration.LineThrough,
                                modifier = Modifier.padding(end = 4.dp),
                            )
                        }
                        Text(
                            plan.price,
                            style = MaterialTheme.typography.bodyLarge,
                            fontWeight = FontWeight.Bold,
                        )
                        Text(
                            " ${plan.unit}",
                            style = MaterialTheme.typography.labelSmall,
                            color = MaterialTheme.colorScheme.onSurfaceVariant,
                        )
                    }
                }
            }
        }

        Text(
            "Cancel anytime on the App Store",
            modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp),
            textAlign = TextAlign.Center,
            style = MaterialTheme.typography.labelSmall,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
        )
        Button(
            onClick = onSubscribe,
            modifier = Modifier.fillMaxWidth(),
            shape = CircleShape,
            colors = ButtonDefaults.buttonColors(
                containerColor = MaterialTheme.colorScheme.onBackground,
                contentColor = MaterialTheme.colorScheme.background,
            ),
        ) {
            Text("Subscribe", fontWeight = FontWeight.SemiBold)
        }
    }
}