coderef

Chipotle

Chipotle's iOS app is built like a foil-wrapped burrito: warm, hand-made, and stripped to the essentials. The canvas is a cream paper tone (#FFF5E1) — not stark white — evoking the kraft-paper bags and butcher-paper liners of the…
DownloadRaw
Chipotle

Build with the Chipotle look.

Palette, type and components from this DESIGN.md, applied to a sample hero.

Palette

At a glance

Theme
light
Typefaces
Archivo, Avenir Next, Archivo Narrow
Sections
8
Length
3,325 words
Platforms
Android, Expo, SwiftUI, DESIGN.md

Add it to your project

Then prompt your agent

Design System · Chipotle

Light and dark previews, tokens and a ready-to-use package, generated from this DESIGN.md. No AI involved: the same file always produces the same output.

Generating design system…
DownloadRaw

Chipotle (iOS) — Jetpack Compose Implementation Guide

Companion to DESIGN.md — the framework-neutral spec. This file ports Chipotle’s visual language to Android with Jetpack Compose (Material 3): a color token object, a Typography set, paste-ready @Composables, Modifiers, 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 (Chipotle’s cream kraft canvas, single brick-red accent, ALL-CAPS condensed headers, the build-your-burrito ingredient stepper, the rewards ring) while making everything idiomatic Android — NavigationBar instead of a UITabBar, dp/sp instead of pt, Compose haptics instead of UIImpactFeedbackGenerator.

Assumes Compose BOM 2024.09+, Kotlin 2.0+, minSdk 24, Material 3, Coil 2.6+ for images.

1. Color Tokens

// ui/theme/ChipotleColors.kt
import androidx.compose.ui.graphics.Color

object ChipotleColors {
    // Canvas & Surfaces
    val Cream     = Color(0xFFFFF5E1)
    val Surface   = Color(0xFFFFFFFF)
    val CreamDeep = Color(0xFFF4E8D0)
    val Divider   = Color(0xFFE8DCC4)

    // Text (brown, never black)
    val TextPrimary   = Color(0xFF451400)
    val TextSecondary = Color(0xFF8A6B4F)
    val TextTertiary  = Color(0xFFB49A82)

    // Brand
    val Red        = Color(0xFFA81612)
    val RedPressed = Color(0xFF8C1210)
    val RedTint    = Color(0xFFF4E2E1)

    // Support / Semantic
    val Tan     = Color(0xFFAC8C5B)
    val Success = Color(0xFF3C7A3C)
    val Spice   = Color(0xFFE07B26)
}

Wire it into a Material 3 lightColorScheme so ripples, dividers, and default component colors inherit the brand. Chipotle is light-only by design; do not provide a dark scheme unless mirroring system dark with identical brand colors.

// ui/theme/Theme.kt
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.MaterialTheme

private val ChipotleScheme = lightColorScheme(
    primary          = ChipotleColors.Red,
    onPrimary        = Color.White,
    background        = ChipotleColors.Cream,
    onBackground      = ChipotleColors.TextPrimary,
    surface           = ChipotleColors.Surface,
    onSurface         = ChipotleColors.TextPrimary,
    surfaceVariant    = ChipotleColors.CreamDeep,
    onSurfaceVariant  = ChipotleColors.TextSecondary,
    outline           = ChipotleColors.Divider,
    error             = ChipotleColors.Red,
)

@Composable
fun ChipotleTheme(content: @Composable () -> Unit) =
    MaterialTheme(colorScheme = ChipotleScheme, typography = ChipotleTypography, content = content)

2. Typography

Headers use Archivo (ALL CAPS); body uses a calm sans (Avenir is iOS-only — substitute the bundled brand body face or fall back to Roboto). Drop the TTFs in res/font/ (lowercase, snake_case).

// ui/theme/ChipotleType.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.text.style.TextStyle as _
import androidx.compose.ui.unit.sp

val Archivo = FontFamily(
    Font(R.font.archivo_bold,      FontWeight.Bold),       // 700
    Font(R.font.archivo_extrabold, FontWeight.ExtraBold),  // 800
)
val BodySans = FontFamily(
    Font(R.font.body_regular, FontWeight.Normal),  // 400 (Avenir substitute)
    Font(R.font.body_bold,    FontWeight.Bold),    // 700
)

// Named ramp — mirrors DESIGN.md §3 exactly (pt → sp 1:1). Apply ALL CAPS at call site.
object ChipotleText {
    val Hero        = TextStyle(Archivo,  fontWeight = FontWeight.ExtraBold, fontSize = 32.sp, lineHeight = 34.sp, letterSpacing = 0.4.sp)
    val ScreenTitle = TextStyle(Archivo,  fontWeight = FontWeight.ExtraBold, fontSize = 26.sp, lineHeight = 29.sp, letterSpacing = 0.3.sp)
    val Section     = TextStyle(Archivo,  fontWeight = FontWeight.Bold,      fontSize = 18.sp, lineHeight = 21.sp, letterSpacing = 0.6.sp)
    val Button      = TextStyle(Archivo,  fontWeight = FontWeight.ExtraBold, fontSize = 16.sp, lineHeight = 18.sp, letterSpacing = 0.8.sp)
    val Points      = TextStyle(Archivo,  fontWeight = FontWeight.ExtraBold, fontSize = 34.sp, lineHeight = 36.sp)
    val Badge       = TextStyle(Archivo,  fontWeight = FontWeight.Bold,      fontSize = 11.sp, lineHeight = 13.sp, letterSpacing = 0.6.sp)

    val ItemName = TextStyle(BodySans, fontWeight = FontWeight.Bold,   fontSize = 17.sp, lineHeight = 22.sp)
    val Price    = TextStyle(BodySans, fontWeight = FontWeight.Bold,   fontSize = 16.sp, lineHeight = 19.sp)
    val Body     = TextStyle(BodySans, fontWeight = FontWeight.Normal, fontSize = 15.sp, lineHeight = 22.sp)
    val Meta     = TextStyle(BodySans, fontWeight = FontWeight.Normal, fontSize = 13.sp, lineHeight = 17.sp)
    val Tab      = TextStyle(BodySans, fontWeight = FontWeight.Bold,   fontSize = 11.sp, lineHeight = 13.sp, letterSpacing = 0.2.sp)
    val Caption  = TextStyle(BodySans, fontWeight = FontWeight.Normal, fontSize = 11.sp, lineHeight = 14.sp)
}

// Map onto Material 3 slots so stock components inherit the brand
val ChipotleTypography = Typography(
    headlineLarge = ChipotleText.ScreenTitle,
    headlineSmall = ChipotleText.Section,
    titleMedium   = ChipotleText.ItemName,
    bodyMedium    = ChipotleText.Body,
    labelSmall    = ChipotleText.Tab,
)

Render prices, calories, and points with tabular figures via TextStyle(fontFeatureSettings = "tnum"). Apply ALL CAPS with text = title.uppercase().

3. Signature Components

Section Header (ALL CAPS)

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun SectionHeader(title: String, modifier: Modifier = Modifier) {
    Column(modifier.padding(top = 24.dp)) {
        Text(title.uppercase(), style = ChipotleText.Section, color = ChipotleColors.TextPrimary)
        Spacer(Modifier.height(8.dp))
        HorizontalDivider(color = ChipotleColors.Divider, thickness = 1.dp)
    }
}

Build-Your-Burrito Ingredient Row

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.ui.Alignment
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback

@Composable
fun IngredientRow(
    name: String,
    meta: String?,
    selected: Boolean,
    multiSelect: Boolean = false,
    onClick: () -> Unit,
) {
    val haptics = LocalHapticFeedback.current
    val shape = if (multiSelect) RoundedCornerShape(6.dp) else CircleShape
    val size = if (multiSelect) 26.dp else 28.dp

    Row(
        Modifier
            .fillMaxWidth()
            .heightIn(min = 60.dp)
            .background(if (selected) ChipotleColors.RedTint else ChipotleColors.Surface)
            .then(if (selected) Modifier.drawLeftRule(ChipotleColors.Red) else Modifier)
            .clickable {
                haptics.performHapticFeedback(HapticFeedbackType.TextHandleMove)
                onClick()
            }
            .padding(horizontal = 16.dp, vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(12.dp),
    ) {
        Column(Modifier.weight(1f)) {
            Text(name, style = ChipotleText.ItemName, color = ChipotleColors.TextPrimary)
            if (meta != null) Text(meta, style = ChipotleText.Meta, color = ChipotleColors.TextSecondary)
        }
        Box(
            Modifier
                .size(size)
                .clip(shape)
                .background(if (selected) ChipotleColors.Red else Color.Transparent)
                .border(2.dp, if (selected) ChipotleColors.Red else ChipotleColors.Divider, shape),
            contentAlignment = Alignment.Center,
        ) {
            AnimatedVisibility(selected, enter = scaleIn(), exit = scaleOut()) {
                Icon(Icons.Filled.Check, contentDescription = null, tint = Color.White,
                    modifier = Modifier.size(16.dp))
            }
        }
    }
}

// Small modifier: draw a 1.5dp red rule on the leading edge of a selected row
fun Modifier.drawLeftRule(color: Color) = drawBehind {
    drawRect(color, size = androidx.compose.ui.geometry.Size(1.5.dp.toPx(), size.height))
}

Primary CTA (ALL CAPS)

import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.ui.draw.scale

@Composable
fun ChipotleCTA(label: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
    val interaction = remember { MutableInteractionSource() }
    val pressed by interaction.collectIsPressedAsState()
    val scale by animateFloatAsState(if (pressed) 0.98f else 1f,
        spring(dampingRatio = 0.8f), label = "ctaScale")
    Box(
        modifier
            .fillMaxWidth()
            .height(54.dp)
            .scale(scale)
            .clip(RoundedCornerShape(8.dp))
            .background(if (pressed) ChipotleColors.RedPressed else ChipotleColors.Red)
            .clickable(interaction, indication = null, onClick = onClick),
        contentAlignment = Alignment.Center,
    ) {
        Text(label.uppercase(), style = ChipotleText.Button, color = Color.White)
    }
}

Rewards Points Ring

import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.Canvas
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke

@Composable
fun RewardsRing(points: Int, goal: Int, modifier: Modifier = Modifier) {
    val target = (points.toFloat() / goal).coerceAtMost(1f)
    val sweep = remember { Animatable(0f) }
    LaunchedEffect(target) { sweep.animateTo(target, tween(700, easing = FastOutSlowInEasing)) }

    Box(modifier.size(120.dp), contentAlignment = Alignment.Center) {
        Canvas(Modifier.fillMaxSize()) {
            val sw = 10.dp.toPx()
            drawArc(ChipotleColors.Divider, 0f, 360f, false,
                style = Stroke(sw))
            drawArc(ChipotleColors.Red, -90f, 360f * sweep.value, false,
                style = Stroke(sw, cap = StrokeCap.Round))
        }
        Column(horizontalAlignment = Alignment.CenterHorizontally) {
            Text("%,d".format(points),
                style = ChipotleText.Points.copy(fontFeatureSettings = "tnum"),
                color = ChipotleColors.Red)
            Text("POINTS", style = ChipotleText.Badge, color = ChipotleColors.TextSecondary)
        }
    }
}

Step Progress Bar

@Composable
fun StepProgress(total: Int, current: Int, modifier: Modifier = Modifier) {
    Row(
        modifier.fillMaxWidth().padding(horizontal = 16.dp),
        horizontalArrangement = Arrangement.spacedBy(2.dp),
    ) {
        repeat(total) { i ->
            val color by animateColorAsState(
                if (i <= current) ChipotleColors.Red else ChipotleColors.Divider,
                tween(250), label = "seg$i",
            )
            Box(
                Modifier
                    .weight(1f)
                    .height(4.dp)
                    .clip(CircleShape)
                    .background(color),
            )
        }
    }
}

4. Build-Flow State

data class OrderBuild(
    val rice: String? = null,             // single-select
    val beans: String? = null,            // single-select
    val protein: String? = null,          // single-select
    val toppings: Set<String> = emptySet(), // multi-select
    val salsas: Set<String> = emptySet(),   // multi-select
    val extras: Set<String> = emptySet(),   // each adds a "+$2.00" meta tag
) {
    val runningTotal: Double get() = 7.65 + extras.size * 2.0  // base + extras
}

5. Bottom Navigation (Tab Bar)

Use Material 3 NavigationBar. Chipotle’s iOS tab bar is an opaque white surface with a hairline divider and a red active tint.

import androidx.compose.material.icons.filled.*

@Composable
fun ChipotleBottomBar(selected: Int, onSelect: (Int) -> Unit) {
    NavigationBar(containerColor = ChipotleColors.Surface, tonalElevation = 0.dp) {
        val items = listOf(
            "Order"    to Icons.Filled.Restaurant,
            "Scan"     to Icons.Filled.QrCode,
            "Delivery" to Icons.Filled.DirectionsBike,
            "More"     to Icons.Filled.MoreHoriz,
        )
        items.forEachIndexed { i, (label, icon) ->
            NavigationBarItem(
                selected = selected == i,
                onClick = { onSelect(i) },
                icon = { Icon(icon, contentDescription = label, modifier = Modifier.size(24.dp)) },
                label = { Text(label.uppercase(), style = ChipotleText.Tab) },
                colors = NavigationBarItemDefaults.colors(
                    selectedIconColor = ChipotleColors.Red,
                    selectedTextColor = ChipotleColors.Red,
                    unselectedIconColor = ChipotleColors.TextSecondary,
                    unselectedTextColor = ChipotleColors.TextSecondary,
                    indicatorColor = ChipotleColors.RedTint,
                ),
            )
        }
    }
}

The persistent “ADD TO BAG” footer sits above the bottom bar during the build flow — render ChipotleCTA (with the running total appended) in the Scaffold bottomBar slot stacked above ChipotleBottomBar, on a ChipotleColors.Surface band with a top hairline.

6. Motion

Moment Compose recipe
Ingredient select check AnimatedVisibility scaleIn()/scaleOut(); row bg crossfade; HapticFeedbackType.TextHandleMove
Step progress slide animateColorAsState(tween(250)) per segment as current advances
Rewards ring fill Animatable 0 → target tween(700, FastOutSlowInEasing) driving drawArc sweep; number counts
Add to Bag CTA press Modifier.scale 1 → 0.98; bag badge spring 1 → 1.2 → 1; success haptic
Card tap Modifier.scale 1 → 0.98 via collectIsPressedAsState()
Sheet present ModalBottomSheet default slide; scrim ChipotleColors.TextPrimary.copy(alpha = 0.40f)
// Bag-count badge bounce after add
val badge = remember { Animatable(1f) }
LaunchedEffect(bagCount) {
    if (bagCount > 0) { badge.animateTo(1.2f, spring(dampingRatio = 0.5f)); badge.animateTo(1f, spring()) }
}

Haptics: prefer LocalHapticFeedback. For a softer “select” tick on ingredient taps, LocalView.current.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) (API 30+) approximates iOS .light impact.

7. Icons

Chipotle ships custom glyphs; the closest first-party set is androidx.compose.material:material-icons-extended. For exact parity, export Chipotle’s glyphs as vector drawables and load via ImageVector.vectorResource(R.drawable.…).

Purpose SF Symbol (iOS) Material Icon (Compose)
Selected check checkmark Icons.Filled.Check
Order (tab) fork.knife Icons.Filled.Restaurant
Scan (tab) qrcode Icons.Filled.QrCode
Delivery (tab) bicycle Icons.Filled.DirectionsBike
More (tab) ellipsis Icons.Filled.MoreHoriz
Bag bag / bag.fill Icons.Filled.ShoppingBag
Search magnifyingglass Icons.Filled.Search
Location chevron chevron.down Icons.Filled.KeyboardArrowDown
Quantity minus / plus minus / plus Icons.Filled.Remove / Icons.Filled.Add
Rewards / star star.fill Icons.Filled.Star
Spicy flame.fill Icons.Filled.LocalFireDepartment
Back chevron.left Icons.Filled.ArrowBackIosNew

8. Minimum SDK & Accessibility Notes

  • minSdk 24 (Compose floor is 21; modern motion + drawArc are comfortable at 24). compileSdk 34+, Compose BOM 2024.09+, Kotlin 2.0+.
  • Edge-to-edge: call enableEdgeToEdge() in the Activity; the cream canvas wants dark system-bar icons via WindowCompat. Apply Scaffold insets so the “ADD TO BAG” footer clears the gesture nav.
  • Font scaling: sp honors the user’s font scale automatically — keep it on item names and body. ALL-CAPS condensed headers absorb growth; pin tab labels and step segments via dp-derived sizing or CompositionLocalProvider(LocalDensity provides Density(density, fontScale = 1f)).
  • TalkBack: ingredient rows use Modifier.semantics { role = Role.Checkbox /* or RadioButton */; stateDescription = if (selected) "Selected" else "Not selected" }; merge the name + meta; the CTA announces “Add to bag, total $X”.
  • Touch targets: Material guidance is 48.dp minimum. Ingredient rows are 60.dp (clear); ensure the quantity − / + glyphs get 48.dp hit areas via padding.
  • Contrast: espresso #451400 on cream #FFF5E1 is very high contrast. #8A6B4F on cream passes WCAG AA at 13sp+ — validate 11sp captions and darken toward #6E5238 for strict compliance.
  • Dynamic color: do not enable Material You dynamicLightColorScheme() — Chipotle’s brand requires the fixed cream canvas, brick-red accent, and brown text regardless of wallpaper.
All mobile designs →

Brand names describe the visual style only; coderef is not affiliated with or endorsed by any brand listed. Licenses