# Orbit Compose Multiplatform Orbit Compose Multiplatform is a Compose Multiplatform port of Kiwi.com's Orbit design system, providing a comprehensive UI component library for building consistent, beautiful applications across Android, iOS, JVM Desktop, JS, and WebAssembly platforms. The library builds upon Material 3 and offers pre-styled components that follow Orbit's design principles, including buttons, alerts, badges, form inputs, dialogs, and more. The library consists of three main modules: `ui` for core components and theming, `icons` for Orbit's icon set, and `illustrations` for pre-built illustrations. All components are accessible through simple Composable functions and can be customized through the `OrbitTheme` object which provides colors, typography, shapes, and elevations following the Orbit design language. ## Installation Add dependencies to your Gradle configuration. ```kotlin // build.gradle.kts dependencies { implementation("glass.yasan.orbit:ui:") implementation("glass.yasan.orbit:icons:") implementation("glass.yasan.orbit:illustrations:") } ``` ## OrbitTheme The `OrbitTheme` composable wraps your application content to provide Orbit's design tokens including colors, typography, and shapes throughout the component tree. ```kotlin @Composable fun MyApp() { OrbitTheme( colors = OrbitTheme.colors, // Use default or custom colors typography = OrbitTheme.typography, shapes = OrbitTheme.shapes, ) { // Your app content with Orbit styling Column(modifier = Modifier.padding(16.dp)) { Text("Hello Orbit!", style = OrbitTheme.typography.title1) Text( "Styled text", color = OrbitTheme.colors.primary.normal, style = OrbitTheme.typography.bodyNormal ) } } } ``` ## ButtonPrimary Primary button component for main call-to-action elements. Takes a content slot for button text and optional icons. ```kotlin @Composable fun ButtonExample() { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { // Simple primary button ButtonPrimary( onClick = { /* Handle click */ }, modifier = Modifier.fillMaxWidth() ) { Text("Click me") } // Button with icon and text ButtonPrimary(onClick = { /* Handle click */ }) { Icon(Icons.Android, contentDescription = null) Text("Download App") } } } ``` ## ButtonSecondary Secondary button for less prominent actions. Uses neutral surface colors for a subtle appearance. ```kotlin @Composable fun SecondaryButtonExample() { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { ButtonPrimary(onClick = { /* Primary action */ }) { Text("Confirm") } ButtonSecondary(onClick = { /* Secondary action */ }) { Text("Cancel") } } } ``` ## ButtonCritical Critical button variant for destructive or dangerous actions. Uses critical (red) colors to warn users. ```kotlin @Composable fun CriticalButtonExample() { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { ButtonCritical( onClick = { /* Delete action */ }, modifier = Modifier.fillMaxWidth() ) { Text("Delete Account") } // Subtle variant for less prominent critical actions ButtonCriticalSubtle(onClick = { /* Action */ }) { Text("Remove Item") } } } ``` ## ButtonBundle Variants Gradient buttons for product tier visualization (Basic, Medium, Top). ```kotlin @Composable fun BundleButtonsExample() { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { ButtonBundleBasic(onClick = { /* Select basic */ }) { Text("Basic Plan") } ButtonBundleMedium(onClick = { /* Select medium */ }) { Text("Medium Plan") } ButtonBundleTop(onClick = { /* Select top */ }) { Text("Premium Plan") } } } ``` ## AlertInfo / AlertSuccess / AlertWarning / AlertCritical Alert components display important messages with semantic colors. Each variant includes a title slot, content slot, and actions slot for buttons. ```kotlin @Composable fun AlertExamples() { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { // Success alert with actions AlertSuccess( title = { Text("Thank you for subscribing") }, content = { Text("Your subscription was successfully activated. All premium content is now available.") }, actions = { ButtonPrimary(onClick = { /* Show content */ }) { Text("Show content") } ButtonSecondary(onClick = { /* Manage */ }) { Text("Manage subscription") } } ) // Warning alert without actions AlertWarning( title = { Text("Flight delayed") }, content = { Text("Your flight has been delayed by 2 hours.") } ) // Critical alert with suppressed (neutral) background AlertCritical( title = { Text("Payment failed") }, content = { Text("Please check your payment details.") }, suppressed = true, // Uses neutral background actions = { ButtonPrimary(onClick = { /* Retry */ }) { Text("Retry payment") } } ) // Info alert with custom icon AlertInfo( icon = Icons.Check, title = {}, content = { Text("Your information is up-to-date.") } ) } } ``` ## Badge Variants Badge components for displaying status labels, counts, or categories with semantic colors. ```kotlin @Composable fun BadgeExamples() { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { // Semantic badges BadgeInfo { Text("New") } BadgeSuccess { Text("Active") } BadgeWarning { Text("Pending") } BadgeCritical { Text("Error") } // Subtle variants (lighter background) BadgeInfoSubtle { Text("Info") } BadgeSuccessSubtle { Text("Done") } // Neutral badges BadgeNeutral { Text("Default") } BadgeNeutralStrong { Text("Strong") } // Badge with icon BadgeInfo( icon = { Icon(Icons.Android, contentDescription = null) } ) { Text("Android") } // Bundle badges for product tiers BadgeBundleBasic { Text("Basic") } BadgeBundleMedium { Text("Medium") } BadgeBundleTop { Text("Premium") } } } ``` ## Card Rectangular surface component that groups related content into sections with optional title, action, and description. ```kotlin @Composable fun CardExamples() { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { // Simple card with content Card { Text("Basic card content goes here.") } // Card with title and action Card( title = { Text("Flight Details") }, action = { ButtonTextLinkPrimary("View all", onClick = { /* Navigate */ }) }, description = { Text("Your upcoming flights") } ) { Column { Text("Prague to Berlin") Text("Departure: 10:30 AM") } } // Card with custom content padding and ListChoice items Card( title = { Text("Quick Actions") }, contentPadding = PaddingValues(vertical = 12.dp) ) { Column { ListChoice( onClick = { /* Navigate */ }, icon = { Icon(Icons.AirplaneTakeoff, contentDescription = null) }, trailingIcon = { Icon(Icons.ChevronForward, contentDescription = null) } ) { Text("Check-in") } ListChoice( onClick = { /* Navigate */ }, icon = { Icon(Icons.AirplaneLanding, contentDescription = null) }, trailingIcon = { Icon(Icons.ChevronForward, contentDescription = null) } ) { Text("Boarding pass") } } } } } ``` ## TextField Text input component with support for labels, placeholders, error/info messages, and leading/trailing icons. ```kotlin @Composable fun TextFieldExamples() { var name by remember { mutableStateOf("") } var email by remember { mutableStateOf("") } var hasError by remember { mutableStateOf(false) } Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { // Basic text field with label TextField( value = name, onValueChange = { name = it }, label = { Text("Full Name") }, placeholder = { Text("Enter your name") }, modifier = Modifier.fillMaxWidth() ) // Text field with icons TextField( value = email, onValueChange = { email = it }, label = { Text("Email") }, placeholder = { Text("email@example.com") }, leadingIcon = { Icon(Icons.Email, contentDescription = null) }, trailingIcon = { Icon( Icons.Close, contentDescription = "Clear", modifier = Modifier.clickable { email = "" } ) }, modifier = Modifier.fillMaxWidth() ) // Text field with error state TextField( value = "", onValueChange = { }, label = { Text("Password") }, error = { Text("Password is required") }, modifier = Modifier.fillMaxWidth() ) // Text field with info message TextField( value = "", onValueChange = { }, label = { Text("Phone") }, info = { Text("Include country code") }, modifier = Modifier.fillMaxWidth() ) } } ``` ## Checkbox Checkbox control for boolean selections with support for error states and disabled mode. ```kotlin @Composable fun CheckboxExamples() { var termsAccepted by remember { mutableStateOf(false) } var newsletterOptIn by remember { mutableStateOf(true) } Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { // Basic checkbox Row(verticalAlignment = Alignment.CenterVertically) { Checkbox( checked = termsAccepted, onCheckedChange = { termsAccepted = it } ) Text("I accept the terms and conditions") } // Checkbox with error state Row(verticalAlignment = Alignment.CenterVertically) { Checkbox( checked = false, onCheckedChange = { }, isError = true ) Text("Required field") } // Disabled checkbox Row(verticalAlignment = Alignment.CenterVertically) { Checkbox( checked = newsletterOptIn, onCheckedChange = null, // null makes it non-interactive enabled = false ) Text("Newsletter subscription (locked)") } } } ``` ## Radio Radio button for single selection from multiple options with support for error states. ```kotlin @Composable fun RadioExamples() { var selectedOption by remember { mutableStateOf(0) } val options = listOf("Economy", "Business", "First Class") Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Select cabin class:", style = OrbitTheme.typography.title5) options.forEachIndexed { index, option -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedOption = index } ) { Radio( selected = selectedOption == index, onClick = { selectedOption = index } ) Text(option, modifier = Modifier.padding(start = 8.dp)) } } // Radio with error state Row(verticalAlignment = Alignment.CenterVertically) { Radio( selected = false, onClick = { }, isError = true ) Text("Selection required", color = OrbitTheme.colors.critical.normal) } } } ``` ## Switch Toggle switch for binary on/off states with optional custom icons. ```kotlin @Composable fun SwitchExamples() { var notificationsEnabled by remember { mutableStateOf(true) } var darkModeEnabled by remember { mutableStateOf(false) } Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { // Basic switch Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { Text("Enable notifications") Switch( checked = notificationsEnabled, onCheckedChange = { notificationsEnabled = it } ) } // Switch with custom icon Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { Text("Dark mode") Switch( checked = darkModeEnabled, onCheckedChange = { darkModeEnabled = it }, icon = if (darkModeEnabled) Icons.Visibility else Icons.VisibilityOff ) } // Disabled switch Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { Text("Locked setting") Switch( checked = true, onCheckedChange = null, enabled = false ) } } } ``` ## TabRow and Tab Tab navigation components for organizing content into switchable sections. ```kotlin @Composable fun TabsExample() { var selectedTabIndex by rememberSaveable { mutableIntStateOf(0) } Column { // Fixed tab row TabRow(selectedTabIndex = selectedTabIndex) { Tab( selected = selectedTabIndex == 0, onClick = { selectedTabIndex = 0 } ) { Text("Flights") } Tab( selected = selectedTabIndex == 1, onClick = { selectedTabIndex = 1 } ) { Text("Hotels") } Tab( selected = selectedTabIndex == 2, onClick = { selectedTabIndex = 2 } ) { Text("Cars") } } // Tab content when (selectedTabIndex) { 0 -> Text("Flight search content", modifier = Modifier.padding(16.dp)) 1 -> Text("Hotel booking content", modifier = Modifier.padding(16.dp)) 2 -> Text("Car rental content", modifier = Modifier.padding(16.dp)) } // Scrollable tab row for many tabs ScrollableTabRow(selectedTabIndex = selectedTabIndex) { repeat(5) { index -> Tab( selected = selectedTabIndex == index, onClick = { selectedTabIndex = index } ) { Text("Tab ${index + 1}") } } } } } ``` ## Dialog Modal dialog component with support for titles, descriptions, illustrations, and action buttons. ```kotlin @Composable fun DialogExamples() { var showDialog by remember { mutableStateOf(false) } var showMultiActionDialog by remember { mutableStateOf(false) } Column { ButtonPrimary(onClick = { showDialog = true }) { Text("Show Dialog") } // Basic dialog with confirm/dismiss if (showDialog) { Dialog( onDismissRequest = { showDialog = false }, title = { Text("Confirm Booking") }, text = { Text("Are you sure you want to book this flight?") }, confirmButton = { ButtonPrimary( onClick = { showDialog = false }, modifier = Modifier.fillMaxWidth() ) { Text("Confirm") } }, dismissButton = { ButtonLinkSecondary( onClick = { showDialog = false }, modifier = Modifier.fillMaxWidth() ) { Text("Cancel") } }, illustration = { Image( painter = Illustrations.Success, contentDescription = null, modifier = Modifier.fillMaxWidth(0.64f) ) } ) } // Dialog with multiple actions if (showMultiActionDialog) { Dialog( onDismissRequest = { showMultiActionDialog = false }, title = { Text("Delete Account") }, text = { Text("This action cannot be undone.") }, buttons = { ButtonCritical( onClick = { /* Delete */ }, modifier = Modifier.fillMaxWidth() ) { Text("Delete") } ButtonLinkPrimary( onClick = { showMultiActionDialog = false }, modifier = Modifier.fillMaxWidth() ) { Text("Keep Account") } ButtonLinkSecondary( onClick = { showMultiActionDialog = false }, modifier = Modifier.fillMaxWidth() ) { Text("Cancel") } } ) } } } ``` ## Tile Elevated actionable tile component for displaying clickable items with optional icon, title, description, and trailing content. ```kotlin @Composable fun TileExamples() { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { // Simple tile Tile( title = { Text("Account Settings") }, onClick = { /* Navigate */ } ) // Tile with icon and description Tile( title = { Text("Flight Details") }, description = { Text("View your booking information") }, icon = { Icon(Icons.Airplane, contentDescription = null) }, onClick = { /* Navigate */ } ) // Tile with custom trailing content Tile( title = { Text("Notifications") }, description = { Text("Manage your notification preferences") }, trailingContent = { Text( text = "3 new", modifier = Modifier.align(Alignment.Top), color = OrbitTheme.colors.primary.normal, style = OrbitTheme.typography.bodyNormalMedium ) }, onClick = { /* Navigate */ } ) } } ``` ## Stepper Numeric stepper component for incrementing/decrementing integer values with min/max bounds. ```kotlin @Composable fun StepperExamples() { var passengers by remember { mutableIntStateOf(1) } var bags by remember { mutableIntStateOf(0) } Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { // Basic stepper with bounds Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { Text("Passengers") Stepper( value = passengers, onValueChange = { passengers = it }, minValue = 1, maxValue = 9 ) } // Stepper starting at 0 (inactive style) Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { Text("Extra bags") Stepper( value = bags, onValueChange = { bags = it }, minValue = 0, maxValue = 5 ) } // Stepper with custom active state Stepper( value = 2, active = true, // Force active styling onValueChange = { }, valueValidator = { it in 0..10 } ) } } ``` ## Slider Slider component for selecting a value from a continuous range with value and range labels. ```kotlin @Composable fun SliderExamples() { var price by remember { mutableFloatStateOf(250f) } var range by remember { mutableStateOf(100f..500f) } Column(verticalArrangement = Arrangement.spacedBy(24.dp)) { // Single value slider Slider( value = price, onValueChange = { price = it }, valueRange = 0f..1000f, valueLabel = { Text("$${"%.0f".format(it)}") }, startLabel = { Text("$0") }, endLabel = { Text("$1000") } ) // Slider with discrete steps Slider( value = price, onValueChange = { price = it }, valueRange = 0f..1000f, steps = 9, // Creates 10 discrete values valueLabel = { Text("$${"%.0f".format(it)}") }, startLabel = { Text("Min") }, endLabel = { Text("Max") } ) // Range slider for selecting a range RangeSlider( value = range, onValueChange = { range = it }, valueRange = 0f..1000f, valueLabel = { Text("$${"%.0f".format(it.start)} - $${"%.0f".format(it.endInclusive)}") }, startLabel = { Text("$0") }, endLabel = { Text("$1000") } ) } } ``` ## SelectField Dropdown select component for choosing from a list of options with full form field styling. ```kotlin @Composable fun SelectFieldExample() { var selectedCountry by remember { mutableStateOf("") } val countries = listOf("United States", "United Kingdom", "Germany", "France", "Spain") Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { SelectField( value = selectedCountry, options = countries, onOptionSelect = { selectedCountry = it }, label = { Text("Country") }, placeholder = { Text("Select your country") }, leadingIcon = { Icon(Icons.CountryFlag, contentDescription = null) }, info = { Text("This helps us show relevant content") }, modifier = Modifier.fillMaxWidth() ) { option -> Text(option) } // Select field with error SelectField( value = "", options = listOf("Option 1", "Option 2"), onOptionSelect = { }, label = { Text("Required Field") }, error = { Text("Please select an option") }, modifier = Modifier.fillMaxWidth() ) { option -> Text(option) } } } ``` ## Toast Toast notification component for displaying transient messages with optional icons, images, and actions. ```kotlin @Composable fun ToastExample() { val toastHostState = remember { ToastHostState() } val scope = rememberCoroutineScope() Scaffold( snackbarHost = { ToastHost(hostState = toastHostState) } ) { padding -> Column(modifier = Modifier.padding(padding)) { ButtonPrimary( onClick = { scope.launch { toastHostState.showToast( message = "Booking confirmed!", iconName = IconName.CheckCircle ) } } ) { Text("Show Toast") } ButtonSecondary( onClick = { scope.launch { val result = toastHostState.showToast( message = "Flight added to favorites", actionLabel = "Undo" ) if (result == ToastResult.ActionPerformed) { // Handle undo action } } } ) { Text("Toast with Action") } } } } ``` ## Colors and Typography Access Orbit's design tokens through OrbitTheme for consistent styling across your application. ```kotlin @Composable fun ThemeTokensExample() { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { // Typography styles Text("Title 1", style = OrbitTheme.typography.title1) Text("Title 3", style = OrbitTheme.typography.title3) Text("Body Large", style = OrbitTheme.typography.bodyLarge) Text("Body Normal Medium", style = OrbitTheme.typography.bodyNormalMedium) Text("Body Small", style = OrbitTheme.typography.bodySmall) // Color usage Text("Primary color", color = OrbitTheme.colors.primary.normal) Text("Info color", color = OrbitTheme.colors.info.normal) Text("Success color", color = OrbitTheme.colors.success.normal) Text("Warning color", color = OrbitTheme.colors.warning.normal) Text("Critical color", color = OrbitTheme.colors.critical.normal) // Surface colors for backgrounds Box( modifier = Modifier .background(OrbitTheme.colors.surface.subtle) .padding(16.dp) ) { Text("Content on subtle surface") } // Content emphasis Text("Normal emphasis", emphasis = ContentEmphasis.Normal) Text("Minor emphasis", emphasis = ContentEmphasis.Minor) Text("Disabled text", emphasis = ContentEmphasis.Disabled) } } ``` ## Icons and Illustrations Access Orbit's icon and illustration sets through the Icons and Illustrations objects. ```kotlin @Composable fun IconsAndIllustrationsExample() { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { // Icons Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Icon(Icons.Airplane, contentDescription = "Airplane") Icon(Icons.Hotel, contentDescription = "Hotel") Icon(Icons.Check, contentDescription = "Check") Icon(Icons.Close, contentDescription = "Close") Icon(Icons.ChevronForward, contentDescription = "Navigate") } // Icon with custom color Icon( painter = Icons.Heart, contentDescription = "Favorite", tint = OrbitTheme.colors.critical.normal ) // Illustrations Image( painter = Illustrations.Success, contentDescription = "Success", modifier = Modifier.size(120.dp) ) Image( painter = Illustrations.NoResults, contentDescription = "No results found", modifier = Modifier.fillMaxWidth(0.5f) ) } } ``` ## Summary Orbit Compose Multiplatform provides a comprehensive design system implementation for building consistent, beautiful applications across multiple platforms. The library is ideal for travel-related applications (originally designed for Kiwi.com) but its semantic color system, typography scale, and component library make it suitable for any application requiring a polished, accessible UI. Key integration patterns include wrapping your application in OrbitTheme, using semantic color names (primary, info, success, warning, critical) for consistency, and leveraging the slot-based API design for customizable components. The library follows Compose best practices with Composable functions accepting Modifier parameters, using slot APIs for flexible content, and providing sensible defaults. Components integrate seamlessly with Material 3 while maintaining Orbit's distinctive design language. For multiplatform projects, the library handles platform-specific rendering automatically, allowing developers to write UI code once and deploy across Android, iOS, Desktop, Web, and WebAssembly targets.