Zomato (iOS) — Jetpack Compose Implementation Guide
Companion to DESIGN.md — the framework-neutral spec. This file ports Zomato’s visual language to Android with Jetpack Compose (Material 3): a color token object, a Typography set, the semantic rating pill, the veg/non-veg mark, the bordered ADD button, the Delivery/Dining toggle, motion, and haptics.
Why a Compose guide for an iOS-referenced app? The DESIGN.md tokens are platform-neutral. This file keeps the visual identity (white canvas, Zomato Red, the semantic rating pill, the veg/non-veg mark, the bordered ADD, the Delivery/Dining toggle) while making everything idiomatic Android —
NavigationBarinstead of a UITabBar,ModalBottomSheetinstead of a UIKit sheet,sp/dpinstead ofpt.
Assumes Compose BOM 2024.09+, Kotlin 2.0+, minSdk 24, Material 3, and Coil 2.6+ for hero photos and dish thumbnails. Zomato’s palette is a fixed brand + semantic rating scale, so dynamic color extraction (Palette / Material You) is not used.
1. Color Tokens
// ui/theme/ZomatoColors.kt
import androidx.compose.ui.graphics.Color
object ZomatoColors {
// Brand (interactive)
val Red = Color(0xFFE23744)
val RedPressed = Color(0xFFC42531)
// Rating scale (semantic)
val RatingGreen = Color(0xFF267E3E)
val RatingAmber = Color(0xFFDB7C38)
val RatingRed = Color(0xFFE23744) // = brand
val RatingGrey = Color(0xFF9C9C9C)
// Membership & dining
val Gold = Color(0xFFEFC75E)
val DiningBlue = Color(0xFF256FEF)
// Canvas & Surfaces (Light)
val Canvas = Color(0xFFFFFFFF)
val Surface1 = Color(0xFFF4F4F5)
val Surface2 = Color(0xFFEBEBEC)
val Divider = Color(0xFFE8E8E8)
// Canvas & Surfaces (Dark)
val DarkCanvas = Color(0xFF121212) // warm near-black — NOT pure black
val DarkSurface1 = Color(0xFF1C1C1E)
val DarkSurface2 = Color(0xFF262629)
val DarkDivider = Color(0xFF2C2C2E)
// Text
val TextPrimary = Color(0xFF1C1C1C)
val TextSecondary = Color(0xFF696969)
val TextTertiary = Color(0xFF9C9C9C)
val DarkTextPrimary = Color(0xFFF2F2F2)
// Veg / non-veg
val VegGreen = Color(0xFF267E3E)
val NonVegMaroon = Color(0xFFB91C1C)
// Semantic
val Error = Color(0xFFE03A3A)
}
// Semantic rating → fill color
fun ratingFill(score: Double): Color = when {
score >= 4.0 -> ZomatoColors.RatingGreen
score >= 3.0 -> ZomatoColors.RatingAmber
score > 0.0 -> ZomatoColors.RatingRed
else -> ZomatoColors.RatingGrey
}
Wire it into both schemes. Zomato is light-first (white, photo-forward, dense); the dark scheme uses a warm #121212, never true black.
// ui/theme/Theme.kt
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.MaterialTheme
private val ZomatoLight = lightColorScheme(
primary = ZomatoColors.Red,
onPrimary = Color.White,
background = ZomatoColors.Canvas,
onBackground = ZomatoColors.TextPrimary,
surface = ZomatoColors.Surface1,
onSurface = ZomatoColors.TextPrimary,
surfaceVariant = ZomatoColors.Surface2,
outline = ZomatoColors.Divider,
error = ZomatoColors.Error,
)
private val ZomatoDark = darkColorScheme(
primary = ZomatoColors.Red,
onPrimary = Color.White,
background = ZomatoColors.DarkCanvas,
onBackground = ZomatoColors.DarkTextPrimary,
surface = ZomatoColors.DarkSurface1,
onSurface = ZomatoColors.DarkTextPrimary,
surfaceVariant = ZomatoColors.DarkSurface2,
outline = ZomatoColors.DarkDivider,
error = ZomatoColors.Error,
)
@Composable
fun ZomatoTheme(
dark: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit,
) = MaterialTheme(
colorScheme = if (dark) ZomatoDark else ZomatoLight,
typography = ZomatoTypography,
content = content,
)
2. Typography (Material 3)
Zomato’s brand face is a custom Okra-family grotesque (proprietary); use Inter (SIL OFL) — drop the TTFs in res/font/. The app is dense and heavy: names 800, screen titles 900; numbers tabular.
// ui/theme/ZomatoType.kt
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
val Inter = FontFamily(
Font(R.font.inter_regular, FontWeight.Normal),
Font(R.font.inter_medium, FontWeight.Medium),
Font(R.font.inter_semibold, FontWeight.SemiBold),
Font(R.font.inter_bold, FontWeight.Bold),
Font(R.font.inter_extrabold, FontWeight.ExtraBold),
Font(R.font.inter_black, FontWeight.Black),
)
// Named ramp — mirrors DESIGN.md §3 exactly (pt → sp 1:1)
object ZomatoText {
val ScreenTitle = TextStyle(Inter, fontWeight = FontWeight.Black, fontSize = 32.sp, lineHeight = 37.sp, letterSpacing = (-0.5).sp)
val RestaurantHero = TextStyle(Inter, fontWeight = FontWeight.ExtraBold, fontSize = 24.sp, lineHeight = 29.sp, letterSpacing = (-0.4).sp)
val Section = TextStyle(Inter, fontWeight = FontWeight.ExtraBold, fontSize = 20.sp, lineHeight = 26.sp, letterSpacing = (-0.3).sp)
val MenuCategory = TextStyle(Inter, fontWeight = FontWeight.ExtraBold, fontSize = 17.sp, lineHeight = 22.sp, letterSpacing = (-0.2).sp)
val ItemName = TextStyle(Inter, fontWeight = FontWeight.Bold, fontSize = 15.sp, lineHeight = 20.sp)
val Body = TextStyle(Inter, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 20.sp)
val Price = TextStyle(Inter, fontWeight = FontWeight.Bold, fontSize = 14.sp, lineHeight = 18.sp)
val Meta = TextStyle(Inter, fontWeight = FontWeight.Normal, fontSize = 13.sp, lineHeight = 18.sp)
val RatingPill = TextStyle(Inter, fontWeight = FontWeight.ExtraBold, fontSize = 14.sp, lineHeight = 14.sp)
val RatingCaption = TextStyle(Inter, fontWeight = FontWeight.Normal, fontSize = 11.sp, lineHeight = 14.sp)
val Tag = TextStyle(Inter, fontWeight = FontWeight.ExtraBold, fontSize = 11.sp, lineHeight = 11.sp, letterSpacing = 0.4.sp)
val Button = TextStyle(Inter, fontWeight = FontWeight.ExtraBold, fontSize = 16.sp, lineHeight = 16.sp)
val AddLabel = TextStyle(Inter, fontWeight = FontWeight.ExtraBold, fontSize = 14.sp, lineHeight = 14.sp, letterSpacing = 0.3.sp)
val Tab = TextStyle(Inter, fontWeight = FontWeight.SemiBold, fontSize = 10.sp, lineHeight = 10.sp, letterSpacing = 0.1.sp)
val Caption = TextStyle(Inter, fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 16.sp)
}
// Map onto Material 3 slots so stock components inherit the brand
val ZomatoTypography = Typography(
headlineLarge = ZomatoText.ScreenTitle,
headlineMedium = ZomatoText.RestaurantHero,
titleLarge = ZomatoText.Section,
titleMedium = ZomatoText.MenuCategory,
bodyMedium = ZomatoText.Body,
labelSmall = ZomatoText.Tab,
)
For tabular figures on prices/ratings, use TextStyle.copy(fontFeatureSettings = "tnum").
3. Signature Components
Semantic Rating Pill
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
@Composable
fun RatingPill(score: Double, count: String? = null) {
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(5.dp)) {
Row(
Modifier.clip(RoundedCornerShape(7.dp)).background(ratingFill(score))
.padding(horizontal = 9.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Icon(Icons.Filled.Star, null, tint = Color.White, modifier = Modifier.size(11.dp))
Text(String.format("%.1f", score),
style = ZomatoText.RatingPill.copy(fontFeatureSettings = "tnum"), color = Color.White)
}
if (count != null) {
Text(count, style = ZomatoText.RatingCaption,
color = ZomatoColors.TextSecondary, textDecoration = TextDecoration.Underline)
}
}
}
Veg / Non-Veg Mark
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Path
@Composable
fun VegMark(isVeg: Boolean) {
val c = if (isVeg) ZomatoColors.VegGreen else ZomatoColors.NonVegMaroon
Box(
Modifier.size(16.dp).border(1.5.dp, c, RoundedCornerShape(3.dp)),
contentAlignment = Alignment.Center,
) {
if (isVeg) {
Box(Modifier.size(7.dp).clip(RoundedCornerShape(50)).background(c))
} else {
Canvas(Modifier.size(9.dp)) {
val p = Path().apply {
moveTo(size.width / 2f, 0f)
lineTo(size.width, size.height)
lineTo(0f, size.height)
close()
}
drawPath(p, c)
}
}
}
}
Menu Item Row (bordered ADD → stepper)
import androidx.compose.foundation.clickable
import androidx.compose.foundation.shadow
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Remove
import androidx.compose.material.icons.filled.Star
import androidx.compose.runtime.*
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.unit.IntOffset
import coil.compose.AsyncImage
@Composable
fun MenuItemRow(
isVeg: Boolean, bestseller: Boolean, name: String, price: String,
rating: Double, ratingCount: Int, imageUrl: String,
) {
var qty by remember { mutableIntStateOf(0) }
val haptics = LocalHapticFeedback.current
fun tick(n: Int) { haptics.performHapticFeedback(HapticFeedbackType.TextHandleMove); qty = n }
Row(
Modifier.fillMaxWidth().padding(vertical = 14.dp),
horizontalArrangement = Arrangement.spacedBy(14.dp),
) {
Column(Modifier.weight(1f)) {
VegMark(isVeg)
if (bestseller) {
Row(Modifier.padding(top = 7.dp, bottom = 3.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
Icon(Icons.Filled.Star, null, tint = ZomatoColors.RatingAmber, modifier = Modifier.size(9.dp))
Text("Bestseller", style = ZomatoText.Tag, color = ZomatoColors.RatingAmber)
}
}
Text(name, style = ZomatoText.ItemName, color = ZomatoColors.TextPrimary,
modifier = if (bestseller) Modifier else Modifier.padding(top = 7.dp))
Text(price, style = ZomatoText.Price, color = ZomatoColors.TextPrimary,
modifier = Modifier.padding(top = 5.dp))
Row(Modifier.padding(top = 6.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
Icon(Icons.Filled.Star, null, tint = ZomatoColors.RatingGreen, modifier = Modifier.size(10.dp))
Text("${"%.1f".format(rating)} ($ratingCount)",
style = ZomatoText.Caption, color = ZomatoColors.TextSecondary)
}
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage(model = imageUrl, contentDescription = null,
modifier = Modifier.size(width = 96.dp, height = 90.dp).clip(RoundedCornerShape(12.dp)),
contentScale = ContentScale.Crop)
Box(
Modifier
.offset { IntOffset(0, -16.dp.roundToPx()) } // overlaps the thumbnail
.shadow(8.dp, RoundedCornerShape(10.dp))
.clip(RoundedCornerShape(10.dp))
.background(ZomatoColors.Canvas)
.border(1.dp, ZomatoColors.Red, RoundedCornerShape(10.dp)),
) {
if (qty == 0) {
Text("ADD", style = ZomatoText.AddLabel, color = ZomatoColors.Red,
modifier = Modifier.clickable { qty = 1 }
.padding(horizontal = 22.dp, vertical = 8.dp))
} else {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Filled.Remove, "Decrease", tint = ZomatoColors.Red,
modifier = Modifier.size(36.dp).clickable { if (qty > 1) tick(qty - 1) else qty = 0 }
.padding(11.dp))
Text("$qty", style = ZomatoText.ItemName.copy(fontFeatureSettings = "tnum"),
color = ZomatoColors.Red, modifier = Modifier.widthIn(min = 24.dp))
Icon(Icons.Filled.Add, "Increase", tint = ZomatoColors.Red,
modifier = Modifier.size(36.dp).clickable { tick(qty + 1) }.padding(11.dp))
}
}
}
}
}
HorizontalDivider(color = ZomatoColors.Divider, thickness = 1.dp)
}
Delivery / Dining Out Toggle
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.layout.fillMaxWidth
@Composable
fun DeliveryDiningToggle(isDelivery: Boolean, onChange: (Boolean) -> Unit) {
Row(
Modifier.fillMaxWidth().clip(RoundedCornerShape(50))
.background(ZomatoColors.Surface1).padding(4.dp),
) {
Seg("Delivery", isDelivery, Modifier.weight(1f)) { onChange(true) }
Seg("Dining Out", !isDelivery, Modifier.weight(1f)) { onChange(false) }
}
}
@Composable
private fun Seg(label: String, active: Boolean, modifier: Modifier, onClick: () -> Unit) {
Box(
modifier.clip(RoundedCornerShape(50))
.background(if (active) ZomatoColors.Red else Color.Transparent)
.clickable { onClick() }
.padding(vertical = 9.dp),
contentAlignment = Alignment.Center,
) {
Text(label,
style = ZomatoText.RatingPill.copy(fontSize = 13.sp, fontWeight = FontWeight.Bold),
color = if (active) Color.White else ZomatoColors.TextSecondary)
}
}
Restaurant Detail Header (hero + pull-up card)
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.icons.filled.Search
@Composable
fun RestaurantDetailHeader(
imageUrl: String, name: String, meta: String,
deliveryRating: Double, diningRating: Double,
) {
Column {
Box(Modifier.fillMaxWidth().height(200.dp)) {
AsyncImage(model = imageUrl, contentDescription = name,
modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
Row(
Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, top = 14.dp),
verticalAlignment = Alignment.CenterVertically,
) {
HeroBtn(Icons.Filled.ArrowBack)
Spacer(Modifier.weight(1f))
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
HeroBtn(Icons.Filled.Search)
HeroBtn(Icons.Filled.FavoriteBorder)
}
}
}
Column(
Modifier.fillMaxWidth()
.offset { IntOffset(0, -22.dp.roundToPx()) }
.clip(RoundedCornerShape(topStart = 22.dp, topEnd = 22.dp))
.background(ZomatoColors.Canvas)
.padding(horizontal = 18.dp).padding(top = 20.dp),
) {
Text(name, style = ZomatoText.RestaurantHero, color = ZomatoColors.TextPrimary)
Text(meta, style = ZomatoText.Meta, color = ZomatoColors.TextSecondary,
modifier = Modifier.padding(top = 5.dp))
Row(Modifier.padding(top = 14.dp), horizontalArrangement = Arrangement.spacedBy(18.dp)) {
RatingPill(deliveryRating, "12.4K Delivery")
RatingPill(diningRating, "3.1K Dining")
}
}
}
}
@Composable
private fun HeroBtn(icon: androidx.compose.ui.graphics.vector.ImageVector) {
Box(
Modifier.size(34.dp).clip(RoundedCornerShape(50)).background(Color.Black.copy(alpha = 0.4f)),
contentAlignment = Alignment.Center,
) { Icon(icon, null, tint = Color.White, modifier = Modifier.size(17.dp)) }
}
Primary Button
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.ui.draw.scale
@Composable
fun ZButton(title: String, onClick: () -> Unit) {
val interaction = remember { MutableInteractionSource() }
val pressed by interaction.collectIsPressedAsState()
Box(
Modifier.fillMaxWidth().heightIn(min = 52.dp)
.scale(if (pressed) 0.98f else 1f)
.clip(RoundedCornerShape(12.dp))
.background(if (pressed) ZomatoColors.RedPressed else ZomatoColors.Red)
.clickable(interaction, indication = null) { onClick() },
contentAlignment = Alignment.Center,
) { Text(title, style = ZomatoText.Button, color = Color.White) }
}
// Embeds price: ZButton("Add item · ₹320", onClick = {})
4. Navigation
Zomato has a 4-tab bottom strip and no Material tint pill — active is just Zomato Red.
import androidx.compose.material.icons.filled.*
@Composable
fun ZomatoBottomBar(selected: Int, onSelect: (Int) -> Unit) {
NavigationBar(containerColor = ZomatoColors.Canvas, tonalElevation = 0.dp) {
val items = listOf(
"Delivery" to Icons.Filled.Home,
"Dining" to Icons.Filled.Restaurant,
"Live" to Icons.Filled.GraphicEq,
"Profile" to Icons.Filled.AccountCircle,
)
items.forEachIndexed { i, (label, icon) ->
NavigationBarItem(
selected = selected == i,
onClick = { onSelect(i) },
icon = { Icon(icon, contentDescription = label, modifier = Modifier.size(22.dp)) },
label = { Text(label, style = ZomatoText.Tab) },
colors = NavigationBarItemDefaults.colors(
selectedIconColor = ZomatoColors.Red,
selectedTextColor = ZomatoColors.Red,
unselectedIconColor = Color(0xFF888888),
unselectedTextColor = Color(0xFF888888),
indicatorColor = Color.Transparent, // no Material pill — Zomato has none
),
)
}
}
}
The customize, cart and address surfaces are ModalBottomSheet with a 22.dp top radius (matching the detail pull-up card). The rider map uses Google Maps Compose with a Zomato-Red route + rider marker; the sticky cart bar floats above the menu with the item count + total embedded in a ZButton.
5. Motion
Zomato motion is functional — the rating color is the signal, not animation.
| Moment | Compose recipe |
|---|---|
| ADD → stepper | AnimatedContent swapping the ADD label for the stepper tween(180); cart bar slideInVertically tween(280) + HapticFeedbackType.LongPress |
| Quantity change | Crossfade / AnimatedContent on the count + TextHandleMove haptic |
| Delivery/Dining toggle | active fill animateDpAsState x-offset spring(dampingRatio = 0.85f); content Crossfade(tween(200)) |
| Hero pull-up parallax | LazyColumn + nestedScroll; hero graphicsLayer { translationY = scroll * 0.5f }; nav alpha animates as the hero scrolls away |
| Card → detail | shared element via SharedTransitionLayout (Compose 1.7+) tween(320) |
| Rating pill appear | fadeIn + slight scaleIn tween(150) — color is the signal, keep it subtle |
| Sheet present | ModalBottomSheet default slide; scrim fades in parallel |
| Order tracking advance | status node animateDpAsState tween(250) + HapticFeedbackType.LongPress (medium analog) |
// ADD → stepper — the canonical Zomato motion
AnimatedContent(targetState = qty == 0, label = "addToStepper",
transitionSpec = { fadeIn(tween(180)) togetherWith fadeOut(tween(120)) }) { isAdd ->
if (isAdd) /* ADD label */ else /* bordered red stepper */
}
Haptics: HapticFeedbackType.TextHandleMove for the stepper tick; HapticFeedbackType.LongPress for ADD / stage-complete. Order-placed success can use VibrationEffect.createPredefined(EFFECT_HEAVY_CLICK) via the system Vibrator.
6. Icons
The closest first-party set to Zomato’s iconography is androidx.compose.material:material-icons-extended. The veg/non-veg mark is custom-drawn (Canvas), not an icon.
| Purpose | SF Symbol (iOS) | Material Icon (Compose) |
|---|---|---|
| Delivery (tab) | house / house.fill |
Icons.Filled.Home |
| Dining (tab) | fork.knife |
Icons.Filled.Restaurant |
| Live (tab) | dot.radiowaves.left.and.right |
Icons.Filled.GraphicEq |
| Profile (tab) | person.crop.circle |
Icons.Filled.AccountCircle |
| Rating star | star.fill |
Icons.Filled.Star |
| Hero back | chevron.left |
Icons.Filled.ArrowBack |
| Hero search | magnifyingglass |
Icons.Filled.Search |
| Hero save | heart / heart.fill |
Icons.Filled.FavoriteBorder / Icons.Filled.Favorite |
| ADD minus | minus |
Icons.Filled.Remove |
| ADD plus | plus |
Icons.Filled.Add |
| Share | square.and.arrow.up |
Icons.Filled.IosShare |
| Dining — book | calendar |
Icons.Filled.CalendarMonth |
| Offer | tag.fill |
Icons.Filled.LocalOffer |
| Pro / Gold | crown.fill |
Icons.Filled.WorkspacePremium |
| Rider (tracking) | bicycle |
Icons.Filled.DirectionsBike |
7. Minimum SDK & Accessibility Notes
- minSdk 24 (Compose floor is 21; modern motion and
SharedTransitionLayoutare comfortable at 24).compileSdk 34+, Compose BOM2024.09+, Kotlin2.0+. - Edge-to-edge: call
enableEdgeToEdge(); the white canvas wants dark-content system bars (light-content over the hero photo and in dark mode). The hero nav scrims respect the camera cutout; the bottom tab + sticky cart bar useModifier.navigationBarsPadding(). - Semantic rating: always derive the pill fill from
ratingFill(score)— never hard-code one color. The color carrying the score’s quality is the core of Zomato. - Veg/non-veg is not color-only: the
VegMarkmust carry acontentDescription(“Vegetarian”/“Non-vegetarian”); it is legally significant in India and must not depend on color perception. - Font scaling:
sphonors the user’s font scale — keep it on screen titles, restaurant names, body and prices. Pin layout-sensitive text (the rating pill, veg mark, 11sp Bestseller tag, 10sp tab labels, the ADD label) by deriving fromdporCompositionLocalProvider(LocalDensity provides Density(density, fontScale = 1f)). - Tabular numerals: apply
fontFeatureSettings = "tnum"to ratings, prices, review counts and quantity counters. - TalkBack: the rating pill announces score and quality (“4.3 stars, rated great”); the menu row is one focusable node combining veg status + name + price + rating; the ADD button is “Add {dish}” and after tap becomes a stepper with state.
- Touch targets: Material guidance is 48.dp. Give the ADD button and the 36.dp stepper controls a 48.dp hit area via padding; the segmented toggle halves are ≥ 44.dp tall; primary buttons ≥ 52.dp.
- Contrast:
#1C1C1Con#FFFFFFpasses WCAG AAA; white on the rating greens/ambers/reds passes AA for the pill score; the semantic scale is always paired with the star + an accessible label so it is never color-only. - Reduce motion: when
Settings.Global.ANIMATOR_DURATION_SCALE == 0, disable the ADD→stepper transition and the toggle’s sliding fill (substitute aCrossfade); keep the rating pill static. - Dark mode: invert via the
Dark*palette —#121212, NOT true black;#1C1C1Ctext becomes#F2F2F2. Brand red, the full rating scale, veg colors, gold and dining blue are constant so a pill’s color always means the same thing. Shadows are nearly invisible on dark, so add a 0.5dpDarkDividerborder on floating surfaces (ADD button, sticky cart). Do not enable Material YoudynamicColorScheme()— Zomato’s red identity and the semantic rating scale must hold regardless of wallpaper.