UI Components

Reusable Compose controls, feedback states, media components, and gallery demos.

Reusable Compose UI lives under core/ui/components, while the Component tab shows a live, data-driven catalog of those controls. Treat the tab as the visual reference when composing new screens.

What it provides

SurfaceStatusImplementation
Shared controlsIntegratedcore/ui/components/*.kt
Component galleryIntegratedfeature/component/ComponentCatalog.kt and nested component graph
Toast overlayIntegratedToastController + ToastHost at the activity root
HapticsIntegratedHaptics singleton with selection, success, warning, and error feedback
Remote imagesExampleAppAsyncImage wraps Coil 3

Component inventory

FileUse it for
AppButton.ktPrimary, secondary, outline, destructive, ghost, and link buttons in multiple sizes
AppTextField.ktLabeled inputs with leading/trailing icons, secure entry, and error text
AppCard.ktGrouped surface cards for rows, widgets, and settings sections
AppAsyncImage.ktCoil-backed remote image with placeholder/fallback behavior
DetailScaffold.ktDetail pages with a centered top app bar and back action
OfflineBanner.ktSlim connectivity banner driven by NetworkObserver state
OtpInput.ktSix-box one-time-code input
PasswordStrengthBar.ktPassword strength meter and pure passwordStrength() helper
SectionHeader.ktUppercase grouped-section labels
SectionedDocument.ktLegal/guide-style document sections
State.ktEmptyState, LoadingState, and ErrorState placeholders
ThemeToggle.ktSegmented theme picker for System/Light/Dark

The Component tab is implemented as a feature-local graph:

ComponentGraph
  -> ComponentHome
  -> ComponentDetail(route: ComponentRoute)

ComponentCatalog groups entries into Controls, Inputs, Containers, Feedback, and Media. Current detail demos cover:

RouteDemo
ButtonsButton variants, loading/disabled states
ThemeToggleLive theme segmented control
TextFieldsInputs, icons, password transformation, errors
PasswordStrengthInteractive password meter
OtpInputOTP boxes with completion callback
CardsGrouped-surface containers
SectionHeadersSettings-style section labels
StatesLoading, empty, and error placeholders
OfflineBannerConnectivity banner
ToastInfo/success/error transient messages
AsyncImageRemote image rendering via Coil

Some gallery titles and summaries are plain Kotlin strings today. If you need the gallery itself localized, convert ComponentEntry.title and summary to string resource ids.

Toasts

ToastController exposes a buffered SharedFlow<ToastEvent> and variants:

enum class ToastVariant { Info, Success, Error }

MainActivity renders one ToastHost above the root AppNavGraph, aligned to the top center and padded for status bars. Success and error toasts trigger haptics automatically through ToastController.

Use it from a ViewModel or controller:

toastController.showSuccess("Saved")
toastController.showError("Could not save")

Haptics

Haptics resolves VibratorManager.defaultVibrator on Android 12+ and falls back to Vibrator on older versions. It no-ops when the device has no vibrator.

Available calls:

MethodUse
selection() / light()small UI ticks
medium() / heavy()stronger press feedback
success()success waveform
warning()warning waveform
error()error waveform

Compose a new screen

  1. Start with a route composable that gets its ViewModel via hiltViewModel().
  2. Collect the ViewModel state and pass plain values/callbacks into a stateless screen composable.
  3. Use DetailScaffold for pushed detail screens.
  4. Use AppTextField, AppButton, and AppCard before creating one-off controls.
  5. Render loading, empty, and error states with State.kt components.
  6. Use MaterialTheme.colorScheme.groupedBackground and grouped surfaces for app screens.
  7. Trigger transient feedback through ToastController; it already handles haptics for success and error.

On this page