Blocks
Auth
A two-screen registration flow with a colorful mosaic splash, form fields, and social sign-up options.
Welcome to B-List
Record everything you need in one place in a few seconds and easily.
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
// ── Welcome screen ─────────────────────────────────────────────
@Composable
fun WelcomeScreen(onRegister: () -> Unit, onSignIn: () -> Unit) {
val accent = Color(0xFF7C3AED) // violet-700
Column(Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
MosaicPattern(Modifier.fillMaxWidth().height(260.dp))
Column(
Modifier
.weight(1f)
.padding(horizontal = 28.dp)
.padding(bottom = 36.dp),
verticalArrangement = Arrangement.Center,
) {
Text(
"Welcome to B-List",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.ExtraBold,
)
Text(
"Record everything you need in one place " +
"in a few seconds and easily.",
modifier = Modifier.padding(top = 12.dp),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
)
Spacer(Modifier.height(28.dp))
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Button(
onClick = onRegister,
modifier = Modifier.weight(1f),
shape = CircleShape,
colors = ButtonDefaults.buttonColors(
containerColor = accent,
),
) { Text("Register") }
OutlinedButton(
onClick = onSignIn,
modifier = Modifier.weight(1f),
shape = CircleShape,
border = BorderStroke(1.5.dp, accent),
colors = ButtonDefaults.outlinedButtonColors(
contentColor = accent,
),
) { Text("Sign in") }
}
}
}
}
// ── Register screen ──────────────────────────────────────────────
@Composable
fun RegisterScreen(onBack: () -> Unit, onRegister: () -> Unit) {
val accent = Color(0xFF7C3AED)
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
Column(Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
// Mosaic header with back button
Box {
MosaicPattern(
Modifier
.fillMaxWidth()
.height(140.dp)
.align(Alignment.TopEnd),
)
IconButton(
onClick = onBack,
modifier = Modifier.padding(top = 8.dp, start = 4.dp),
) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
)
}
}
Column(
Modifier
.weight(1f)
.verticalScroll(rememberScrollState())
.padding(horizontal = 24.dp)
.padding(bottom = 32.dp),
) {
Text(
"Create a new account",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.ExtraBold,
)
Text(
"Please enter your information below to create an account.",
modifier = Modifier.padding(top = 8.dp),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(Modifier.height(20.dp))
OutlinedTextField(
value = name,
onValueChange = { name = it },
placeholder = { Text("Full name") },
singleLine = true,
shape = RoundedCornerShape(14.dp),
modifier = Modifier.fillMaxWidth(),
)
Spacer(Modifier.height(12.dp))
OutlinedTextField(
value = email,
onValueChange = { email = it },
placeholder = { Text("Email") },
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
),
shape = RoundedCornerShape(14.dp),
modifier = Modifier.fillMaxWidth(),
)
Spacer(Modifier.height(12.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
placeholder = { Text("Password") },
singleLine = true,
visualTransformation = if (passwordVisible)
VisualTransformation.None
else
PasswordVisualTransformation(),
trailingIcon = {
IconButton(
onClick = { passwordVisible = !passwordVisible },
) {
Icon(
if (passwordVisible) Icons.Filled.Visibility
else Icons.Filled.VisibilityOff,
contentDescription = "Toggle password",
)
}
},
shape = RoundedCornerShape(14.dp),
modifier = Modifier.fillMaxWidth(),
)
Spacer(Modifier.height(20.dp))
Button(
onClick = onRegister,
modifier = Modifier.fillMaxWidth(),
shape = CircleShape,
colors = ButtonDefaults.buttonColors(
containerColor = accent,
),
) { Text("Register now") }
Spacer(Modifier.height(16.dp))
Text(
"or sign up with",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(Modifier.height(12.dp))
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
listOf("Facebook", "Google", "Apple").forEach { provider ->
OutlinedIconButton(
onClick = {},
modifier = Modifier.size(48.dp),
shape = CircleShape,
) {
Icon(
when (provider) {
"Facebook" -> Icons.Filled.Facebook
"Google" -> Icons.Filled.Android
else -> Icons.Filled.Apple
},
contentDescription = provider,
modifier = Modifier.size(20.dp),
)
}
Spacer(Modifier.width(12.dp))
}
}
Spacer(Modifier.height(16.dp))
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
Text(
"Already have an account? ",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(
"Sign In",
style = MaterialTheme.typography.bodySmall,
fontWeight = FontWeight.SemiBold,
color = accent,
)
}
}
}
}
// ── Mosaic composable (shared) ───────────────────────────────────
@Composable
private fun MosaicPattern(modifier: Modifier = Modifier) {
val colors = listOf(
Color(0xFFF97316), Color(0xFFA855F7),
Color(0xFF14B8A6), Color(0xFFEC4899),
)
// 5 × 4 grid of quarter-circle tiles, 52dp each
val tileSize = 52.dp
val cols = 5
val rows = 4
Canvas(modifier.size(width = tileSize * cols, height = tileSize * rows)) {
val px = tileSize.toPx()
repeat(rows) { row ->
repeat(cols) { col ->
val idx = row * cols + col
val color = colors[idx % colors.size]
val rot = (idx % 4) * 90f
withTransform({
translate(col * px, row * px)
rotate(rot, pivot = Offset(px / 2, px / 2))
}) {
// A full 2×2 circle, offset so only the top-left quadrant shows
drawCircle(
color = color,
radius = px,
center = Offset(0f, 0f),
)
}
}
}
}
}