coderef

Kik

Kik is a username-based messenger whose entire product premise — and therefore its design — is anonymity without a phone number.
DownloadRaw
Kik

Build with the Kik look.

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

Palette

At a glance

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

Add it to your project

Then prompt your agent

Design System · Kik

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

Kik (iOS) — Jetpack Compose Implementation Guide

Companion to DESIGN.md — the framework-neutral spec. This file ports Kik’s visual language to Android with Jetpack Compose (Material 3): a color token object, a Typography set, paste-ready @Composables, the bubble + S/D/R receipt + Kik Code, 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 (Kik’s modern Blue brand, the gray/blue bubble pair, the single-letter S/D/R receipt, username-first identity, bots, Kik Codes) while making everything idiomatic Android — NavigationBar instead of a UITabBar, sp/dp. Kik is light-first; a neutral near-black dark scheme is provided.

Assumes Compose BOM 2024.09+, Kotlin 2.0+, minSdk 24, Material 3, and Coil 2.6+ for avatars / Kik Code art.

1. Color Tokens

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

object KikColors {
    // Brand Kik Blue (modern) + Green (heritage)
    val Blue        = Color(0xFF00B0F0)
    val BlueDeep    = Color(0xFF0093C8)
    val BluePressed = Color(0xFF0086B8)
    val Cyan        = Color(0xFF4FCBF7)
    val GreenLegacy = Color(0xFF82BC23) // heritage accent only
    val InkOnBlue   = Color(0xFF002A36)

    // Canvas & Surfaces (Light)
    val Canvas      = Color(0xFFFFFFFF)
    val Incoming    = Color(0xFFE9EAEC)
    val SurfaceGray = Color(0xFFF3F4F6)
    val RowPressed  = Color(0xFFE7E8EB)
    val Divider     = Color(0xFFE1E2E5)

    // Canvas & Surfaces (Dark) — neutral near-black
    val DarkCanvas   = Color(0xFF121316)
    val DarkSurface1 = Color(0xFF1B1D21)
    val DarkSurface2 = Color(0xFF25282D)
    val DarkDivider  = Color(0xFF31343A)

    // Text
    val TextPrimary       = Color(0xFF16181B)
    val TextPrimaryDark   = Color(0xFFF1F2F4)
    val TextSecondary     = Color(0xFF6C6F77)
    val TextSecondaryDark = Color(0xFF9CA0A8)
    val TextTertiary      = Color(0xFF9A9DA5)
    val TextTertiaryDark  = Color(0xFF686C74)

    // Semantic
    val Error = Color(0xFFF0473E)
    val Link  = Color(0xFF0093C8)
}

Wire it into both schemes. Kik is light-first; dark uses a neutral near-black #121316, never color-cast.

// 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 KikLight = lightColorScheme(
    primary        = KikColors.Blue,
    onPrimary      = KikColors.InkOnBlue,   // dark-cyan ink on bright blue (AA)
    background     = KikColors.Canvas,
    onBackground   = KikColors.TextPrimary,
    surface        = KikColors.SurfaceGray,
    onSurface      = KikColors.TextPrimary,
    surfaceVariant = KikColors.Incoming,
    outline        = KikColors.Divider,
    error          = KikColors.Error,
)

private val KikDark = darkColorScheme(
    primary        = KikColors.Blue,
    onPrimary      = KikColors.InkOnBlue,
    background      = KikColors.DarkCanvas,
    onBackground   = KikColors.TextPrimaryDark,
    surface        = KikColors.DarkSurface1,
    onSurface      = KikColors.TextPrimaryDark,
    surfaceVariant = KikColors.DarkSurface2,
    outline        = KikColors.DarkDivider,
    error          = KikColors.Error,
)

@Composable
fun KikTheme(
    dark: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit,
) = MaterialTheme(
    colorScheme = if (dark) KikDark else KikLight,
    typography = KikTypography,
    content = content,
)

2. Typography (M3)

Kik’s brand face is proprietary; bundle Inter (SIL OFL) in res/font/ as the closest free substitute. Heavy 700/800 titles & username headline; body 400 (outgoing bumps to 500 for ink crispness on Blue). pt → sp 1:1.

// ui/theme/KikType.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),
)

object KikText {
    val LargeTitle  = TextStyle(Inter, fontWeight = FontWeight.ExtraBold, fontSize = 32.sp, lineHeight = 38.sp, letterSpacing = (-0.4).sp)
    val ScreenTitle = TextStyle(Inter, fontWeight = FontWeight.ExtraBold, fontSize = 26.sp, lineHeight = 31.sp, letterSpacing = (-0.3).sp)
    val Title3      = TextStyle(Inter, fontWeight = FontWeight.Bold,      fontSize = 22.sp, lineHeight = 27.sp, letterSpacing = (-0.2).sp)
    val Headline    = TextStyle(Inter, fontWeight = FontWeight.Bold,      fontSize = 17.sp, lineHeight = 22.sp, letterSpacing = (-0.2).sp)
    val RowTitle    = TextStyle(Inter, fontWeight = FontWeight.Bold,      fontSize = 15.sp, lineHeight = 20.sp, letterSpacing = (-0.1).sp)
    val Body        = TextStyle(Inter, fontWeight = FontWeight.Normal,    fontSize = 14.sp, lineHeight = 19.sp)
    val BodyOut     = TextStyle(Inter, fontWeight = FontWeight.Medium,    fontSize = 14.sp, lineHeight = 19.sp)
    val BodyEmph    = TextStyle(Inter, fontWeight = FontWeight.SemiBold,  fontSize = 14.sp, lineHeight = 19.sp)
    val Preview     = TextStyle(Inter, fontWeight = FontWeight.Medium,    fontSize = 13.sp, lineHeight = 18.sp)
    val Footnote    = TextStyle(Inter, fontWeight = FontWeight.SemiBold,  fontSize = 13.sp, lineHeight = 18.sp)
    val Caption     = TextStyle(Inter, fontWeight = FontWeight.SemiBold,  fontSize = 11.sp, lineHeight = 15.sp, letterSpacing = 0.1.sp)
    val Button      = TextStyle(Inter, fontWeight = FontWeight.Bold,      fontSize = 16.sp, lineHeight = 16.sp)
    val Receipt     = TextStyle(Inter, fontWeight = FontWeight.Bold,      fontSize = 11.sp, lineHeight = 11.sp, letterSpacing = 0.2.sp)
    val Tab         = TextStyle(Inter, fontWeight = FontWeight.SemiBold,  fontSize = 11.sp, lineHeight = 13.sp, letterSpacing = 0.1.sp)
}

val KikTypography = Typography(
    headlineLarge = KikText.LargeTitle,
    headlineMedium = KikText.ScreenTitle,
    titleMedium   = KikText.Headline,
    bodyLarge     = KikText.Body,
    labelSmall    = KikText.Tab,
)

3. Signature Components

Message Bubble (in / out, with tail)

import androidx.compose.foundation.background
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

enum class SDR { Sending, Sent, Delivered, Read, Failed }

@Composable
fun KikBubble(text: String, outgoing: Boolean, tailEnd: Boolean, timestamp: String, receipt: SDR, dark: Boolean) {
    val shape = RoundedCornerShape(
        topStart = 16.dp, topEnd = 16.dp,
        bottomStart = if (!outgoing && tailEnd) 5.dp else 16.dp,
        bottomEnd   = if ( outgoing && tailEnd) 5.dp else 16.dp,
    )
    Column(Modifier.fillMaxWidth(), horizontalAlignment = if (outgoing) Alignment.End else Alignment.Start) {
        Box(
            Modifier
                .fillMaxWidth(0.80f)
                .wrapContentWidth(if (outgoing) Alignment.End else Alignment.Start)
                .clip(shape)
                .background(if (outgoing) KikColors.Blue else if (dark) KikColors.DarkSurface2 else KikColors.Incoming)
                .padding(horizontal = 13.dp, vertical = 8.dp)
        ) {
            Text(text, style = if (outgoing) KikText.BodyOut else KikText.Body,
                color = if (outgoing) KikColors.InkOnBlue else if (dark) KikColors.TextPrimaryDark else KikColors.TextPrimary)
        }
        if (outgoing) {
            Row(Modifier.padding(top = 3.dp, end = 4.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(5.dp)) {
                Text(timestamp, fontSize = 10.sp, color = if (dark) KikColors.TextTertiaryDark else KikColors.TextTertiary)
                SDRReceipt(receipt, dark)
            }
        }
    }
}

S / D / R Receipt

@Composable
fun SDRReceipt(state: SDR, dark: Boolean) {
    val letter = when (state) {
        SDR.Sending -> "…"; SDR.Sent -> "S"; SDR.Delivered -> "D"; SDR.Read -> "R"; SDR.Failed -> "!"
    }
    val color = when (state) {
        SDR.Sending, SDR.Sent -> if (dark) KikColors.TextTertiaryDark else KikColors.TextTertiary
        SDR.Delivered -> if (dark) KikColors.TextSecondaryDark else KikColors.TextSecondary
        SDR.Read -> KikColors.Blue   // the one color moment
        SDR.Failed -> KikColors.Error
    }
    // No animation — instant glyph swap; only the R recolor reads as an event
    Text(letter, style = KikText.Receipt, color = color)
}

Conversation Row (person vs bot)

import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.graphics.Brush

@Composable
fun KikConvRow(
    name: String, preview: String, time: String, isBot: Boolean,
    unread: Int?, receipt: SDR?, gradient: List<Color>, dark: Boolean,
) {
    Row(
        Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp, vertical = 12.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(12.dp),
    ) {
        Box(
            Modifier.size(48.dp)
                .clip(if (isBot) RoundedCornerShape(14.dp) else CircleShape)
                .background(Brush.linearGradient(gradient)),
            contentAlignment = Alignment.Center,
        ) { Text(name.take(1), color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.Bold) }

        Column(Modifier.weight(1f)) {
            Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(5.dp)) {
                Text(name, style = KikText.RowTitle, maxLines = 1,
                    color = if (dark) KikColors.TextPrimaryDark else KikColors.TextPrimary)
                if (isBot) {
                    Box(
                        Modifier.clip(RoundedCornerShape(4.dp))
                            .background(if (dark) KikColors.DarkSurface2 else KikColors.SurfaceGray)
                            .padding(horizontal = 5.dp, vertical = 1.dp)
                    ) { Text("BOT", fontSize = 9.sp, fontWeight = FontWeight.Bold,
                        color = if (dark) KikColors.TextSecondaryDark else KikColors.TextSecondary) }
                }
            }
            Text(preview, style = KikText.Preview, maxLines = 1,
                color = if (dark) KikColors.TextSecondaryDark else KikColors.TextSecondary)
        }

        Column(horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.spacedBy(5.dp)) {
            Text(time, fontSize = 11.sp, color = if (dark) KikColors.TextTertiaryDark else KikColors.TextTertiary)
            when {
                unread != null -> Box(
                    Modifier.clip(CircleShape).background(KikColors.Blue).defaultMinSize(18.dp, 18.dp).padding(horizontal = 5.dp),
                    contentAlignment = Alignment.Center,
                ) { Text("$unread", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = KikColors.InkOnBlue) }
                receipt != null -> SDRReceipt(receipt, dark)
            }
        }
    }
}

Kik Code

import androidx.compose.foundation.Canvas
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.Stroke

@Composable
fun KikCode(modifier: Modifier = Modifier) {
    // Flat, high-contrast — NO shadow (must scan reliably)
    Box(
        modifier.size(120.dp).clip(RoundedCornerShape(24.dp)).background(KikColors.Blue),
        contentAlignment = Alignment.Center,
    ) {
        Canvas(Modifier.size(96.dp)) {
            drawCircle(
                color = Color.White,
                radius = size.minDimension / 2 - 3.dp.toPx(),
                center = center,
                style = Stroke(width = 6.dp.toPx(), pathEffect = PathEffect.dashPathEffect(floatArrayOf(2.dp.toPx(), 6.dp.toPx()))),
            )
        }
        Box(Modifier.size(44.dp).clip(RoundedCornerShape(12.dp)).background(Color.White))
    }
}

Compose Bar (text “Send” label)

import androidx.compose.foundation.clickable
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Icon
import androidx.compose.runtime.*

@Composable
fun KikComposeBar(dark: Boolean, onSend: (String) -> Unit) {
    var text by remember { mutableStateOf("") }
    val empty = text.isBlank()
    Row(
        Modifier.fillMaxWidth()
            .background(if (dark) KikColors.DarkSurface1 else KikColors.Canvas)
            .padding(horizontal = 14.dp, vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(10.dp),
    ) {
        Icon(Icons.Filled.Add, "Attach", tint = if (dark) KikColors.TextSecondaryDark else KikColors.TextSecondary, modifier = Modifier.size(22.dp))
        Box(
            Modifier.weight(1f).heightIn(min = 36.dp).clip(RoundedCornerShape(18.dp))
                .background(if (dark) KikColors.DarkSurface2 else KikColors.SurfaceGray)
                .padding(horizontal = 14.dp, vertical = 8.dp),
        ) {
            BasicTextField(
                value = text, onValueChange = { text = it },
                textStyle = KikText.Body.copy(color = if (dark) KikColors.TextPrimaryDark else KikColors.TextPrimary),
                decorationBox = { inner ->
                    if (empty) Text("Type a message", style = KikText.Body, color = if (dark) KikColors.TextTertiaryDark else KikColors.TextTertiary)
                    inner()
                },
            )
        }
        Text(
            "Send",
            style = KikText.Receipt.copy(fontSize = 14.sp),
            color = if (empty) (if (dark) KikColors.TextTertiaryDark else KikColors.TextTertiary) else KikColors.Blue,
            modifier = Modifier.clickable(enabled = !empty) { onSend(text); text = "" }.padding(horizontal = 6.dp),
        )
    }
}

4. Navigation

Kik is text/bot-first — no call/video tab. On Android model the strip as a NavigationBar with no tint pill — active is bright Kik Blue.

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

@Composable
fun KikBottomBar(selected: Int, onSelect: (Int) -> Unit) {
    NavigationBar(containerColor = KikColors.Canvas, tonalElevation = 0.dp) {
        val items = listOf(
            "Chats" to Icons.Filled.ChatBubble,
            "People" to Icons.Filled.People,
            "Discover" to Icons.Filled.AutoAwesome,
            "Kik Code" to Icons.Filled.QrCode,
        )
        items.forEachIndexed { i, (label, icon) ->
            NavigationBarItem(
                selected = selected == i,
                onClick = { onSelect(i) },
                icon = { Icon(icon, label, Modifier.size(22.dp)) },
                label = { Text(label, style = KikText.Tab) },
                colors = NavigationBarItemDefaults.colors(
                    selectedIconColor = KikColors.Blue,
                    selectedTextColor = KikColors.Blue,
                    unselectedIconColor = KikColors.TextTertiary,
                    unselectedTextColor = KikColors.TextTertiary,
                    indicatorColor = Color.Transparent, // no Material pill — Kik has none
                ),
            )
        }
    }
}

The chat header is a CenterAlignedTopAppBar: leading back ChevronLeft tinted Blue, centered @username Headline, trailing 32.dp gradient avatar (tap → profile / Kik Code). No call/video icons.

5. Motion

Kik motion is soft spring, 0.15–0.32s.

Moment Compose recipe
Outgoing bubble send new bubble slideInVertically { it } + scaleIn(initialScale = 0.88f, spring(dampingRatio = 0.8f))
S/D/R receipt no animation — instant glyph swap; the R-to-Blue recolor is the only “event”
List row reorder LazyColumn items animateItemPlacement(tween(250)) when a chat bumps to the top
Bot suggested-reply chips AnimatedVisibility fadeIn(tween(200)) + slideInVertically { it / 4 }
Kik Code match plate animateFloatAsState scale 1 → 1.1 → 1 pulse, then nav push
Tab switch instant; icon tint animateColorAsState tween(150)
// Canonical outgoing-bubble entrance
AnimatedVisibility(
    visible = true,
    enter = slideInVertically(spring(dampingRatio = 0.8f)) { it / 2 } +
            scaleIn(initialScale = 0.88f, animationSpec = spring(dampingRatio = 0.8f)),
) { KikBubble(/* … */) }

Haptics: LocalHapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) for send and bot-button tap; HapticFeedbackConstants.CONFIRM (via LocalView) on Kik Code match / friend added. The receipt change is visual only (no haptic, no animation).

6. Icons

Kik uses a custom brand glyph set; the closest first-party Compose set is androidx.compose.material:material-icons-extended.

Purpose SF Symbol (iOS) Material Icon (Compose)
Back chevron.left Icons.Filled.ChevronLeft
New chat plus Icons.Filled.Add
Scan / camera qrcode.viewfinder Icons.Filled.QrCodeScanner
Attachment plus Icons.Filled.Add
Search magnifyingglass Icons.Filled.Search
Chats (tab) bubble.left.fill Icons.Filled.ChatBubble
People (tab) person.2.fill Icons.Filled.People
Discover (tab) sparkles Icons.Filled.AutoAwesome
Kik Code (tab) qrcode Icons.Filled.QrCode
GIF photo.stack Icons.Filled.Gif
Web content globe Icons.Filled.Public
Failed message exclamationmark.circle.fill Icons.Filled.ErrorOutline
Group person.3.fill Icons.Filled.Groups
Block / report hand.raised.fill Icons.Filled.Block
Settings gearshape.fill Icons.Filled.Settings
Share Kik Code square.and.arrow.up Icons.Filled.Share

7. Minimum SDK & Accessibility Notes

  • minSdk 24 (Compose floor 21; spring + Canvas comfortable at 24). compileSdk 34+, Compose BOM 2024.09+, Kotlin 2.0+.
  • Edge-to-edge: enableEdgeToEdge(); white canvas wants dark system-bar content (light-content on the neutral dark canvas). Compose bar above the IME — Modifier.imePadding() on the thread LazyColumn.
  • Font: Kik’s brand face is proprietary — bundle Inter (SIL OFL) in res/font/; keep ExtraBold/Bold for titles and the username headline. sp honors the user’s font scale.
  • Font scaling: scale titles/headline/row-title/body/previews; pin layout-sensitive text (timestamps, 11sp tab labels, the S/D/R glyph, “BOT” tag) via a fixed-fontScale LocalDensity; keep the Kik Code plate fixed.
  • TalkBack: spell receipts as values (“Sent”/“Delivered”/“Read”) via stateDescription, not the bare letter; bot rows announce “{name}, bot”; usernames read with leading “at”; the Kik Code uses contentDescription = "Kik Code, double-tap to share". The Read-Blue receipt is reinforced by the explicit “R” so it survives color-blindness.
  • Touch targets: Material guidance 48.dp — give the 22.dp attachment icon and the “Send” text label a 48.dp hit box; list rows / tabs / suggested-reply chips are full-press; the Kik Code plate is fully tappable.
  • Contrast: dark-cyan ink #002A36 on bright #00B0F0 passes WCAG AA — never white on the bright Blue; primary text on #E9EAEC/#25282D passes AA.
  • Reduce motion: when Settings.Global.ANIMATOR_DURATION_SCALE == 0, disable the bubble spring-in (crossfade) and the Kik Code pulse; the receipt has no animation to disable.
  • Dark mode: invert via the Dark* palette — neutral #121316, NOT color-cast; Kik Blue #00B0F0 holds; incoming → #25282D, text → #F1F2F4; the R receipt stays #00B0F0 in both modes. Shadows fade on dark, so add a 1dp DarkDivider border to floating menus as the elevation cue; keep the Kik Code plate flat & high-contrast for reliable scanning. Do not enable Material You dynamicColorScheme() — Kik’s modern-Blue identity must hold regardless of wallpaper.

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