# Write Composable

> Use when creating a new Composable or modifying an existing one in the Alkaa project — screen structure, state handling, adaptive layouts, Kuvio usage, or previews.

- Skill: `igorescodro/write-composable` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add igorescodro/write-composable`
- Raw SKILL.md: https://api.skillmd.com/api/skills/igorescodro/write-composable/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: igorescodro (https://skillmd.com/u/igorescodro)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/igorescodro/write-composable

---


# Write Composable

## Overview

Composables in Alkaa follow a strict three-layer Screen → Loader → Content pattern. All UI uses Kuvio components exclusively, state flows from ViewModel through the Loader, and every composable requires both dark and light previews.

## Screen Structure

Every screen has three layers: `<Feature>Screen` (public, stateless, NavGraph entry point) → `<Feature>Loader` (internal, injects ViewModel, collects state) → `<Feature>Content` (internal, stateless, tested with Compose Testing).

→ See `references/SCREEN_PATTERNS.md` for Kotlin code examples of each layer.

For screens with list→detail relationships, a two-pane adaptive layout is required on wide windows (tablets, desktop). The `isSinglePane` boolean originates at the NavGraph entry and flows unchanged to the Loader where branching happens.

→ See `references/ADAPTIVE_LAYOUTS.md` for `isSinglePane` flow, Loader branching pattern, `ListDetailPaneScaffold`, and toolbar adaptation.

## Rules

| Rule | Details |
|------|---------|
| **Kuvio only** | Use `KuvioText`, `KuvioIcon`, etc. — never raw `Text`, `Icon`, or Material components |
| **Modifier param** | Every rendering composable accepts `modifier: Modifier = Modifier` |
| **Paddings** | Always even numbers, multiples of 4 (4, 8, 12, 16, 24 dp) |
| **Snackbar** | Use Snackbar, never Toast |
| **Adaptive** | Screens must work on landscape, tablets, and desktop |
| **Previews** | All composables need both dark and light previews |
| **isSinglePane origin** | Always computed at NavGraph entry via `currentWindowAdaptiveInfo().windowSizeClass.isSinglePane()` |

## Red Flags

Stop if you notice:
- `koinInject()` or `get()` called outside of a `Loader`
- `Text(text = ..., style = ...)` anywhere in the code
- No `Modifier` parameter on a rendering composable
- A single preview without dark/light variants
- Odd padding or padding not a multiple of 4
- A needed Kuvio component doesn't exist → implement with `write-design-system-component` first

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Injecting ViewModel outside a Loader | Move all `koinViewModel()` / `koinInject()` calls to `<Feature>Loader` |
| Using raw `Text(style = ...)` | Use the appropriate `Kuvio*Text` variant instead |
| Mapping or transforming data inside a composable | Push logic to the ViewModel; composables only render |
| Missing `Modifier` parameter on a rendering composable | Add `modifier: Modifier = Modifier` to every rendering function |
| Single preview without dark/light variants | Always provide both light and dark `@Preview` |
| Non-multiple-of-4 padding | Use multiples of 4 (4, 8, 12, 16, 24 dp) |
| Computing `isSinglePane` inside a composable | Always compute at NavGraph entry, never inside the screen |
| Passing `isSinglePane = true` to detail pane in `ListDetailPaneScaffold` | Side-panel detail always receives `false` |
| Calling `navigator.navigateTo()` without `coroutineScope.launch` | `navigateTo` is a suspend function — wrap in `coroutineScope.launch` |

## Related Skills

- **Missing Kuvio component** → use `write-design-system-component` skill before implementing the composable
- **User-facing strings** → use `localization` skill
- **Testing composable behavior** → use `write-ui-tests` skill

