Blocks

Social Discover

A warm-toned social feed with stories, full-bleed post cards, action counters and a floating compose button.

Me

Discover

Add story
B
Live
Brian S
J
Jennie
J
James
R
Roes
12.3k
250
100
NR

Nathan Rusl

@nathanrsl

Nature's beauty is the best remedy for a restless soul. #FindYourPeace

Home
Search
Message
Profile

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 Story(
    val name: String,
    val isLive: Boolean,
)

data class Post(
    val authorName: String,
    val authorHandle: String,
    val caption: String,
    val likes: String,
    val shares: String,
    val comments: String,
)

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SocialDiscoverScreen(
    stories: List<Story>,
    posts: List<Post>,
    onAddStory: () -> Unit,
    onPost: () -> Unit,
) {
    val terracotta = Color(0xFFE8612C)
    val warmBg = MaterialTheme.colorScheme.background

    Scaffold(
        containerColor = warmBg,
        bottomBar = {
            Box(contentAlignment = Alignment.TopCenter) {
                NavigationBar(containerColor = warmBg) {
                    NavigationBarItem(
                        selected = true,
                        onClick = {},
                        icon = {
                            Icon(
                                Icons.Filled.Home,
                                contentDescription = "Home",
                                tint = terracotta,
                            )
                        },
                        label = {
                            Text(
                                "Home",
                                color = terracotta,
                                style = MaterialTheme.typography.labelSmall,
                            )
                        },
                    )
                    NavigationBarItem(
                        selected = false,
                        onClick = {},
                        icon = {
                            Icon(Icons.Filled.Search, contentDescription = "Search")
                        },
                        label = { Text("Search") },
                    )
                    // FAB spacer
                    NavigationBarItem(
                        selected = false,
                        onClick = {},
                        icon = { Spacer(Modifier.size(48.dp)) },
                        label = {},
                        enabled = false,
                    )
                    NavigationBarItem(
                        selected = false,
                        onClick = {},
                        icon = {
                            Icon(
                                Icons.Outlined.ChatBubbleOutline,
                                contentDescription = "Message",
                            )
                        },
                        label = { Text("Message") },
                    )
                    NavigationBarItem(
                        selected = false,
                        onClick = {},
                        icon = {
                            Icon(Icons.Outlined.Person, contentDescription = "Profile")
                        },
                        label = { Text("Profile") },
                    )
                }
                FloatingActionButton(
                    onClick = onPost,
                    modifier = Modifier.offset(y = (-24).dp),
                    containerColor = terracotta,
                    shape = CircleShape,
                ) {
                    Icon(Icons.Filled.Add, contentDescription = "New post", tint = Color.White)
                }
            }
        },
    ) { padding ->
        LazyColumn(
            modifier = Modifier.fillMaxSize().padding(padding),
            contentPadding = PaddingValues(bottom = 16.dp),
        ) {
            // Top bar
            item {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(horizontal = 16.dp)
                        .padding(top = 16.dp),
                    horizontalArrangement = Arrangement.SpaceBetween,
                    verticalAlignment = Alignment.CenterVertically,
                ) {
                    Surface(shape = CircleShape, color = terracotta.copy(alpha = 0.2f)) {
                        Box(
                            Modifier.size(40.dp),
                            contentAlignment = Alignment.Center,
                        ) {
                            Text(
                                "Me",
                                color = terracotta,
                                style = MaterialTheme.typography.labelSmall,
                                fontWeight = FontWeight.Bold,
                            )
                        }
                    }
                    IconButton(onClick = {}) {
                        Icon(Icons.Outlined.Notifications, contentDescription = "Notifications")
                    }
                }
            }

            // Title
            item {
                Text(
                    "Discover",
                    modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
                    style = MaterialTheme.typography.headlineMedium,
                    fontWeight = FontWeight.ExtraBold,
                )
            }

            // Stories row
            item {
                LazyRow(
                    contentPadding = PaddingValues(horizontal = 16.dp),
                    horizontalArrangement = Arrangement.spacedBy(12.dp),
                ) {
                    item {
                        Column(horizontalAlignment = Alignment.CenterHorizontally) {
                            Surface(
                                onClick = onAddStory,
                                shape = CircleShape,
                                color = terracotta.copy(alpha = 0.08f),
                                border = BorderStroke(
                                    1.5.dp,
                                    MaterialTheme.colorScheme.outlineVariant,
                                ),
                                modifier = Modifier.size(56.dp),
                            ) {
                                Box(contentAlignment = Alignment.Center) {
                                    Icon(
                                        Icons.Filled.Add,
                                        contentDescription = "Add story",
                                        tint = terracotta,
                                    )
                                }
                            }
                            Text(
                                "Add story",
                                modifier = Modifier.padding(top = 4.dp),
                                style = MaterialTheme.typography.labelSmall,
                                color = MaterialTheme.colorScheme.onSurfaceVariant,
                            )
                        }
                    }
                    items(stories) { s ->
                        Column(horizontalAlignment = Alignment.CenterHorizontally) {
                            Box {
                                Surface(
                                    shape = CircleShape,
                                    border = BorderStroke(2.dp, terracotta),
                                    modifier = Modifier
                                        .size(56.dp)
                                        .padding(2.dp),
                                ) {
                                    Box(
                                        Modifier
                                            .fillMaxSize()
                                            .background(
                                                MaterialTheme.colorScheme.surfaceVariant,
                                                CircleShape,
                                            ),
                                        contentAlignment = Alignment.Center,
                                    ) {
                                        Text(
                                            s.name.first().toString(),
                                            style = MaterialTheme.typography.labelMedium,
                                            fontWeight = FontWeight.Bold,
                                        )
                                    }
                                }
                                if (s.isLive) {
                                    Surface(
                                        modifier = Modifier
                                            .align(Alignment.BottomCenter)
                                            .offset(y = 4.dp),
                                        color = terracotta,
                                        shape = CircleShape,
                                    ) {
                                        Text(
                                            "LIVE",
                                            modifier = Modifier.padding(
                                                horizontal = 5.dp, vertical = 1.dp
                                            ),
                                            color = Color.White,
                                            style = MaterialTheme.typography.labelSmall,
                                            fontWeight = FontWeight.ExtraBold,
                                        )
                                    }
                                }
                            }
                            Text(
                                s.name,
                                modifier = Modifier.padding(top = 6.dp),
                                style = MaterialTheme.typography.labelSmall,
                                color = MaterialTheme.colorScheme.onSurfaceVariant,
                            )
                        }
                    }
                }
            }

            // Post cards
            items(posts) { post ->
                PostCard(post = post, accent = terracotta)
            }
        }
    }
}

@Composable
private fun PostCard(post: Post, accent: Color) {
    Box(
        modifier = Modifier
            .padding(horizontal = 12.dp)
            .padding(top = 12.dp)
            .fillMaxWidth()
            .height(240.dp)
            .clip(RoundedCornerShape(20.dp))
            .background(Color(0xFF4A3728)),
    ) {
        // Bottom gradient overlay
        Box(
            Modifier
                .fillMaxSize()
                .background(
                    Brush.verticalGradient(
                        listOf(Color.Transparent, Color(0xCC1a1208)),
                        startY = 80f,
                    ),
                ),
        )

        // Action column — right side
        Column(
            modifier = Modifier
                .align(Alignment.CenterEnd)
                .padding(end = 12.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(16.dp),
        ) {
            listOf(
                Triple(Icons.Filled.Favorite, post.likes, true),
                Triple(Icons.Outlined.Share, post.shares, false),
                Triple(Icons.Outlined.ChatBubbleOutline, post.comments, false),
            ).forEach { (icon, count, filled) ->
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Icon(
                        imageVector = icon,
                        contentDescription = null,
                        tint = if (filled) accent else Color.White,
                        modifier = Modifier.size(22.dp),
                    )
                    Text(
                        count,
                        color = Color.White,
                        style = MaterialTheme.typography.labelSmall,
                        fontWeight = FontWeight.SemiBold,
                    )
                }
            }
        }

        // Author + caption — bottom left
        Column(
            modifier = Modifier
                .align(Alignment.BottomStart)
                .padding(12.dp)
                .padding(end = 56.dp),
        ) {
            Row(verticalAlignment = Alignment.CenterVertically) {
                Surface(
                    shape = CircleShape,
                    border = BorderStroke(2.dp, accent),
                    modifier = Modifier.size(32.dp),
                ) {
                    Box(
                        Modifier
                            .fillMaxSize()
                            .background(accent.copy(alpha = 0.3f)),
                        contentAlignment = Alignment.Center,
                    ) {
                        Text(
                            post.authorName.take(2).uppercase(),
                            color = Color.White,
                            style = MaterialTheme.typography.labelSmall,
                            fontWeight = FontWeight.Bold,
                        )
                    }
                }
                Spacer(Modifier.width(8.dp))
                Column {
                    Text(
                        post.authorName,
                        color = Color.White,
                        style = MaterialTheme.typography.labelLarge,
                        fontWeight = FontWeight.Bold,
                    )
                    Text(
                        post.authorHandle,
                        color = Color.White.copy(alpha = 0.7f),
                        style = MaterialTheme.typography.labelSmall,
                    )
                }
            }
            Text(
                post.caption,
                modifier = Modifier.padding(top = 6.dp),
                color = Color.White.copy(alpha = 0.9f),
                style = MaterialTheme.typography.bodySmall,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
            )
        }
    }
}