
Language-learning flashcard UI handling full card lifecycle: 3D flips, settled-lock, confirm/dismiss exit animations, audio button states, language pills, ghost stack, and extensive styling/customization.
A language-learning flashcard component for Compose Multiplatform (Android & iOS).
CuteCard handles the full interaction lifecycle of a single flashcard - 3D flip animation, settled-lock delay, confirm/dismiss exit animations, and audio button visuals - so you can focus on your deck logic and data.
implementation("site.llinsoft:cutecard:0.5.1")var isPlaying by remember { mutableStateOf(false) }
CuteCard(
content = CuteCardContent(
word = "hierro",
translation = "iron",
phonetics = "[ˈje.ro]",
wordClass = "noun",
audioUrl = "https://example.com/hierro.mp3"
),
onKnown = { /* advance to next card */ },
onUnknown = { /* re-queue or move on */ },
isPlaying = isPlaying,
onAudioRequested = {
isPlaying = true
player.play(content.audioUrl) { isPlaying = false }
}
)onKnown and onUnknown are called after their exit animations complete — replace the card there.onAudioRequested. Pass null to hide the button.config = CuteCardConfig(frontSide = CardFrontSide.Translation)Word (default) shows the word first and reveals translation, phonetics, word class and audio. Translation reverses it.
style = CuteCardDefaults.style().copy(
cardBackgroundColor = Color(0xFFFFF8F0),
wordTextColor = Color(0xFF2C2C2C)
)
style = CuteCardDefaults.darkStyle()Start from the defaults and override only what you need.
// Hide it
CuteCard(content = content, onKnown = { ... }, dismissButton = null)
// Or place your own control anywhere
val cardState = rememberCuteCardState()
CuteCard(content = content, state = cardState, dismissButton = null, onKnown = { ... }, onUnknown = { ... })
Button(onClick = { cardState.dismiss() }, enabled = cardState.canDismiss) { Text("I don't know") }config = CuteCardConfig(unflipButtonPosition = UnflipButtonPosition.CardTopEnd)Off by default. AboveCard or CardTopEnd shows a rewind button that flips the card back without dismissing it.
Upgrading? See the changelog for breaking changes and migration notes.
Everything else is in the full documentation:
| Topic | Section |
|---|---|
| All parameters, data classes and enums | API reference |
| Word placement — padding, reserved rows, centring | Word placement |
| Layered shadows | Shadows |
| Ghost stack size, offset, opacity | Ghost stack |
| Per-side word text style and pill colors | Per-side word text style, LanguagePillStyle |
| Custom audio, dismiss and unflip buttons | Audio playback, Dismiss button, Unflip button |
| Flip callbacks | Flip callbacks |
| Programmatic flip for tutorials | Programmatic flip |
| Animation timing and exit animations | Animation |
| Labels and localisation | Labels and localisation |
MIT — see LICENSE for details.
# Build all targets
./gradlew :cutecard:build
# Run common tests
./gradlew :cutecard:allTests
# Build Android artifact
./gradlew :cutecard:assemble
# Build iOS frameworks
./gradlew :cutecard:linkDebugFrameworkIosArm64 \
:cutecard:linkDebugFrameworkIosSimulatorArm64A language-learning flashcard component for Compose Multiplatform (Android & iOS).
CuteCard handles the full interaction lifecycle of a single flashcard - 3D flip animation, settled-lock delay, confirm/dismiss exit animations, and audio button visuals - so you can focus on your deck logic and data.
implementation("site.llinsoft:cutecard:0.5.1")var isPlaying by remember { mutableStateOf(false) }
CuteCard(
content = CuteCardContent(
word = "hierro",
translation = "iron",
phonetics = "[ˈje.ro]",
wordClass = "noun",
audioUrl = "https://example.com/hierro.mp3"
),
onKnown = { /* advance to next card */ },
onUnknown = { /* re-queue or move on */ },
isPlaying = isPlaying,
onAudioRequested = {
isPlaying = true
player.play(content.audioUrl) { isPlaying = false }
}
)onKnown and onUnknown are called after their exit animations complete — replace the card there.onAudioRequested. Pass null to hide the button.config = CuteCardConfig(frontSide = CardFrontSide.Translation)Word (default) shows the word first and reveals translation, phonetics, word class and audio. Translation reverses it.
style = CuteCardDefaults.style().copy(
cardBackgroundColor = Color(0xFFFFF8F0),
wordTextColor = Color(0xFF2C2C2C)
)
style = CuteCardDefaults.darkStyle()Start from the defaults and override only what you need.
// Hide it
CuteCard(content = content, onKnown = { ... }, dismissButton = null)
// Or place your own control anywhere
val cardState = rememberCuteCardState()
CuteCard(content = content, state = cardState, dismissButton = null, onKnown = { ... }, onUnknown = { ... })
Button(onClick = { cardState.dismiss() }, enabled = cardState.canDismiss) { Text("I don't know") }config = CuteCardConfig(unflipButtonPosition = UnflipButtonPosition.CardTopEnd)Off by default. AboveCard or CardTopEnd shows a rewind button that flips the card back without dismissing it.
Upgrading? See the changelog for breaking changes and migration notes.
Everything else is in the full documentation:
| Topic | Section |
|---|---|
| All parameters, data classes and enums | API reference |
| Word placement — padding, reserved rows, centring | Word placement |
| Layered shadows | Shadows |
| Ghost stack size, offset, opacity | Ghost stack |
| Per-side word text style and pill colors | Per-side word text style, LanguagePillStyle |
| Custom audio, dismiss and unflip buttons | Audio playback, Dismiss button, Unflip button |
| Flip callbacks | Flip callbacks |
| Programmatic flip for tutorials | Programmatic flip |
| Animation timing and exit animations | Animation |
| Labels and localisation | Labels and localisation |
MIT — see LICENSE for details.
# Build all targets
./gradlew :cutecard:build
# Run common tests
./gradlew :cutecard:allTests
# Build Android artifact
./gradlew :cutecard:assemble
# Build iOS frameworks
./gradlew :cutecard:linkDebugFrameworkIosArm64 \
:cutecard:linkDebugFrameworkIosSimulatorArm64