Prototype apps in minutes

Paper is a prototyping tool that lets you customize anything visually then exports to code, ready to be picked by developers.

Loved by developers
Powered by
Kotlin
Android Studio compatible
Compose Multiplatform ready

Examples

Designed by AI. Edited by a human.

Paper lets you prototype your apps super fast by combining AI speed with full creative control like design tools.

Pixel perfect design with a click of a button

Paper writes production ready clean code instantly. Forget AI slop, forget spaghetti code, only perfectly written code every time

Preview
            package com.example

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
// ...

@Composable
fun Discover() {
    var text by remember { mutableStateOf("") }
    val breakpoint = currentScreenWidthBreakpoint()
    
    Column(Modifier.fillMaxSize().background(LocalSurfaceColorColor.current), horizontalAlignment = Alignment.CenterHorizontally) {
        CompositionLocalProvider(LocalContentColor provides LocalOnSurfaceColorColor.current) {
            TopAppBar(
                modifier = Modifier.height(56.dp).fillMaxWidth(),
                title = {
                    Text("Discover", style = LocalTitleLargeTextStyle.current)
                },
                actions = {
                    Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
                    }
                },
                contentColor = LocalOnSurfaceColorColor.current,
                backgroundColor = LocalSurfaceColorColor.current,
                contentPadding = PaddingValues(start = 4.dp, top = 0.dp, end = 4.dp, bottom = 0.dp)
            )
            Column(Modifier.widthIn(max = 600.dp).weight(weight = 1f, fill = false).fillMaxHeight().clip(RoundedCornerShape(LocalCornerRadiusShape.current)).padding(top = 8.dp)) {
                Row(Modifier.padding(16.dp)) {
                    Row(Modifier.height(56.dp).weight(1.0f, fill = false).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadiusShape.current)).border(1.dp, LocalOutlineColorColor.current, RoundedCornerShape(LocalCornerRadiusShape.current)).background(LocalSurfaceColorColor.current).padding(end = 8.dp, start = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
                        CompositionLocalProvider(LocalContentColor provides LocalOnSurfaceColorColor.current) {
                            Icon(Lucide.Search, contentDescription = "", tint = LocalContentColor.current)
                            TextField(
                                value = text,
                                onValueChange = {
                                    text = it
                                },
                                modifier = Modifier.weight(1.0f, fill = false).fillMaxWidth(),
                                maxLines = 1,
                                placeholderText = "Search",
                                contentPadding = PaddingValues(start = 8.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
                                borderWidth = 0.dp,
                                borderColor = LocalOutlineColorColor.current,
                                fontSize = 16.sp,
                                singleLine = true
                            )
                            Row {
                                GhostButton(onClick = { /* TODO Handle this */ }, shape = RoundedCornerShape(LocalCornerRadiusShape.current), contentPadding = PaddingValues(8.dp)) {
                                    Icon(Lucide.Mic, contentDescription = "", tint = LocalContentColor.current, modifier = Modifier.alpha(0.66f))
                                }
                            }
                        }
                    }
                }
                Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()).padding(end = 16.dp, bottom = 8.dp, start = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                    PrimaryButton(onClick = { /* TODO Handle this */ }, shape = RoundedCornerShape(LocalCornerRadiusShape.current), contentColor = LocalOnPrimaryColorColor.current, backgroundColor = LocalPrimaryColorColor.current, borderColor = LocalPrimaryColorColor.current) {
                        Text("All", fontSize = 16.sp, maxLines = 1)
                    }
                    OutlinedButton(onClick = { /* TODO Handle this */ }, shape = RoundedCornerShape(LocalCornerRadiusShape.current), borderColor = LocalOutlineColorColor.current) {
                        Text("T-Shirts", fontSize = 16.sp, maxLines = 1)
                    }
                    OutlinedButton(onClick = { /* TODO Handle this */ }, shape = RoundedCornerShape(LocalCornerRadiusShape.current), borderColor = LocalOutlineColorColor.current) {
                        Text("Jeans", fontSize = 16.sp, maxLines = 1)
                    }
                    OutlinedButton(onClick = { /* TODO Handle this */ }, shape = RoundedCornerShape(LocalCornerRadiusShape.current), borderColor = LocalOutlineColorColor.current) {
                        Text("Accessories", fontSize = 16.sp, maxLines = 1)
                    }
                    OutlinedButton(onClick = { /* TODO Handle this */ }, shape = RoundedCornerShape(LocalCornerRadiusShape.current), borderColor = LocalOutlineColorColor.current) {
                        Text("Shoes", fontSize = 16.sp, maxLines = 1)
                    }
                    OutlinedButton(onClick = { /* TODO Handle this */ }, shape = RoundedCornerShape(LocalCornerRadiusShape.current), borderColor = LocalOutlineColorColor.current) {
                        Text("Wallets", fontSize = 16.sp, maxLines = 1)
                    }
                }
                LazyVerticalGrid(if (breakpoint isAtLeast ScreenWidthBreakpoint.Small) GridCells.Fixed(3) else GridCells.Fixed(2), modifier = Modifier.fillMaxWidth().weight(weight = 1f, fill = false).fillMaxHeight(), contentPadding = PaddingValues(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                    item {
                        Column(Modifier.fillMaxWidth()) {
                            Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?q=80&w=2124&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.height(300.dp).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadiusShape.current)), contentScale = ContentScale.Crop)
                            Column(Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
                                Text("Yellow Outfit", modifier = Modifier.fillMaxWidth(), fontSize = 20.sp, fontWeight = FontWeight(500))
                                Text("$120.00", modifier = Modifier.fillMaxWidth().alpha(0.6f), fontSize = 16.sp)
                            }
                        }
                    }
                    item {
                        Column(Modifier.fillMaxWidth()) {
                            Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1487222477894-8943e31ef7b2?q=80&w=3408&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.height(300.dp).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadiusShape.current)), contentScale = ContentScale.Crop)
                            Column(Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
                                Text("Leather Jacket", modifier = Modifier.fillMaxWidth(), fontSize = 20.sp, fontWeight = FontWeight(500))
                                Text("$190.00", modifier = Modifier.fillMaxWidth().alpha(0.6f), fontSize = 16.sp)
                            }
                        }
                    }
                    item {
                        Column(Modifier.fillMaxWidth()) {
                            Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1554838692-3b50e4261b6f?q=80&w=3456&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "Red Shades photo", modifier = Modifier.height(300.dp).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadiusShape.current)), contentScale = ContentScale.Crop)
                            Column(Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
                                Text("Red Shades", modifier = Modifier.fillMaxWidth(), fontSize = 20.sp, fontWeight = FontWeight(500))
                                Text("$50.00", modifier = Modifier.fillMaxWidth().alpha(0.6f), fontSize = 16.sp)
                            }
                        }
                    }
                    item {
                        Column(Modifier.fillMaxWidth()) {
                            Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?q=80&w=3000&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.height(300.dp).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadiusShape.current)), contentScale = ContentScale.Crop)
                            Column(Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
                                Text("Basic T-Shirt", modifier = Modifier.fillMaxWidth(), fontSize = 20.sp, fontWeight = FontWeight(500))
                                Text("$20.00", modifier = Modifier.fillMaxWidth().alpha(0.6f), fontSize = 16.sp)
                            }
                        }
                    }
                    item {
                        Column(Modifier.fillMaxWidth()) {
                            Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1510060637021-6287bd1b5232?q=80&w=3542&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "Basic Cap photo", modifier = Modifier.height(300.dp).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadiusShape.current)), contentScale = ContentScale.Crop)
                            Column(Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
                                Text("Basic Cap", modifier = Modifier.fillMaxWidth(), fontSize = 20.sp, fontWeight = FontWeight(500))
                                Text("$35.00", modifier = Modifier.fillMaxWidth().alpha(0.6f), fontSize = 16.sp)
                            }
                        }
                    }
                    item {
                        Column(Modifier.fillMaxWidth()) {
                            Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1519027356316-9f99e11d8bac?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.height(300.dp).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadiusShape.current)), contentScale = ContentScale.Crop)
                            Column(Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
                                Text("Cardigan", modifier = Modifier.fillMaxWidth(), fontSize = 20.sp, fontWeight = FontWeight(500))
                                Text("$90.00", modifier = Modifier.fillMaxWidth().alpha(0.6f), fontSize = 16.sp)
                            }
                        }
                    }
                }
            }
            BottomNavigation(modifier = Modifier.heightIn(56.dp).fillMaxWidth().shadow(4.dp).zIndex(4.0f).background(if (breakpoint isAtLeast ScreenWidthBreakpoint.Small) LocalSurfaceColorColor.current else Color.Unspecified), contentColor = LocalOnSurfaceColorColor.current, backgroundColor = LocalSurfaceColorColor.current) {
                TabItem(selected = true, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1.0f, fill = false).fillMaxWidth(), selectedColor = LocalPrimaryColorColor.current, contentColor = LocalContentColor.current) {
                    Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically)) {
                        Icon(Lucide.House, contentDescription = "", tint = LocalContentColor.current)
                        Text("Discover", fontSize = 16.sp)
                    }
                }
                TabItem(selected = false, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1.0f, fill = false).fillMaxWidth(), selectedColor = LocalPrimaryColorColor.current, contentColor = LocalContentColor.current) {
                    Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically)) {
                        Icon(Lucide.ShoppingCart, contentDescription = "", tint = LocalContentColor.current)
                        Text("Cart", fontSize = 16.sp)
                    }
                }
                TabItem(selected = false, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1.0f, fill = false).fillMaxWidth(), selectedColor = LocalPrimaryColorColor.current, contentColor = LocalContentColor.current) {
                    Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically)) {
                        Icon(Lucide.ContactRound, contentDescription = "", tint = LocalContentColor.current)
                        Text("Account", fontSize = 16.sp)
                    }
                }
            }
        }
    }
}


High quality apps need high quality tooling

Paper got the right thing for the right moment of your building process to bring your ideas to life
Drag & Drop
Intuitive WYSIWYG editor
Live Preview
See changes live without waiting for instal
Theme Editor
Create consistent styling across your app
Tablet Overrides
Make your app responsive depending on the screensize
Cross-Platform
Exports to Android, iOS, Desktop & Web
Iconography
Curated icons included straight from the editor
Color Pallete
Hand-picked colors right from the editor
Zero dev handoff
Code ready for devving (for real)

Faster than ChatGPT, easier than Code

Learn how to build a responsive Log In screen visually in 60 seconds.

About

Alex Styl
Alex Styl
Solo Founder of BuiltWithPaper.com
Previously: Apple

I've spent 10+ building native apps in companies such as Apple, product studios and my own startups.

99% of the time, I want to focus on the app instead of the code. I'm fast with code, but trying out the changes on the device takes forever. You need to build, install, see the app on the device etc.

So I built Paper.

It works like a professional design tool and exports clean, professional code. You move fast without touching code, but still get full control when needed.

Paper is a bootstrapped company. There is no VC funding, all profits go directly to improving the product for you (and paying my rent and coffee). This means that every single decision made is to improve the product for you, while running a sustainable business that will last for years to come.

Don't let Android Studio Slow You Down

Paper
30 day money guaranteed
Get Started