The Prototyping tool loved by developers
Everything you need to build high quality responsive app prototypes for every screen size fast. Edit visually, and export to high quality production ready code.





Founded by ex-Apple
Build apps, not graphics
Pixel Perfect UI

Marcel Teixeira
Examples
Build apps like these
Pixel perfect design with a click of a button

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)
}
}
}
}
}
}
Faster than ChatGPT, easier than Code
Iterate at the speed of light with Live Preview
High quality apps need high quality tooling
About
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.
Kotlin
From idea to reality, with one time purchase
Solo License
$29
$23
- ✅ 1 personal device
- ✅ Native Mac OS app
- ✅ One year of updates
- ✅ Prototype apps for iOS, Android, Desktop & Web
- ✅ Pixel Perfect Exports
- ✅ Save apps to JSON
- ✅ Yours to keep forever
Team License
$64
$51
- ✅ 3 personal devices
- ✅ Native Mac OS app
- ✅ One year of updates
- ✅ Prototype apps for iOS, Android, Desktop & Web
- ✅ Pixel Perfect Exports
- ✅ Save apps to JSON
- ✅ Yours to keep forever