coderef

Delta

Fly Delta's iOS app is a calm, navy-grounded travel concierge that organizes itself entirely around one question: where is my trip right now?
DownloadRaw
Delta

Build with the Delta 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,224 words
Platforms
Android, Expo, SwiftUI, DESIGN.md

Add it to your project

Then prompt your agent

Design System · Delta

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

Delta (iOS) — Jetpack Compose Implementation Guide

Companion to DESIGN.md — the framework-neutral spec. This file ports Delta’s visual language to Android with Jetpack Compose (Material 3): a color token object, a Typography set, paste-ready @Composables for the boarding pass + flight-status timeline, 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 (Delta Blue navy, the navy→red boarding pass, the status-first trip header, calm 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 — Delta’s palette is a fixed brand system. Delta is light-mode-first; a full navy-charcoal dark scheme is provided.

1. Color Tokens (Compose + M3)

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

object DeltaColors {
    // Brand
    val Blue        = Color(0xFF003268)
    val BlueBright  = Color(0xFF0F4C9A)
    val Red         = Color(0xFFC8102E)
    val RedPressed  = Color(0xFFA30D26)
    val WidgetMaroon = Color(0xFF862633)
    val SkyMilesGold = Color(0xFFC99700)

    // Canvas & Surfaces (Light)
    val Canvas         = Color(0xFFFFFFFF)
    val SurfaceGray    = Color(0xFFF4F6F9)
    val SurfacePressed = Color(0xFFE8ECF3)
    val DividerLight   = Color(0xFFD6DCE7)

    // Canvas & Surfaces (Dark) — deep navy-charcoal, NOT pure black
    val DarkCanvas   = Color(0xFF0B1320)
    val DarkSurface1 = Color(0xFF131C2B)
    val DarkSurface2 = Color(0xFF1C2738)
    val DarkDivider  = Color(0xFF25314A)

    // Text
    val TextPrimary       = Color(0xFF10243F)
    val TextSecondary     = Color(0xFF5A6A82)
    val TextTertiary      = Color(0xFF8A98AE)
    val DarkTextPrimary   = Color(0xFFE8ECF3)
    val DarkTextSecondary = Color(0xFF9AA6BC)
    val DarkTextTertiary  = Color(0xFF5F6E88)

    // Flight status (functional, constant)
    val OnTime       = Color(0xFF1E8E5A)
    val OnTimeDark   = Color(0xFF4FD18C)
    val Boarding     = Color(0xFF0F4C9A)
    val BoardingDark = Color(0xFF6FB0EA)
    val Delayed      = Color(0xFFC99700)
    val DelayedDark  = Color(0xFFE0B743)
    val Canceled     = Color(0xFFC8102E)
    val CanceledDark = Color(0xFFE0414E)
}

Wire it into both schemes. Delta is light-first; the dark scheme uses the signature #0B1320 navy-charcoal, 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 DeltaLight = lightColorScheme(
    primary        = DeltaColors.Red,          // primary CTA — the single high-energy accent
    onPrimary      = Color.White,
    secondary      = DeltaColors.Blue,         // brand/navigation anchor
    onSecondary    = Color.White,
    background     = DeltaColors.Canvas,
    onBackground   = DeltaColors.TextPrimary,
    surface        = DeltaColors.SurfaceGray,
    onSurface      = DeltaColors.TextPrimary,
    surfaceVariant = DeltaColors.SurfacePressed,
    outline        = DeltaColors.DividerLight,
    error          = DeltaColors.Canceled,
)

private val DeltaDark = darkColorScheme(
    primary        = DeltaColors.Red,
    onPrimary      = Color.White,
    secondary      = DeltaColors.BlueBright,
    onSecondary    = Color.White,
    background     = DeltaColors.DarkCanvas,
    onBackground   = DeltaColors.DarkTextPrimary,
    surface        = DeltaColors.DarkSurface1,
    onSurface      = DeltaColors.DarkTextPrimary,
    surfaceVariant = DeltaColors.DarkSurface2,
    outline        = DeltaColors.DarkDivider,
    error          = DeltaColors.CanceledDark,
)

@Composable
fun DeltaTheme(
    dark: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit,
) = MaterialTheme(
    colorScheme = if (dark) DeltaDark else DeltaLight,
    typography  = DeltaTypography,
    content     = content,
)

2. Typography (M3)

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

// ui/theme/DeltaType.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 DeltaText {
    val RouteIATA   = TextStyle(OpenSans, fontWeight = FontWeight.ExtraBold, fontSize = 34.sp, lineHeight = 36.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 DeltaTypography = Typography(
    headlineLarge  = DeltaText.ScreenTitle,
    headlineMedium = DeltaText.Section,
    titleMedium    = DeltaText.PaxName,
    bodyMedium     = DeltaText.Body,
    labelSmall     = DeltaText.Tab,
)

3. Signature Components (@Composable)

Digital Boarding Pass

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.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Composable
fun BoardingPass(
    paxName: String, gate: String, seat: String, group: String,
    boardsAt: String, zone: String, dark: Boolean = true,
) {
    Column(
        Modifier
            .clip(RoundedCornerShape(18.dp))
            .background(DeltaColors.DarkSurface1)
            .border(1.dp, DeltaColors.DarkDivider, RoundedCornerShape(18.dp)),
    ) {
        Box(
            Modifier
                .fillMaxWidth().height(6.dp)
                .background(Brush.horizontalGradient(listOf(DeltaColors.Blue, DeltaColors.Red))),
        )
        Column(Modifier.padding(18.dp)) {
            Text("PASSENGER", style = DeltaText.CellLabel, color = DeltaColors.DarkTextSecondary)
            Text(paxName.uppercase(), style = DeltaText.PaxName, color = DeltaColors.DarkTextPrimary,
                modifier = Modifier.padding(top = 2.dp))

            Row(Modifier.padding(top = 16.dp), horizontalArrangement = Arrangement.spacedBy(18.dp)) {
                PassCell("GATE", gate, DeltaColors.DarkTextPrimary, Modifier.weight(1f))
                PassCell("SEAT", seat, DeltaColors.DarkTextPrimary, Modifier.weight(1f))
                PassCell("GROUP", group, DeltaColors.SkyMilesGold, Modifier.weight(1f))
            }

            Row(
                Modifier
                    .padding(top = 16.dp).fillMaxWidth()
                    .clip(RoundedCornerShape(10.dp))
                    .background(DeltaColors.DarkSurface2)
                    .padding(14.dp),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Text("Boards $boardsAt · Zone", style = DeltaText.DataInline.copy(fontWeight = FontWeight.SemiBold),
                    color = DeltaColors.DarkTextSecondary)
                Text(zone, style = DeltaText.DataInline.copy(fontWeight = FontWeight.Bold),
                    color = DeltaColors.DarkTextPrimary)
            }

            BarcodeStripe(dark = dark, modifier = Modifier.padding(top = 16.dp).fillMaxWidth().height(56.dp))
        }
    }
}

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

/** 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) DeltaColors.DarkTextPrimary else DeltaColors.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))
        }
    }
}

Flight Status Timeline

import androidx.compose.foundation.shape.CircleShape

data class FlightStep(val title: String, val meta: String, val done: Boolean)

@Composable
fun FlightStatusTimeline(steps: List<FlightStep>, modifier: Modifier = Modifier) {
    Column(
        modifier
            .clip(RoundedCornerShape(16.dp))
            .background(DeltaColors.DarkSurface1)
            .border(1.dp, DeltaColors.DarkDivider, RoundedCornerShape(16.dp))
            .padding(18.dp),
    ) {
        Text("Flight Status", style = DeltaText.DataInline.copy(fontWeight = FontWeight.Bold),
            color = DeltaColors.DarkTextPrimary, modifier = Modifier.padding(bottom = 16.dp))
        Row(horizontalArrangement = Arrangement.spacedBy(14.dp)) {
            Column(horizontalAlignment = Alignment.CenterHorizontally) {
                steps.forEachIndexed { i, s ->
                    Box(
                        Modifier.size(12.dp).clip(CircleShape)
                            .background(if (s.done) DeltaColors.OnTimeDark else DeltaColors.DarkTextSecondary)
                            .border(2.dp, if (s.done) DeltaColors.OnTimeDark else DeltaColors.DarkTextSecondary, CircleShape),
                    )
                    if (i < steps.lastIndex) {
                        Box(Modifier.width(2.dp).height(28.dp)
                            .background(DeltaColors.DarkDivider).padding(vertical = 2.dp))
                    }
                }
            }
            Column(verticalArrangement = Arrangement.spacedBy(18.dp)) {
                steps.forEach { s ->
                    Column {
                        Text(s.title, style = DeltaText.DataInline.copy(fontWeight = FontWeight.Bold),
                            color = DeltaColors.DarkTextPrimary)
                        Text(s.meta, style = DeltaText.Meta, color = DeltaColors.DarkTextSecondary,
                            modifier = Modifier.padding(top = 2.dp))
                    }
                }
            }
        }
    }
}

Trip Header + Buttons + Status Chip

@Composable
fun TripHeader(flightNo: String, origin: String, dest: String, status: String) {
    Column(
        Modifier
            .fillMaxWidth()
            .background(Brush.verticalGradient(listOf(DeltaColors.Blue, Color(0xFF06203F))))
            .padding(horizontal = 18.dp).padding(top = 8.dp, bottom = 16.dp),
    ) {
        Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically) {
            Text("TODAY · $flightNo", style = DeltaText.Eyebrow.copy(letterSpacing = 1.sp),
                color = Color(0xFF8FB4DD))
            StatusChip(StatusKind.OnTime, status)
        }
        Row(Modifier.padding(top = 12.dp), verticalAlignment = Alignment.Bottom,
            horizontalArrangement = Arrangement.spacedBy(14.dp)) {
            Text(origin, style = DeltaText.RouteIATA, color = Color.White)
            Row(Modifier.weight(1f).padding(bottom = 8.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(6.dp)) {
                Box(Modifier.weight(1f).height(2.dp).background(Color.White.copy(alpha = 0.35f)))
                Icon(Icons.Filled.Flight, null, tint = Color.White, modifier = Modifier.size(18.dp))
                Box(Modifier.weight(1f).height(2.dp).background(Color.White.copy(alpha = 0.35f)))
            }
            Text(dest, style = DeltaText.RouteIATA, color = Color.White)
        }
    }
}

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

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

@Composable
fun DeltaPrimaryButton(title: String, onClick: () -> Unit) {
    androidx.compose.material3.Button(
        onClick = onClick,
        shape = RoundedCornerShape(6.dp),
        colors = androidx.compose.material3.ButtonDefaults.buttonColors(
            containerColor = DeltaColors.Red, contentColor = Color.White),
        modifier = Modifier.fillMaxWidth().height(48.dp),
    ) { Text(title, style = DeltaText.Button) }
}

4. Navigation

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

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

@Composable
fun DeltaBottomBar(selected: Int, onSelect: (Int) -> Unit) {
    NavigationBar(containerColor = DeltaColors.DarkCanvas, tonalElevation = 0.dp) {
        val items = listOf(
            "Today"    to Icons.Filled.Home,
            "Book"     to Icons.Filled.Flight,
            "Trips"    to Icons.Filled.Receipt,
            "SkyMiles" to Icons.Filled.AccountCircle,
            "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(22.dp)) },
                label = { Text(label, style = DeltaText.Tab) },
                colors = NavigationBarItemDefaults.colors(
                    selectedIconColor   = DeltaColors.BoardingDark,
                    selectedTextColor   = DeltaColors.BoardingDark,
                    unselectedIconColor = DeltaColors.DarkTextTertiary,
                    unselectedTextColor = DeltaColors.DarkTextTertiary,
                    indicatorColor      = Color.Transparent, // no Material pill — Delta has none
                ),
            )
        }
    }
}

Use a NavHost for Today/Book/Trips/SkyMiles/More; a ModalBottomSheet for the seat map and fare details; push transitions are a 300ms horizontal slide.

5. Motion

Delta motion is calm and functional — never decorative bounce. Shadows signal “the thing you act on”.

Moment Compose recipe
Plane glide (header) animateFloatAsState 0 → 1 tween(1200, easing = FastOutSlowInEasing); offset the plane glyph along the line
Status change pill color animateColorAsState(tween(250)); affected step tween(300); soft haptic
Active timeline dot rememberInfiniteTransition scale 1 → 1.15 tween(1600) reverse
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 planeProgress by animateFloatAsState(if (visible) 1f else 0f,
    animationSpec = tween(1200, easing = FastOutSlowInEasing), label = "planeGlide")

val dotScale by rememberInfiniteTransition(label = "dot").animateFloat(
    initialValue = 1f, targetValue = 1.15f,
    animationSpec = infiniteRepeatable(tween(1600), RepeatMode.Reverse), label = "dotPulse")

Haptics: LocalHapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) for the soft impact on status change and bag-tracker advance; on gate change use a stronger pattern via 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)
Today (tab) house.fill Icons.Filled.Home
Book (tab) airplane Icons.Filled.Flight
Trips (tab) rectangle.stack.fill Icons.Filled.Receipt
SkyMiles (tab) person.crop.circle.fill Icons.Filled.AccountCircle
More (tab) ellipsis.circle.fill Icons.Filled.MoreHoriz
In-flight plane airplane Icons.Filled.Flight
Departed airplane.departure Icons.Filled.FlightTakeoff
Arrived airplane.arrival Icons.Filled.FlightLand
Gate change exclamationmark.triangle.fill Icons.Filled.Warning
Bag tracker suitcase.fill Icons.Filled.Luggage
Add to Wallet wallet.pass Icons.Filled.AccountBalanceWallet
Boarding pass qrcode Icons.Filled.QrCode2
Seat chair.lounge.fill Icons.Filled.EventSeat
Back chevron.left Icons.AutoMirrored.Filled.ArrowBack
Share square.and.arrow.up Icons.Filled.IosShare
Refresh status arrow.clockwise Icons.Filled.Refresh
Medallion tier star.circle.fill Icons.Filled.Stars

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 trip 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, 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}, group {group}, boards {time}”; each timeline step “{title}, {meta}, {completed/upcoming}”; 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; list rows 52.dp full-row tappable; the timeline step rows are full-row tappable for segment detail.
  • Contrast: #E8ECF3 on #0B1320 and #10243F on #FFFFFF pass WCAG AA; the functional status pairs are validated against their translucent fills — re-check any custom delay/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 plane glide and active-dot pulse; keep the status color change as an instant swap (it conveys state).
  • Dark mode: invert via the Dark* palette — #0B1320, NOT true black, so the Delta navy survives; the barcode flips to a light bar pattern so it stays machine-scannable. Do not enable Material You dynamicColorScheme() — Delta’s navy + widget-red 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