Android Authentication UI in Jetpack Compose: Modern Sign up Screen (Full Code Explained)
Creating a clean authentication UI is one of the fastest ways to make your Android app look “production-ready”. In this post, we’ll break down a modern Sign up screen in Jetpack Compose featuring a gradient background, a rounded card layout, input fields, checkbox consent, a primary button, and social login icons.
This article explains the full UI code step-by-step so you can customize it easily for your own app.
What you’ll build
This Compose screen includes:- Gradient background with soft decorative blobs
- A “Back” chip at the top-left
- White rounded card in the center
- Full name, email, and password text fields
- “I agree…” checkbox line
- Sign up button
- Divider text (“Sign up with”)
- Social login icons row
- “Already have an account? Sign in” text
Full Jetpack Compose code
Paste this code into your Compose UI module (and replace the social drawables with your own icons). The screen is designed to be reusable by exposing callbacks for Back, Sign up, and Sign in.
@Composable
fun SignUpDesignScreen(
onBack: () -> Unit = {},
onSignUp: (name: String, email: String, pass: String, accepted: Boolean) -> Unit = { _, _, _, _ -> },
onSignIn: () -> Unit = {}
) {
var fullName by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var accepted by remember { mutableStateOf(false) }
val bg = Brush.linearGradient(
colors = listOf(Color(0xFF5B6DFF), Color(0xFF7B57F3))
)
Box(
modifier = Modifier
.fillMaxSize()
.background(bg)
.padding(18.dp)
) {
// Soft blobs (approx.)
Box(
Modifier
.size(170.dp)
.align(Alignment.TopEnd)
.offset(x = 30.dp, y = 40.dp)
.clip(CircleShape)
.background(Color.White.copy(alpha = 0.18f))
)
Box(
Modifier
.size(110.dp)
.align(Alignment.TopCenter)
.offset(y = 140.dp)
.clip(CircleShape)
.background(Color.White.copy(alpha = 0.22f))
)
// Back chip
BackChip(
modifier = Modifier.align(Alignment.TopStart),
onBack = onBack
)
// White card
Surface(
modifier = Modifier
.align(Alignment.Center)
.fillMaxWidth()
.padding(top = 60.dp),
color = Color.White,
shape = RoundedCornerShape(24.dp),
tonalElevation = 0.dp,
shadowElevation = 0.dp
) {
Column(
modifier = Modifier.padding(horizontal = 18.dp, vertical = 18.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Get Started",
fontSize = 22.sp,
fontWeight = FontWeight.SemiBold,
color = Color(0xFF2A2E3B),
modifier = Modifier.padding(top = 6.dp, bottom = 14.dp)
)
RoundedInput(
label = "Full Name",
value = fullName,
onValueChange = { fullName = it },
placeholder = "Enter Full Name",
leading = { Icon(Icons.Default.Person, contentDescription = null) }
)
Spacer(Modifier.height(10.dp))
RoundedInput(
label = "Email",
value = email,
onValueChange = { email = it },
placeholder = "Enter Email",
leading = { Icon(Icons.Default.Mail, contentDescription = null) }
)
Spacer(Modifier.height(10.dp))
RoundedInput(
label = "Password",
value = password,
onValueChange = { password = it },
placeholder = "Enter Password",
isPassword = true,
leading = { Icon(Icons.Default.Lock, contentDescription = null) }
)
Spacer(Modifier.height(10.dp))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = accepted,
onCheckedChange = { accepted = it },
colors = CheckboxDefaults.colors(
checkedColor = Color(0xFF5B6DFF),
uncheckedColor = Color(0xFF9AA3B2),
checkmarkColor = Color.White
)
)
Text(
text = buildAnnotatedString {
append("I agree to the processing of ")
withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) {
append("Personal data")
}
},
fontSize = 12.sp,
color = Color(0xFF6C7486)
)
}
Spacer(Modifier.height(10.dp))
Button(
onClick = { onSignUp(fullName, email, password, accepted) },
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
shape = RoundedCornerShape(14.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF4E62FF)
),
enabled = true
) {
Text("Sign up", fontSize = 15.sp, fontWeight = FontWeight.Medium)
}
Spacer(Modifier.height(14.dp))
// Divider text
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Divider(modifier = Modifier.weight(1f), color = Color(0xFFE7EAF0))
Text(
text = " Sign up with ",
fontSize = 12.sp,
color = Color(0xFF9AA3B2)
)
Divider(modifier = Modifier.weight(1f), color = Color(0xFFE7EAF0))
}
Spacer(Modifier.height(10.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
SocialCircle(image = R.drawable.ic_facebook) // replace with FB icon
SocialCircle(image = R.drawable.ic_twitter) // replace with Twitter icon
SocialCircle(image = R.drawable.ic_google) // replace with Google icon
SocialCircle(image = R.drawable.ic_apple) // replace with Apple icon
}
Spacer(Modifier.height(14.dp))
Text(
text = buildAnnotatedString {
append("Already have an account? ")
withStyle(SpanStyle(color = Color(0xFF4E62FF), fontWeight = FontWeight.SemiBold)) {
append("Sign in")
}
},
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp),
textAlign = TextAlign.Center,
fontSize = 12.sp,
color = Color(0xFF7B8497)
)
}
}
}
}
@Composable
private fun BackChip(
modifier: Modifier = Modifier,
onBack: () -> Unit
) {
Surface(
modifier = modifier,
shape = CircleShape,
color = Color(0xFF2E3A73).copy(alpha = 0.35f)
) {
Row(
modifier = Modifier
.padding(horizontal = 14.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Default.ArrowBackIosNew,
contentDescription = "Back",
tint = Color.White,
modifier = Modifier.size(16.dp)
)
Spacer(Modifier.width(6.dp))
Text("Back", color = Color.White, fontSize = 13.sp, fontWeight = FontWeight.Medium)
}
}
}
@Composable
private fun RoundedInput(
label: String,
value: String,
onValueChange: (String) -> Unit,
placeholder: String,
leading: @Composable (() -> Unit)? = null,
isPassword: Boolean = false
) {
Column(Modifier.fillMaxWidth()) {
Text(
text = label,
fontSize = 12.sp,
color = Color(0xFF6C7486),
modifier = Modifier.padding(bottom = 6.dp)
)
val shape = RoundedCornerShape(14.dp)
OutlinedTextField(
value = value,
onValueChange = onValueChange,
singleLine = true,
placeholder = { Text(placeholder, fontSize = 12.sp) },
leadingIcon = leading,
visualTransformation = if (isPassword) PasswordVisualTransformation() else VisualTransformation.None,
modifier = Modifier
.fillMaxWidth()
.height(54.dp),
shape = shape,
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color(0xFFB9C2FF),
unfocusedBorderColor = Color(0xFFE7EAF0),
focusedContainerColor = Color(0xFFF8FAFF),
unfocusedContainerColor = Color(0xFFF8FAFF),
cursorColor = Color(0xFF4E62FF),
focusedLeadingIconColor = Color(0xFF7B8497),
unfocusedLeadingIconColor = Color(0xFF9AA3B2)
)
)
}
}
@Composable
private fun SocialCircle(image:Int) {
Icon(
modifier = Modifier
.size(38.dp),
painter = painterResource(image),
contentDescription = "Google",
tint = Color.Unspecified
)
}
Step-by-step explanation
Dependencies Required
Add these to your build.gradle.kts (Module: app):
dependencies {
implementation("androidx.compose.ui:ui:1.5.4")
implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.compose.material:material-icons-extended:1.5.4")
}1) SignUpDesignScreen(...): the main screen container
This is your main composable function. It exposes three callbacks so the UI stays reusable and doesn’t directly depend on navigation or authentication logic.
- onBack: called when user wants to go back
- onSignUp(name, email, pass, accepted): called when user taps Sign up
- onSignIn: for navigating to sign-in screen (you haven’t wired the click yet, but the UI is ready)
Inside the function, you store UI state using Compose state:
- fullName, email, password: String states for text fields
- accepted: Boolean state for the checkbox
Because you used remember { mutableStateOf(...) }, Compose automatically recomposes (redraws) the parts of the UI when values change.
2) Gradient background using Brush.linearGradient
You created a background like this:
- Brush.linearGradient(colors = listOf(...))
Then you apply it to the full screen using:
- Box(modifier = Modifier.fillMaxSize().background(bg))
This gives your auth screen a modern look, similar to many real-world apps.
3) Why the root layout is a Box
A Box is used so you can layer UI elements: the gradient, decorative circles, the back chip, and the main white card.
Using Modifier.align(...) lets each child be positioned precisely without complex nested layouts.
4) Decorative “blobs” (soft circles)
The two extra Box items near the top are clipped with CircleShape and given a semi-transparent white fill
to create soft highlights. This is a simple UI trick that adds depth while keeping the layout lightweight.
5) Back chip component (BackChip)
BackChip is a small reusable UI component made from a circular Surface that contains an icon and label.
For production use, add Modifier.clickable { onBack() } to the Surface or Row so tapping it triggers navigation.
6) Center card using Surface
The main form is placed inside a white Surface with RoundedCornerShape(24.dp).
This creates the popular “floating card” look used in many modern authentication flows.
7) Reusable rounded fields (RoundedInput)
RoundedInput wraps a label + an OutlinedTextField so you don’t repeat styling three times (name, email, password).
Your custom colors are applied via OutlinedTextFieldDefaults.colors(...), which is the Material3 way to control border,
container, cursor, and icon colors. [web:2]
8) Password masking
When isPassword is true, you set PasswordVisualTransformation() to hide typed characters.
This matches the expected behavior for authentication screens and keeps the component reusable.
9) Consent checkbox
The checkbox updates accepted state using onCheckedChange.
The text uses buildAnnotatedString so “Personal data” can be styled differently (semi-bold) for emphasis.
10) Sign up button + callback
The Sign up button triggers onSignUp(fullName, email, password, accepted).
This keeps UI separate from authentication logic (Firebase/Auth API can be implemented outside this composable).
11) Divider + social icons row
The divider row uses two Divider items with weight(1f) and a centered label to create a clean separator.
The social icons are displayed via painterResource(image) with tint = Color.Unspecified to keep original icon colors.
12) “Already have an account? Sign in”
This text is styled so “Sign in” matches the primary theme color.
To make it functional, wrap it with Modifier.clickable { onSignIn() } or use ClickableText to make only “Sign in” clickable.
Quick improvements (recommended)
- Make the Back chip clickable: add
.clickable { onBack() }toBackChip. - Disable Sign up until fields are valid and
accepted == true. - Add keyboard options (Email keyboard type, Password keyboard type) for better UX.
- Add scrolling support using
verticalScrollfor smaller screens.
FAQ (Jetpack Compose Sign up UI)
1) Does this code perform real authentication?
No—this is only the UI layer. The onSignUp(name, email, pass, accepted) callback is where you connect your ViewModel,
Firebase Auth, your backend API, or any auth provider.
2) Why use remember { mutableStateOf() } for inputs?
TextField and Checkbox values must be stored as Compose state so the UI updates automatically when the user types or toggles the checkbox.
Using remember keeps that state across recompositions, which is the standard pattern for local UI state. [web:6]
3) How does the gradient background work?
The background uses Brush.linearGradient(...) and applies it with Modifier.background(bg).
Jetpack Compose provides built-in gradient brushes (linear, radial, sweep, etc.) for backgrounds and drawing. [web:7]
4) How do I change OutlinedTextField border/background colors?
In Material 3, the recommended way is passing custom colors through OutlinedTextFieldDefaults.colors(...)
(focused/unfocused border, container, cursor, icon colors, etc.). [web:2]
5) Why use a Box as the root layout?
Box is ideal for layering: gradient background, decorative blobs, the back chip, and the centered white card.
It’s simpler than nesting multiple layouts when you need overlapping UI.
6) The Back button shows, but it doesn’t click. How to fix?
Add Modifier.clickable { onBack() } to the Surface or Row inside BackChip.
Right now, it’s only UI without a click modifier.
7) How do I make “Sign in” clickable?
Add Modifier.clickable { onSignIn() } to the Text, or use ClickableText if you want only the “Sign in”
part to be clickable.
8) How can I disable the Sign up button until the checkbox is accepted?
Change enabled = true to something like:
enabled = accepted && fullName.isNotBlank() && email.isNotBlank() && password.isNotBlank().
9) How do I add email/password keyboard types?
Use keyboardOptions in OutlinedTextField (for example KeyboardType.Email for email and
KeyboardType.Password for password) to improve typing experience and autofill behavior.
10) Social icons are visible—how do I handle clicks?
Wrap each icon in IconButton or apply Modifier.clickable and trigger actions like Google Sign-In, Facebook login,
or Apple Sign-In from your auth layer.
11) Why set tint = Color.Unspecified for social icons?
Because many brand icons have their own colors. If you apply a tint, Compose recolors the icon; Color.Unspecified preserves
the original drawable colors.
12) How do I handle small screens / keyboard overlap?
Wrap the card content Column in verticalScroll(rememberScrollState()) so the UI can scroll when the keyboard opens,
preventing input fields from being hidden.

0 Comments