coderef

United

The United iOS app is a deep-navy, business-class-feeling travel tool that organizes itself around the journey detail — not just "where am I going" but the full anatomy of the flight: aircraft type, duration, terminals, gates, your Economy…
DownloadRaw
United

Build with the United look.

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

Palette

At a glance

Theme
dark
Typefaces
Open Sans, SF Mono
Sections
7
Length
4,084 words
Platforms
Android, Expo, SwiftUI, DESIGN.md

Add it to your project

Then prompt your agent

Design System · United

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

United (iOS) — Jetpack Compose Implementation Guide

Companion to DESIGN.md — the framework-neutral spec. This file ports United’s visual language to Android with Jetpack Compose (Material 3): a color token object, a Typography set, paste-ready @Composables for the boarding pass + interactive seat map, navigation, 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 (the two-blue United Blue / Rhapsody Blue brand, the perforated boarding pass, the interactive seat map, refined functional motion) while making everything idiomatic Android — NavigationBar instead of a UITabBar, sp/dp instead of pt.

Assumes Compose BOM 2024.09+, Kotlin 2.0+, minSdk 24, Material 3, and Coil 2.6+. No color extraction — United’s palette is a fixed two-blue brand system. United is light-mode-first; a full deep-navy dark scheme is provided.

1. Color Tokens (Compose + M3)

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

object UAColors {
    // Brand
    val Blue          = Color(0xFF002244)
    val BlueMid       = Color(0xFF1B3D6E)
    val Rhapsody      = Color(0xFF1414FF)
    val RhapsodyPress = Color(0xFF0E0EC8)
    val RhapsodyBright = Color(0xFF4A4AFF)
    val PremierGold   = Color(0xFFC2A14D)
    val PremierGoldBright = Color(0xFFD8B863)

    // Canvas & Surfaces (Light)
    val Canvas         = Color(0xFFFFFFFF)
    val SurfaceGray    = Color(0xFFF3F5FA)
    val SurfacePressed = Color(0xFFE5EAF3)
    val DividerLight   = Color(0xFFD4DCE9)

    // Canvas & Surfaces (Dark) — deep navy, NOT pure black
    val DarkCanvas   = Color(0xFF0A1424)
    val DarkSurface1 = Color(0xFF111E33)
    val DarkSurface2 = Color(0xFF1A2A45)
    val DarkDivider  = Color(0xFF243652)

    // Text
    val TextPrimary       = Color(0xFF0C1F38)
    val TextSecondary     = Color(0xFF566884)
    val TextTertiary      = Color(0xFF8493AC)
    val DarkTextPrimary   = Color(0xFFE9EEF6)
    val DarkTextSecondary = Color(0xFF97A6BE)
    val DarkTextTertiary  = Color(0xFF5E7090)

    // Flight status (functional, constant)
    val OnTime       = Color(0xFF1E8E5A)
    val OnTimeDark   = Color(0xFF4FD18C)
    val Boarding     = Color(0xFF1414FF)
    val BoardingDark = Color(0xFF7A7AFF)
    val Delayed      = Color(0xFFC98A1E)
    val DelayedDark  = Color(0xFFE0A943)
    val Canceled     = Color(0xFFD8434F)
    val CanceledDark = Color(0xFFF0808A)
}

Wire it into both schemes. United is light-first; the dark scheme uses the signature #0A1424 deep navy, never true black, so the brand navy survives.

// 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 UALight = lightColorScheme(
    primary        = UAColors.Rhapsody,   // action accent
    onPrimary      = Color.White,
    secondary      = UAColors.Blue,       // brand/surface
    onSecondary    = Color.White,
    background     = UAColors.Canvas,
    onBackground   = UAColors.TextPrimary,
    surface        = UAColors.SurfaceGray,
    onSurface      = UAColors.TextPrimary,
    surfaceVariant = UAColors.SurfacePressed,
    outline        = UAColors.DividerLight,
    error          = UAColors.Canceled,
)

private val UADark = darkColorScheme(
    primary        = UAColors.Rhapsody,
    onPrimary      = Color.White,
    secondary      = UAColors.BlueMid,
    onSecondary    = Color.White,
    background     = UAColors.DarkCanvas,
    onBackground   = UAColors.DarkTextPrimary,
    surface        = UAColors.DarkSurface1,
    onSurface      = UAColors.DarkTextPrimary,
    surfaceVariant = UAColors.DarkSurface2,
    outline        = UAColors.DarkDivider,
    error          = UAColors.CanceledDark,
)

@Composable
fun UATheme(
    dark: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit,
) = MaterialTheme(
    colorScheme = if (dark) UADark else UALight,
    typography  = UATypography,
    content     = content,
)

2. Typography (M3)

United uses a clean humanist corporate sans; Open Sans (Apache 2.0) is the closest free analog. Drop the TTFs in res/font/. Data is bold and big; the weight ladder is 400/600/700/800.

// ui/theme/UAType.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 OpenSans = FontFamily(
    Font(R.font.opensans_regular,   FontWeight.Normal),
    Font(R.font.opensans_medium,    FontWeight.Medium),
    Font(R.font.opensans_semibold,  FontWeight.SemiBold),
    Font(R.font.opensans_bold,      FontWeight.Bold),
    Font(R.font.opensans_extrabold, FontWeight.ExtraBold),
)

object UAText {
    val RouteIATA   = TextStyle(OpenSans, fontWeight = FontWeight.ExtraBold, fontSize = 32.sp, lineHeight = 34.sp, letterSpacing = (-0.5).sp)
    val ScreenTitle = TextStyle(OpenSans, fontWeight = FontWeight.ExtraBold, fontSize = 26.sp, lineHeight = 30.sp, letterSpacing = (-0.3).sp)
    val Section     = TextStyle(OpenSans, fontWeight = FontWeight.Bold,      fontSize = 22.sp, lineHeight = 26.sp, letterSpacing = (-0.2).sp)
    val PaxName     = TextStyle(OpenSans, fontWeight = FontWeight.Bold,      fontSize = 18.sp, lineHeight = 22.sp)
    val DataLarge   = TextStyle(OpenSans, fontWeight = FontWeight.ExtraBold, fontSize = 22.sp, lineHeight = 24.sp)
    val Body        = TextStyle(OpenSans, fontWeight = FontWeight.Normal,    fontSize = 16.sp, lineHeight = 24.sp)
    val DataInline  = TextStyle(OpenSans, fontWeight = FontWeight.SemiBold,  fontSize = 15.sp, lineHeight = 20.sp)
    val Meta        = TextStyle(OpenSans, fontWeight = FontWeight.Normal,    fontSize = 14.sp, lineHeight = 20.sp)
    val Eyebrow     = TextStyle(OpenSans, fontWeight = FontWeight.Bold,      fontSize = 12.sp, lineHeight = 12.sp, letterSpacing = 0.6.sp)
    val CellLabel   = TextStyle(OpenSans, fontWeight = FontWeight.Bold,      fontSize = 10.sp, lineHeight = 10.sp, letterSpacing = 0.6.sp)
    val Button      = TextStyle(OpenSans, fontWeight = FontWeight.Bold,      fontSize = 16.sp, lineHeight = 16.sp, letterSpacing = 0.2.sp)
    val Tab         = TextStyle(OpenSans, fontWeight = FontWeight.SemiBold,  fontSize = 10.sp, lineHeight = 10.sp, letterSpacing = 0.1.sp)
    val StatusPill  = TextStyle(OpenSans, fontWeight = FontWeight.Bold,      fontSize = 12.sp, lineHeight = 12.sp, letterSpacing = 0.2.sp)
}

val UATypography = Typography(
    headlineLarge  = UAText.ScreenTitle,
    headlineMedium = UAText.Section,
    titleMedium    = UAText.PaxName,
    bodyMedium     = UAText.Body,
    labelSmall     = UAText.Tab,
)

3. Signature Components (@Composable)

Boarding Pass (United Blue header + perforation)

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.unit.dp

@Composable
fun UABoardingPass(
    paxName: String, gate: String, seat: String, boards: String,
    premierGroup: String, dark: Boolean = true,
) {
    Column(
        Modifier
            .clip(RoundedCornerShape(16.dp))
            .background(UAColors.DarkSurface1)
            .border(1.dp, UAColors.DarkDivider, RoundedCornerShape(16.dp)),
    ) {
        Row(
            Modifier.fillMaxWidth().background(UAColors.Blue)
                .padding(horizontal = 18.dp, vertical = 12.dp),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Text("◍ UNITED", style = UAText.RouteIATA.copy(fontSize = 13.sp), color = Color.White)
            Text(premierGroup, style = UAText.Eyebrow, color = UAColors.PremierGoldBright)
        }
        Column(Modifier.padding(18.dp)) {
            Text("PASSENGER", style = UAText.CellLabel, color = UAColors.DarkTextSecondary)
            Text(paxName.uppercase(), style = UAText.PaxName, color = UAColors.DarkTextPrimary,
                modifier = Modifier.padding(top = 2.dp))
            Row(Modifier.padding(top = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
                PassCell("GATE", gate, UAColors.DarkTextPrimary, Modifier.weight(1f))
                PassCell("SEAT", seat, UAColors.DarkTextPrimary, Modifier.weight(1f))
                PassCell("BOARDS", boards, UAColors.PremierGoldBright, Modifier.weight(1f))
            }
            Perforation(Modifier.padding(vertical = 16.dp))
            BarcodeStripe(dark, Modifier.fillMaxWidth().height(54.dp))
        }
    }
}

@Composable
private fun PassCell(label: String, value: String, valueColor: Color, modifier: Modifier = Modifier) {
    Column(modifier) {
        Text(label, style = UAText.CellLabel, color = UAColors.DarkTextTertiary)
        Text(value, style = UAText.DataLarge, color = valueColor, modifier = Modifier.padding(top = 3.dp))
    }
}

/** Dashed line with circular notch cut-outs at the edges — the "tear here" affordance. */
@Composable
fun Perforation(modifier: Modifier = Modifier) {
    Box(modifier.fillMaxWidth().height(20.dp), contentAlignment = Alignment.Center) {
        Box(
            Modifier.fillMaxWidth().height(1.dp).drawBehind {
                drawLine(
                    color = UAColors.DarkDivider,
                    start = Offset(0f, size.height / 2),
                    end = Offset(size.width, size.height / 2),
                    pathEffect = PathEffect.dashPathEffect(floatArrayOf(12f, 10f)),
                )
            },
        )
        Box(Modifier.size(20.dp).offset(x = (-28).dp).clip(androidx.compose.foundation.shape.CircleShape).background(UAColors.DarkCanvas)
            .align(Alignment.CenterStart))
        Box(Modifier.size(20.dp).offset(x = 28.dp).clip(androidx.compose.foundation.shape.CircleShape).background(UAColors.DarkCanvas)
            .align(Alignment.CenterEnd))
    }
}

/** High-contrast bars; inverts to light bars in dark mode so a gate scanner reads a dimmed screen. */
@Composable
fun BarcodeStripe(dark: Boolean, modifier: Modifier = Modifier) {
    val bar = if (dark) UAColors.DarkTextPrimary else UAColors.TextPrimary
    Row(modifier.clip(RoundedCornerShape(8.dp))) {
        repeat(64) { i ->
            Box(Modifier.width(if (i % 3 == 0) 3.dp else 1.dp).fillMaxHeight().background(bar))
            Spacer(Modifier.width(2.dp))
        }
    }
}

Interactive Seat Map

import androidx.compose.foundation.clickable
import androidx.compose.runtime.*
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback

enum class SeatKind { Standard, EconPlus, Taken, Selected, Aisle }

@Composable
fun SeatMap(rows: List<List<SeatKind>>) {
    var selected by remember { mutableStateOf("7A") }
    val haptics = LocalHapticFeedback.current
    Column(
        Modifier
            .clip(RoundedCornerShape(16.dp)).background(UAColors.DarkSurface1)
            .border(1.dp, UAColors.DarkDivider, RoundedCornerShape(16.dp)).padding(18.dp),
    ) {
        Text("Choose your seat", style = UAText.DataInline.copy(fontWeight = FontWeight.Bold),
            color = UAColors.DarkTextPrimary)
        Text("Economy Plus · Row 7–9 · $selected selected", style = UAText.Meta,
            color = UAColors.DarkTextSecondary, modifier = Modifier.padding(top = 4.dp, bottom = 16.dp))
        Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
            rows.forEach { row ->
                Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
                    row.forEach { kind ->
                        SeatCell(kind, Modifier.weight(1f)) {
                            if (kind == SeatKind.Standard || kind == SeatKind.EconPlus) {
                                haptics.performHapticFeedback(HapticFeedbackType.LongPress)
                            }
                        }
                    }
                }
            }
        }
        Row(Modifier.padding(top = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
            Legend(UAColors.Rhapsody.copy(alpha = 0.4f), "Econ+")
            Legend(UAColors.Rhapsody, "Selected")
            Legend(UAColors.DarkCanvas, "Taken")
        }
    }
}

@Composable
private fun SeatCell(kind: SeatKind, modifier: Modifier = Modifier, onTap: () -> Unit) {
    if (kind == SeatKind.Aisle) { Box(modifier.aspectRatio(1f)); return }
    val fill = when (kind) {
        SeatKind.Selected -> UAColors.Rhapsody
        SeatKind.EconPlus -> UAColors.Rhapsody.copy(alpha = 0.18f)
        SeatKind.Taken    -> UAColors.DarkCanvas
        else              -> UAColors.DarkSurface2
    }
    val border = when (kind) {
        SeatKind.Selected -> UAColors.RhapsodyBright
        SeatKind.EconPlus -> UAColors.RhapsodyBright.copy(alpha = 0.4f)
        else              -> UAColors.DarkDivider
    }
    Box(
        modifier
            .aspectRatio(1f)
            .clip(RoundedCornerShape(topStart = 6.dp, topEnd = 6.dp, bottomStart = 3.dp, bottomEnd = 3.dp))
            .background(fill)
            .border(1.dp, border, RoundedCornerShape(topStart = 6.dp, topEnd = 6.dp, bottomStart = 3.dp, bottomEnd = 3.dp))
            .clickable { onTap() },
    )
}

@Composable
private fun Legend(c: Color, t: String) {
    Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
        Box(Modifier.size(10.dp).clip(RoundedCornerShape(3.dp)).background(c))
        Text(t, style = UAText.Eyebrow.copy(fontWeight = FontWeight.Normal, fontSize = 11.sp),
            color = UAColors.DarkTextSecondary)
    }
}

Flight Detail Header + Buttons + Status Chip

import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.graphics.Brush
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Flight

@Composable
fun FlightDetailHeader(
    flightNo: String, origin: String, originCity: String, dest: String,
    destCity: String, dur: String, depRow: String, arrRow: String, status: String,
) {
    Column(
        Modifier
            .fillMaxWidth()
            .background(Brush.linearGradient(listOf(UAColors.Blue, Color(0xFF04152C))))
            .padding(horizontal = 18.dp).padding(top = 8.dp, bottom = 18.dp),
    ) {
        Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
            Text(flightNo.uppercase(), style = UAText.Eyebrow.copy(letterSpacing = 1.sp),
                color = Color(0xFF7FA0CE))
            StatusChip(StatusKind.OnTime, status)
        }
        Row(Modifier.padding(top = 14.dp)) {
            Column {
                Text(origin, style = UAText.RouteIATA, color = Color.White)
                Text(originCity, style = UAText.Meta, color = Color(0xFF9FBADC))
            }
            Column(Modifier.weight(1f).padding(top = 8.dp), horizontalAlignment = Alignment.CenterHorizontally) {
                Icon(Icons.Filled.Flight, null, tint = Color.White, modifier = Modifier.size(20.dp))
                Text(dur, style = UAText.Meta.copy(fontSize = 11.sp), color = Color(0xFF9FBADC))
            }
            Column(horizontalAlignment = Alignment.End) {
                Text(dest, style = UAText.RouteIATA, color = Color.White)
                Text(destCity, style = UAText.Meta, color = Color(0xFF9FBADC))
            }
        }
        Row(Modifier.fillMaxWidth().padding(top = 6.dp), Arrangement.SpaceBetween) {
            Text(depRow, style = UAText.DataInline, color = Color(0xFFCFE0F2))
            Text(arrRow, style = UAText.DataInline, color = Color(0xFFCFE0F2))
        }
    }
}

enum class StatusKind { OnTime, Boarding, Delayed, Canceled }

@Composable
fun StatusChip(kind: StatusKind, label: String) {
    val (fg, bg) = when (kind) {
        StatusKind.OnTime   -> UAColors.OnTimeDark   to UAColors.OnTime.copy(alpha = 0.18f)
        StatusKind.Boarding -> UAColors.BoardingDark to UAColors.Boarding.copy(alpha = 0.22f)
        StatusKind.Delayed  -> UAColors.DelayedDark  to UAColors.Delayed.copy(alpha = 0.18f)
        StatusKind.Canceled -> UAColors.CanceledDark to UAColors.Canceled.copy(alpha = 0.18f)
    }
    Text(label, style = UAText.StatusPill, color = fg,
        modifier = Modifier.clip(CircleShape).background(bg).padding(horizontal = 14.dp, vertical = 7.dp))
}

@Composable
fun UAPrimaryButton(title: String, onClick: () -> Unit) {
    androidx.compose.material3.Button(
        onClick = onClick,
        shape = RoundedCornerShape(4.dp),  // crisp, low-radius
        colors = androidx.compose.material3.ButtonDefaults.buttonColors(
            containerColor = UAColors.Rhapsody, contentColor = Color.White),
        modifier = Modifier.fillMaxWidth().height(48.dp),
    ) { Text(title, style = UAText.Button) }
}

4. Navigation

United has minimal chrome: a 5-tab bottom strip. On Android model it as a NavigationBar — no tint pill, active is the Rhapsody-blue icon + label.

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

@Composable
fun UABottomBar(selected: Int, onSelect: (Int) -> Unit) {
    NavigationBar(containerColor = UAColors.DarkCanvas, tonalElevation = 0.dp) {
        val items = listOf(
            "Home"        to Icons.Filled.Home,
            "Book"        to Icons.Filled.Search,
            "Flights"     to Icons.Filled.Flight,
            "Trips"       to Icons.Filled.Receipt,
            "MileagePlus" 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 = UAText.Tab) },
                colors = NavigationBarItemDefaults.colors(
                    selectedIconColor   = UAColors.RhapsodyBright,
                    selectedTextColor   = UAColors.RhapsodyBright,
                    unselectedIconColor = UAColors.DarkTextTertiary,
                    unselectedTextColor = UAColors.DarkTextTertiary,
                    indicatorColor      = Color.Transparent, // no Material pill — United has none
                ),
            )
        }
    }
}

Use a NavHost for Home/Book/Flights/Trips/MileagePlus; a ModalBottomSheet for fare details and the upgrade list; push transitions are a 300ms horizontal slide.

5. Motion

United motion is refined — never decorative bounce. Shadows signal “the thing you act on / hold up at the gate”.

Moment Compose recipe
Aircraft glide (header) animateFloatAsState 0 → 1 tween(900, easing = FastOutSlowInEasing)
Seat select animateFloatAsState scale 0.9 → 1 spring(dampingRatio = 0.6f) + light haptic
Status change pill color animateColorAsState(tween(250)); affected row tween(300); soft haptic
Boarding pass open AnimatedVisibility scale-in spring(dampingRatio = 0.8f); raise screen brightness
Bag tracker advance active dot fill tween(300) + light haptic on scan event
Tab switch Crossfade(tween(150))
Page navigation Nav slide push tween(300)
val seatScale by animateFloatAsState(if (selected) 1f else 0.9f,
    animationSpec = spring(dampingRatio = 0.6f, stiffness = Spring.StiffnessMedium), label = "seat")

val aircraftProgress by animateFloatAsState(if (visible) 1f else 0f,
    animationSpec = tween(900, easing = FastOutSlowInEasing), label = "aircraftGlide")

Haptics: LocalHapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) for the soft impact on seat select and status change; on gate change use view.performHapticFeedback(HapticFeedbackConstants.CONFIRM). Brightness: cache and raise via WindowManager.LayoutParams.screenBrightness = 1f while the full-screen pass is shown; restore on dismiss. Auto-refresh is silent.

6. Icons

Closest first-party set is androidx.compose.material:material-icons-extended.

Purpose SF Symbol (iOS) Material Icon (Compose)
Home (tab) house.fill Icons.Filled.Home
Book (tab) magnifyingglass Icons.Filled.Search
Flights (tab) airplane Icons.Filled.Flight
Trips (tab) rectangle.stack.fill Icons.Filled.Receipt
MileagePlus (tab) person.crop.circle.fill Icons.Filled.AccountCircle
Aircraft (header) airplane Icons.Filled.Flight
Departed airplane.departure Icons.Filled.FlightTakeoff
Arrived airplane.arrival Icons.Filled.FlightLand
Seat chair.lounge.fill Icons.Filled.EventSeat
Bag tracker suitcase.fill Icons.Filled.Luggage
Add to Wallet wallet.pass Icons.Filled.AccountBalanceWallet
Boarding pass qrcode Icons.Filled.QrCode2
Upgrade list arrow.up.circle.fill Icons.Filled.Upgrade
Gate change exclamationmark.triangle.fill Icons.Filled.Warning
Premier tier star.circle.fill Icons.Filled.Stars
Back chevron.left Icons.AutoMirrored.Filled.ArrowBack
Share square.and.arrow.up Icons.Filled.IosShare

7. Minimum SDK & Accessibility Notes

  • minSdk 24 (Popup, modern motion comfortable at 24). compileSdk 34+, Compose BOM 2024.09+, Kotlin 2.0+.
  • Edge-to-edge: call enableEdgeToEdge(); the navy flight-detail header draws under the status bar with light-content system icons; the boarding-pass barcode must stay above the navigation-bar inset.
  • Font scaling: sp honors the user’s font scale — keep it on route IATA, screen title, section, body, meta. Pin layout-sensitive text (barcode, seat-map cells, 10sp cell labels, 10sp tab labels, status-pill text) via CompositionLocalProvider(LocalDensity provides Density(density, fontScale = 1f)). Data values scale one step then clamp to preserve the 3-cell grid.
  • TalkBack: label the pass “Boarding pass, {name}, gate {gate}, seat {seat}, boards {time}, {premierGroup}”; each seat “Seat {id}, {Economy Plus / standard / taken / selected}, {row}”; the status chip announces the full status word; the bag tracker announces the current step + carousel.
  • Touch targets: Material guidance is 48.dp — primary button ≥ 48.dp; seat cells ≥ 32.dp tap (expand small cabins); list rows 52.dp full-row tappable.
  • Contrast: #E9EEF6 on #0A1424 and #0C1F38 on #FFFFFF pass WCAG AA; the functional status pairs are validated against their translucent fills — re-check any custom amber pair.
  • Brightness: raise WindowManager.LayoutParams.screenBrightness = 1f while the full-screen pass is visible (gate scanning); restore the user’s value on dismiss.
  • Reduce motion: when Settings.Global.ANIMATOR_DURATION_SCALE == 0, disable the aircraft glide and seat-pop spring; keep the status color change as an instant swap and the selected-seat state (they convey state).
  • Dark mode: invert via the Dark* palette — #0A1424, NOT true black, so the United navy survives; the barcode flips to a light bar pattern so it stays machine-scannable. Do not enable Material You dynamicColorScheme() — United’s two-blue identity must hold 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