coderef

monday.com

monday.com's iOS app is a loud, color-coded work operating system whose entire personality is the colorful board. The default canvas is white (#FFFFFF) in light mode and a deep indigo (#181B34) in dark mode — distinctly blue-tinted, NOT…
DownloadRaw
monday.com

Build with the monday.com look.

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

Palette

At a glance

Theme
light
Typefaces
Figtree
Sections
7
Length
4,218 words
Platforms
Android, Expo, SwiftUI, DESIGN.md

Add it to your project

Then prompt your agent

Design System · monday.com

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

monday.com (iOS) — Jetpack Compose Implementation Guide

Companion to DESIGN.md — the framework-neutral spec. This file ports monday.com’s visual language to Android with Jetpack Compose (Material 3): a color token object, a Typography set, paste-ready @Composables, the colorful board + full-bleed status cells + battery rollup, 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 (monday.com’s deep-indigo canvas, full-bleed saturated status cells, the leading group-color stripe, bold Figtree headers) while making everything idiomatic Android — NavigationBar instead of a UITabBar, a FloatingActionButton, a ModalBottomSheet label picker, dp/sp instead of pt.

Assumes Compose BOM 2024.09+, Kotlin 2.0+, minSdk 24, Material 3, and Coil 2.6+ for avatars. No color extraction — monday.com’s palette is a fixed multicolor set, so Palette is not needed. monday.com is light-first; a full deep-indigo night scheme is provided.

1. Color Tokens

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

object MondayColors {
    // Canvas & Surfaces (Light)
    val Canvas         = Color(0xFFFFFFFF)
    val SurfaceGray    = Color(0xFFF6F7FB)
    val SurfacePressed = Color(0xFFEDEFF5)
    val Divider        = Color(0xFFE6E9F0)
    val CellBorder     = Color(0xFFD0D4E4)

    // Canvas & Surfaces (Night)
    val NightCanvas     = Color(0xFF181B34) // deep indigo — NOT pure black
    val NightSurface1   = Color(0xFF20243F)
    val NightSurface2   = Color(0xFF292F4C)
    val NightDivider    = Color(0xFF353B5C)
    val NightCellBorder = Color(0xFF3D436A)

    // Text
    val TextPrimary       = Color(0xFF323338)
    val TextSecondary     = Color(0xFF676879)
    val TextTertiary      = Color(0xFF9699A6)
    val DarkTextPrimary   = Color(0xFFF5F6F8)
    val DarkTextSecondary = Color(0xFFA7AFC6)
    val DarkTextTertiary  = Color(0xFF6B7394)

    // Action (the only "UI" color amid the data colors)
    val Blue        = Color(0xFF0073EA)
    val BluePressed = Color(0xFF0060C2)

    // Multicolor data palette (full-bleed fills)
    val Red        = Color(0xFFE2445C)
    val Orange     = Color(0xFFFDAB3D)
    val Green      = Color(0xFF00C875)
    val BrightBlue = Color(0xFF579BFC)
    val Purple     = Color(0xFFA25DDC)
    val DarkPurple = Color(0xFF401694)
    val Indigo     = Color(0xFF5559DF)
    val Yellow     = Color(0xFFFFCB00)
    val Teal       = Color(0xFF00D2D2)
    val Pink       = Color(0xFFFF158A)
    val BrightGreen = Color(0xFF9CD326)
    val Navy       = Color(0xFF333D6E)

    // Semantic
    val Success = Color(0xFF00C875)
    val Error   = Color(0xFFE2445C)
    val Warning = Color(0xFFFDAB3D)
}

// On-color text: yellow / teal / bright-green flip to dark
private val DarkOn = setOf(MondayColors.Yellow, MondayColors.Teal, MondayColors.BrightGreen)
fun onColor(bg: Color): Color = if (bg in DarkOn) MondayColors.TextPrimary else Color.White

data class StatusOption(val label: String, val color: Color)
val defaultStatuses = listOf(
    StatusOption("Done",          MondayColors.Green),
    StatusOption("Working on it", MondayColors.Orange),
    StatusOption("Stuck",         MondayColors.Red),
    StatusOption("Not started",   MondayColors.Navy),
)

Wire it into both schemes. monday.com is light-first; the night scheme uses the signature #181B34 deep indigo, 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 MondayLight = lightColorScheme(
    primary        = MondayColors.Blue,
    onPrimary      = Color.White,
    background     = MondayColors.Canvas,
    onBackground   = MondayColors.TextPrimary,
    surface        = MondayColors.SurfaceGray,
    onSurface      = MondayColors.TextPrimary,
    surfaceVariant = MondayColors.SurfacePressed,
    outline        = MondayColors.Divider,
    error          = MondayColors.Error,
)

private val MondayNight = darkColorScheme(
    primary        = MondayColors.Blue, // action blue identical in night
    onPrimary      = Color.White,
    background     = MondayColors.NightCanvas,
    onBackground   = MondayColors.DarkTextPrimary,
    surface        = MondayColors.NightSurface1,
    onSurface      = MondayColors.DarkTextPrimary,
    surfaceVariant = MondayColors.NightSurface2,
    outline        = MondayColors.NightDivider,
    error          = MondayColors.Error,
)

@Composable
fun MondayTheme(
    dark: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit,
) = MaterialTheme(
    colorScheme = if (dark) MondayNight else MondayLight,
    typography = MondayTypography,
    content = content,
)

2. Typography

monday.com uses Figtree (Poppins-class brand sans) — drop the TTFs in res/font/. Bold and confident: item names 500, group headers 800.

// ui/theme/MondayType.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 Figtree = FontFamily(
    Font(R.font.figtree_regular,   FontWeight.Normal),
    Font(R.font.figtree_medium,    FontWeight.Medium),
    Font(R.font.figtree_semibold,  FontWeight.SemiBold),
    Font(R.font.figtree_bold,      FontWeight.Bold),
    Font(R.font.figtree_extrabold, FontWeight.ExtraBold),
)

object MondayText {
    val ScreenTitle = TextStyle(Figtree, fontWeight = FontWeight.ExtraBold, fontSize = 32.sp, lineHeight = 37.sp, letterSpacing = (-0.5).sp)
    val BoardTitle  = TextStyle(Figtree, fontWeight = FontWeight.Bold,      fontSize = 26.sp, lineHeight = 31.sp, letterSpacing = (-0.3).sp)
    val GroupHeader = TextStyle(Figtree, fontWeight = FontWeight.ExtraBold, fontSize = 22.sp, lineHeight = 26.sp, letterSpacing = (-0.2).sp) // color per group
    val Section     = TextStyle(Figtree, fontWeight = FontWeight.Bold,      fontSize = 18.sp, lineHeight = 23.sp)
    val Body        = TextStyle(Figtree, fontWeight = FontWeight.Normal,    fontSize = 16.sp, lineHeight = 24.sp)
    val ItemName    = TextStyle(Figtree, fontWeight = FontWeight.Medium,    fontSize = 14.sp, lineHeight = 19.sp)
    val ColumnHead  = TextStyle(Figtree, fontWeight = FontWeight.SemiBold,  fontSize = 13.sp, lineHeight = 16.sp, letterSpacing = 0.2.sp)
    val Meta        = TextStyle(Figtree, fontWeight = FontWeight.Normal,    fontSize = 14.sp, lineHeight = 19.sp)
    val StatusLabel = TextStyle(Figtree, fontWeight = FontWeight.Bold,      fontSize = 11.sp, lineHeight = 12.sp, letterSpacing = 0.2.sp)
    val Button      = TextStyle(Figtree, fontWeight = FontWeight.SemiBold,  fontSize = 15.sp, lineHeight = 15.sp)
    val TextAction  = TextStyle(Figtree, fontWeight = FontWeight.SemiBold,  fontSize = 14.sp, lineHeight = 14.sp)
    val PersonInit  = TextStyle(Figtree, fontWeight = FontWeight.Bold,      fontSize = 9.sp,  lineHeight = 9.sp,  letterSpacing = 0.2.sp)
    val Tab         = TextStyle(Figtree, fontWeight = FontWeight.SemiBold,  fontSize = 10.sp, lineHeight = 10.sp, letterSpacing = 0.1.sp)
}

// Map onto Material 3 slots so stock components inherit the brand
val MondayTypography = Typography(
    headlineLarge = MondayText.ScreenTitle,
    headlineMedium = MondayText.BoardTitle,
    titleMedium   = MondayText.Section,
    bodyMedium    = MondayText.Body,
    labelSmall    = MondayText.Tab,
)

3. Signature Components

Board Item Row (stripe + cells)

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
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.TextOverflow
import androidx.compose.ui.unit.dp

@Composable
fun StatusCell(label: String, color: Color, modifier: Modifier = Modifier) {
    Box(
        modifier
            .widthIn(min = 88.dp)
            .clip(RoundedCornerShape(4.dp))
            .background(color)
            .padding(horizontal = 10.dp, vertical = 5.dp),
        contentAlignment = Alignment.Center,
    ) {
        Text(label.uppercase(), style = MondayText.StatusLabel.copy(color = onColor(color)))
    }
}

@Composable
fun BoardItemRow(
    groupColor: Color,
    name: String,
    personInitials: String,
    personColor: Color,
    status: StatusOption,
    modifier: Modifier = Modifier,
) {
    Column(modifier.fillMaxWidth()) {
        Row(Modifier.fillMaxWidth().height(IntrinsicSize.Min)) {
            Box(Modifier.width(6.dp).fillMaxHeight().background(groupColor)) // group stripe, flush leading
            Row(
                Modifier.weight(1f).padding(horizontal = 14.dp, vertical = 12.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(10.dp),
            ) {
                Text(name, style = MondayText.ItemName.copy(color = MondayColors.TextPrimary),
                     maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f))
                Box(
                    Modifier.size(24.dp).clip(CircleShape).background(personColor),
                    contentAlignment = Alignment.Center,
                ) { Text(personInitials, style = MondayText.PersonInit.copy(color = Color.White)) }
                StatusCell(status.label, status.color)
            }
        }
        HorizontalDivider(color = MondayColors.Divider, thickness = 1.dp)
    }
}

Group Header (colored, collapsible)

import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.ui.draw.rotate

@Composable
fun GroupHeader(name: String, groupColor: Color, count: Int, expanded: Boolean, onToggle: () -> Unit) {
    val rot by animateFloatAsState(if (expanded) 90f else 0f, tween(150), label = "groupCaret")
    Row(
        Modifier
            .fillMaxWidth()
            .clickable { onToggle() }
            .padding(horizontal = 18.dp).padding(top = 12.dp, bottom = 8.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        Icon(Icons.Filled.ChevronRight, null, tint = groupColor, modifier = Modifier.size(14.dp).rotate(rot))
        Text(name, style = MondayText.GroupHeader.copy(color = groupColor))
        Text("$count items", style = MondayText.ColumnHead.copy(color = MondayColors.TextTertiary))
    }
}

Status Label Picker (ModalBottomSheet grid)

import androidx.compose.foundation.layout.FlowRow
import androidx.compose.material3.ModalBottomSheet

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StatusPickerSheet(
    options: List<StatusOption> = defaultStatuses,
    onPick: (StatusOption) -> Unit,
    onDismiss: () -> Unit,
) {
    ModalBottomSheet(onDismissRequest = onDismiss, containerColor = MondayColors.Canvas) {
        FlowRow(
            Modifier.fillMaxWidth().padding(20.dp),
            horizontalArrangement = Arrangement.spacedBy(10.dp),
            verticalArrangement = Arrangement.spacedBy(10.dp),
        ) {
            options.forEach { opt ->
                Box(
                    Modifier
                        .weight(1f, fill = false)
                        .defaultMinSize(minWidth = 140.dp)
                        .clip(RoundedCornerShape(4.dp))
                        .background(opt.color)
                        .clickable { onPick(opt) }
                        .padding(vertical = 14.dp),
                    contentAlignment = Alignment.Center,
                ) { Text(opt.label.uppercase(), style = MondayText.StatusLabel.copy(color = onColor(opt.color))) }
            }
        }
    }
}

Battery Rollup (group health)

@Composable
fun BatteryRollup(segments: List<Pair<Color, Float>>, modifier: Modifier = Modifier) {
    Row(
        modifier
            .fillMaxWidth()
            .height(24.dp)
            .clip(RoundedCornerShape(4.dp))
            .background(MondayColors.NightSurface2),
    ) {
        segments.forEach { (color, fraction) ->
            val animated by animateFloatAsState(fraction, tween(250), label = "battery")
            Box(Modifier.weight(animated.coerceAtLeast(0.0001f)).fillMaxHeight().background(color))
        }
    }
}

Floating Action Button

import androidx.compose.material.icons.filled.Add

@Composable
fun CreateFab(onClick: () -> Unit) {
    FloatingActionButton(
        onClick = onClick,
        containerColor = MondayColors.Blue,
        contentColor = Color.White,
        shape = CircleShape,
        modifier = Modifier.size(56.dp),
    ) { Icon(Icons.Filled.Add, contentDescription = "Create", modifier = Modifier.size(24.dp)) }
}

4. Navigation

monday.com has minimal chrome: a 4-tab bottom strip with the action-blue accent (no Material tint pill) and per-board view tabs.

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

@Composable
fun MondayBottomBar(selected: Int, onSelect: (Int) -> Unit) {
    NavigationBar(containerColor = MondayColors.Canvas, tonalElevation = 0.dp) {
        val items = listOf(
            "Home"   to Icons.Filled.Home,
            "Boards" to Icons.Filled.GridView,
            "Inbox"  to Icons.Filled.Notifications,
            "Search" to Icons.Filled.Search,
        )
        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 = MondayText.Tab) },
                colors = NavigationBarItemDefaults.colors(
                    selectedIconColor = MondayColors.Blue,
                    selectedTextColor = MondayColors.Blue,
                    unselectedIconColor = MondayColors.TextTertiary,
                    unselectedTextColor = MondayColors.TextTertiary,
                    indicatorColor = Color.Transparent, // no Material pill — monday.com has none
                ),
            )
        }
    }
}

The per-board view switcher (Main Table / Kanban / Timeline / Calendar / Chart) is a Row of clickable labels; the active one gets TextPrimary plus a 2.5.dp blue underline via Modifier.drawBehind. The Kanban view is a horizontally scrolling LazyRow of ~260.dp columns containing draggable cards; dropping a card into a column changes its status and recolors the stripe.

5. Motion

monday.com motion is energetic but purposeful — color transitions carry feedback.

Moment Compose recipe
Status change ModalBottomSheet picker (Material slide-up); on pick, animate cell color via animateColorAsState(tween(200)); soft haptic
Group collapse/expand caret animateFloatAsState 0 → 90° tween(150); items AnimatedVisibility(expandVertically(tween(200)) + fadeIn())
FAB → new item navigate to a full-screen / bottom-sheet item editor; FAB container scale on press
Drag item / Kanban card LazyColumn/LazyRow + Modifier.draggable; neighbors animateItemPlacement(tween(200)); soft haptic on drop; stripe + status recolor
Battery rollup each segment animateFloatAsState(tween(250)) on a status change
View tab switch 2.5.dp blue underline animateDpAsState translateX 200ms; board Crossfade(tween(150))
Confetti board milestone → a brief Canvas-drawn confetti burst (~1s)
Page navigation Nav3/NavHost slide push tween(300)
// The canonical monday.com feedback — status cell color crossfade
val cellColor by animateColorAsState(status.color, tween(200), label = "statusCell")
Box(Modifier.background(cellColor)) { /* label */ }

Haptics: LocalHapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) on status change, drag start, and group toggle. For a softer tick use LocalView.current.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK). Auto-save is silent; only show a “Saved” snackbar on manual save or error.

6. Icons

monday.com’s iconography maps cleanly to androidx.compose.material:material-icons-extended. The group stripe and status cell are drawn shapes, not icons.

Purpose SF Symbol (iOS) Material Icon (Compose)
Home (tab) house / house.fill Icons.Filled.Home
Boards (tab) tablecells Icons.Filled.GridView
Inbox (tab) bell / bell.fill Icons.Filled.Notifications
Search (tab) magnifyingglass Icons.Filled.Search
Add / FAB plus Icons.Filled.Add
Group caret chevron.right Icons.Filled.ChevronRight (rotate 90°)
Person (empty) person.crop.circle Icons.Filled.AccountCircle
Date column calendar Icons.Filled.CalendarToday
Timeline column chart.bar.xaxis Icons.Filled.Timeline
Numbers column number Icons.Filled.Tag
More ellipsis Icons.Filled.MoreHoriz
Back chevron.left Icons.Filled.ArrowBack
Filter line.3.horizontal.decrease Icons.Filled.FilterList
Sort arrow.up.arrow.down Icons.Filled.SwapVert
Updates bubble.left Icons.Filled.ChatBubbleOutline
Automation bolt Icons.Filled.Bolt
Main Table view tablecells Icons.Filled.GridOn
Kanban view rectangle.split.3x1 Icons.Filled.ViewKanban
Timeline view calendar.day.timeline.left Icons.Filled.AccountTree
Chart view chart.pie Icons.Filled.PieChart

7. Minimum SDK & Accessibility Notes

  • minSdk 24 (Compose floor is 21; modern motion comfortable at 24). compileSdk 34+, Compose BOM 2024.09+, Kotlin 2.0+.
  • Edge-to-edge: call enableEdgeToEdge(); light canvas wants dark-content system bars (light-content in night). The FAB sits above the NavigationBar and respects the gesture inset; inline cell edit uses Modifier.imePadding().
  • Font scaling: sp honors the user’s font scale — keep it on screen/board/group titles, body, item names. Pin layout-sensitive text (status labels, 10sp tab labels, person initials, column headers) by deriving from dp or CompositionLocalProvider(LocalDensity provides Density(density, fontScale = 1f)). Add fontFeatureSettings = "tnum" on Numbers/date/percentage text.
  • TalkBack: never rely on cell color alone — always render the label text and set contentDescription = "Status: {label}, double tap to change"; announce the group stripe via group context (“Group {name}, item {name}”); the battery rollup as “Group progress: {x}% done, {y}% working, {z}% stuck”. Status cell is Role.Button.
  • Touch targets: Material guidance is 48.dp — the status cell row is ≥ 48.dp tall and fully tappable; give the 14.dp group caret a 48.dp hit area; the FAB is 56.dp; primary buttons ≥ 36.dp.
  • Contrast: white on #00C875 / #FDAB3D / #E2445C / #0073EA passes WCAG AA at 11sp/700. Yellow #FFCB00, teal #00D2D2, and bright-green #9CD326 MUST use #323338 text (white fails) — the onColor() helper enforces this; validate any user-remapped color.
  • Reduce motion: when Settings.Global.ANIMATOR_DURATION_SCALE == 0, disable the cell-color crossfade and battery-segment animation (snap instead) and skip confetti; keep the soft haptic and selection state.
  • Night mode: invert chrome via the Night* palette — #181B34 deep indigo, NOT true black; text → #F5F6F8. Keep the multicolor data palette fully saturated and identical — meaning must never wash out. Shadows vanish on night, so the 1dp NightCellBorder on Kanban cards is the elevation cue; the FAB keeps its blue surface. Do not enable Material You dynamicColorScheme() — monday.com’s multicolor 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