
Feature-rich skeleton and shimmer UI toolkit offering customizable shimmer types (linear, radial, pulse, wave), global pause/resume, staggered animations, Material 3 theming, accessibility and lifecycle-aware performance.
A feature-rich, Kotlin Multiplatform Skeleton & Shimmer library for Android, Desktop & iOS with advanced animation controls.
| Feed Screen | Profile Screen | Dashboard Screen |
|---|---|---|
![]() |
![]() |
![]() |
Add the dependency to your module's build.gradle.kts:
dependencies {
implementation("io.github.ebinjoy999:compose-skeleton-shimmer:2.0.1")
}Or using version catalog (libs.versions.toml):
[versions]
skeleton = "2.0.1"
[libraries]
skeleton-core = { group = "io.github.ebinjoy999", name = "compose-skeleton-shimmer", version.ref = "skeleton" }// Rectangular skeleton with rounded corners
SkeletonBox(
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
shape = RoundedCornerShape(12.dp)
)
// Circular skeleton for avatars
SkeletonCircle(size = 64.dp)
// Text line skeleton
SkeletonLine(
modifier = Modifier.fillMaxWidth(0.7f),
height = 16.dp
)// Card skeleton with image, title, and description
SkeletonCard(
imageHeight = 180.dp,
titleLines = 1,
showDescription = true,
descriptionLines = 2
)
// List item skeleton with avatar and text
SkeletonListItem(
leadingSize = 48.dp,
isLeadingCircle = true,
showSubtitle = true
)
// Profile skeleton with avatar, name, bio, and buttons
SkeletonProfile(
avatarSize = 80.dp,
showBio = true,
actionButtonCount = 2
)
// Grid tile skeleton
SkeletonTile(
modifier = Modifier.aspectRatio(1f),
showLabel = true
)Skeleton(
isLoading = viewModel.isLoading,
skeleton = { SkeletonCard() }
) {
ActualCard(data = viewModel.data)
}
// With crossfade transition
Skeleton(
isLoading = isLoading,
transition = SkeletonTransition.Crossfade,
transitionDurationMs = 400,
skeleton = { SkeletonProfile() }
) {
UserProfile(user = user)
}SkeletonIfNull(
data = user,
skeleton = { SkeletonProfile() }
) { user ->
UserProfile(user = user)
}LazyColumn {
skeletonItems(
isLoading = isLoading,
count = 5
) {
SkeletonListItem()
}
// Real items
items(actualItems) { item ->
ListItem(item)
}
}LazyVerticalGrid(columns = GridCells.Fixed(2)) {
skeletonGridItems(
isLoading = isLoading,
count = 6
) {
SkeletonTile(
modifier = Modifier.aspectRatio(1f)
)
}
}Box(
modifier = Modifier
.size(100.dp)
.background(Color.LightGray)
.shimmer()
)val shimmerState = rememberShimmerState(
durationMillis = 1200,
direction = ShimmerDirection.LeftToRight
)
SkeletonCard(shimmerState = shimmerState)
SkeletonListItem(shimmerState = shimmerState)// Horizontal shimmer
rememberShimmerState(direction = ShimmerDirection.LeftToRight)
rememberShimmerState(direction = ShimmerDirection.RightToLeft)
// Vertical shimmer
rememberShimmerState(direction = ShimmerDirection.TopToBottom)
rememberShimmerState(direction = ShimmerDirection.BottomToTop)Choose from four distinct shimmer animation styles:
// Linear shimmer (default) - Traditional left-to-right sweep
val linearConfig = ShimmerConfig(shimmerType = ShimmerType.Linear)
// Radial/Spotlight shimmer - Expanding circle effect
val radialConfig = ShimmerConfig(shimmerType = ShimmerType.Radial)
// Pulse/Breathing shimmer - Soft fade in/out effect
val pulseConfig = ShimmerConfig(shimmerType = ShimmerType.Pulse)
// Wave shimmer - Multiple wave ripples
val waveConfig = ShimmerConfig(shimmerType = ShimmerType.Wave, waveCount = 3)// Use ready-made configurations
ShimmerConfig.Default // Standard linear shimmer
ShimmerConfig.Subtle // Gentle, slow shimmer
ShimmerConfig.Prominent // Bold, fast shimmer
ShimmerConfig.Pulse // Breathing effect
ShimmerConfig.Spotlight // Radial spotlight
ShimmerConfig.MultiWave // Multiple wave ripples
ShimmerConfig.Accessible // Reduced motion safeval customConfig = ShimmerConfig(
shimmerType = ShimmerType.Linear,
direction = ShimmerDirection.LeftToRight,
durationMillis = 1200,
angle = 20f, // Tilt angle in degrees
shimmerWidth = 200f, // Width of shimmer band
intensity = 0.7f, // Highlight intensity (0.0-1.0)
dropOff = ShimmerDropOff.Soft, // Gradient falloff style
easing = ShimmerEasing.EaseInOut,
repeatMode = ShimmerRepeatMode.Restart,
staggerDelayMillis = 0, // Delay for list items
respectReducedMotion = true // Accessibility support
)
val shimmerState = rememberShimmerState(config = customConfig)
SkeletonCard(shimmerState = shimmerState)// Angled shimmer (e.g., diagonal sweep)
val angledConfig = ShimmerConfig(
angle = 30f // 30 degrees tilt
)Control how the shimmer gradient fades at edges:
ShimmerDropOff.Linear // Standard linear falloff
ShimmerDropOff.Soft // Gentle Gaussian-like falloff
ShimmerDropOff.Sharp // Quick, crisp edgesShimmerEasing.Linear // Constant speed
ShimmerEasing.EaseIn // Slow start, fast end
ShimmerEasing.EaseOut // Fast start, slow end
ShimmerEasing.EaseInOut // Slow start & end, fast middle
ShimmerEasing.Spring // Bouncy spring effectPause all shimmer animations for battery optimization:
val controller = rememberShimmerController()
// Pause all shimmers
controller.pause()
// Resume all shimmers
controller.resume()
// Create states that respect the controller
val shimmerState = rememberShimmerState(controller = controller)Automatically pause when app goes to background:
val shimmerState = rememberLifecycleAwareShimmerState(
config = ShimmerConfig.Default
)Stop shimmer after N iterations:
val shimmerState = rememberLimitedShimmerState(
maxIterations = 5,
onComplete = { /* Animation finished */ }
)val shimmerState = rememberShimmerStateWithCallbacks(
onAnimationStart = { /* Started */ },
onAnimationIteration = { iteration -> /* Iteration $iteration */ },
onAnimationEnd = { /* Stopped */ }
)Create cascading shimmer effects for list items:
// Individual stagger delay
val shimmerState1 = rememberShimmerState(
config = ShimmerConfig(staggerDelayMillis = 0)
)
val shimmerState2 = rememberShimmerState(
config = ShimmerConfig(staggerDelayMillis = 100)
)
val shimmerState3 = rememberShimmerState(
config = ShimmerConfig(staggerDelayMillis = 200)
)
// Or use the helper function
val states = rememberStaggeredShimmerStates(
count = 5,
staggerDelayMillis = 100,
config = ShimmerConfig.Default
)
states.forEachIndexed { index, state ->
SkeletonListItem(shimmerState = state)
}val shimmerState = rememberShimmerState(
baseColor = Color(0xFFE0E0E0),
highlightColor = Color(0xFFF5F5F5)
)Box(
modifier = Modifier
.size(100.dp)
.shimmerWithBrush { progress, size ->
Brush.linearGradient(
colors = listOf(Color.Red, Color.Blue, Color.Red),
start = Offset(-size.width + size.width * 2 * progress, 0f),
end = Offset(size.width * 2 * progress, size.height)
)
}
)The library automatically adapts to light/dark mode:
// Light mode defaults
baseColor = Color(0xFFE0E0E0)
highlightColor = Color(0xFFF5F5F5)
// Dark mode defaults
baseColor = Color(0xFF3A3A3A)
highlightColor = Color(0xFF4A4A4A)// Use Material 3 surface colors
val colors = materialSkeletonColors()
SkeletonTheme(colors = colors) {
SkeletonCard()
}val customColors = customSkeletonColors(
baseColor = MaterialTheme.colorScheme.surfaceVariant,
highlightColor = MaterialTheme.colorScheme.surface
)
SkeletonTheme(colors = customColors) {
// All skeleton components will use these colors
SkeletonCard()
SkeletonListItem()
}Automatically respect system accessibility settings:
// Config that disables animation when reduced motion is enabled
val config = ShimmerConfig(respectReducedMotion = true)
// Or use the accessible preset
val accessibleState = rememberShimmerStateWithPreset(ShimmerConfig.Accessible)
// Check system setting manually
val reduceMotion = rememberReduceMotionEnabled()
if (reduceMotion) {
// Show static placeholder
}Add screen reader support:
SkeletonBox(
modifier = Modifier
.skeletonSemantics(
type = SkeletonSemanticType.Image,
description = "Loading profile picture"
)
)// Get an accessible shimmer config based on system settings
val accessibleConfig = rememberAccessibleShimmerConfig()compose-skeleton/
βββ skeleton-core/ # Library module
β βββ src/main/java/com/ebin/skeleton/
β βββ shimmer/
β β βββ ShimmerConfig.kt # Advanced configuration
β β βββ ShimmerState.kt # State management
β β βββ ShimmerEffects.kt # Lifecycle & callbacks
β βββ skeleton/
β β βββ SkeletonPrimitives.kt # Basic shapes
β β βββ SkeletonComponents.kt # Pre-built components
β β βββ SkeletonController.kt # Visibility controllers
β β βββ LazySkeletonItems.kt # List/Grid extensions
β β βββ SkeletonAccessibility.kt # Accessibility utils
β β βββ SkeletonSemantics.kt # Screen reader support
β βββ modifier/
β β βββ ShimmerModifier.kt # Modifier extensions
β βββ theme/
β βββ SkeletonColors.kt # Color definitions
β βββ SkeletonTheme.kt # Theme provider
βββ sample-app/ # Demo application
β βββ src/main/java/com/ebin/skeleton/sample/
β βββ screens/
β β βββ FeedScreen.kt # Staggered shimmer demo
β β βββ ProfileScreen.kt # Pulse shimmer demo
β β βββ DashboardScreen.kt # Radial shimmer demo
β βββ components/
β β βββ RealComponents.kt # Actual content
β βββ MainActivity.kt # Entry point
βββ README.md
Shared Shimmer State - Use a single ShimmerState for multiple skeletons to synchronize animations and reduce computation:
val shimmerState = rememberShimmerState()
repeat(5) {
SkeletonListItem(shimmerState = shimmerState)
}Avoid Unnecessary Recompositions - The library is designed to minimize recompositions. Skeleton components only recompose when their parameters change.
GPU-Friendly Animations - Shimmer uses Brush.linearGradient which is hardware-accelerated and doesn't create bitmaps.
Efficient List Rendering - Use skeletonItems with proper keys for optimal RecyclerView-style performance:
skeletonItems(
isLoading = isLoading,
count = 10,
key = { "skeleton_$it" } // Stable keys
) {
SkeletonListItem()
}Skeleton screens (also known as "content placeholders") improve perceived performance and user experience:
| Metric | Spinner | Skeleton |
|---|---|---|
| Perceived Load Time | Feels longer | Feels shorter |
| User Anxiety | Higher | Lower |
| Content Preview | None | Layout hint |
| Professional Feel | Generic | Polished |
Research shows that skeleton screens can reduce perceived wait time by up to 30% compared to traditional spinners.
| Property | Type | Default | Description |
|---|---|---|---|
shimmerType |
ShimmerType |
Linear |
Animation style (Linear, Radial, Pulse, Wave) |
direction |
ShimmerDirection |
LeftToRight |
Animation direction |
durationMillis |
Int |
1200 |
Animation cycle duration |
angle |
Float |
0f |
Tilt angle in degrees |
shimmerWidth |
Float |
200f |
Width of shimmer band |
intensity |
Float |
0.6f |
Highlight brightness (0.0-1.0) |
dropOff |
ShimmerDropOff |
Linear |
Gradient falloff style |
easing |
ShimmerEasing |
Linear |
Animation easing function |
repeatMode |
ShimmerRepeatMode |
Restart |
How animation repeats |
waveCount |
Int |
2 |
Number of waves (Wave type only) |
staggerDelayMillis |
Int |
0 |
Delay for cascading effects |
respectReducedMotion |
Boolean |
true |
Honor accessibility settings |
| Component | Description |
|---|---|
SkeletonBox |
Rectangular placeholder |
SkeletonCircle |
Circular placeholder |
SkeletonLine |
Text line placeholder |
SkeletonParagraph |
Multiple text lines |
SkeletonCard |
Card with image & text |
SkeletonListItem |
List item with avatar |
SkeletonProfile |
Profile header |
SkeletonTile |
Grid tile |
| Component | Description |
|---|---|
Skeleton |
Show skeleton or content |
SkeletonIfNull |
Skeleton until data loads |
SkeletonIfEmpty |
Skeleton until list has items |
ShimmerController |
Global pause/resume control |
| Function | Description |
|---|---|
rememberShimmerState() |
Basic shimmer state |
rememberShimmerStateWithPreset() |
State with config preset |
rememberShimmerController() |
Global shimmer controller |
rememberLifecycleAwareShimmerState() |
Auto-pause on background |
rememberLimitedShimmerState() |
Stop after N iterations |
rememberStaggeredShimmerStates() |
Cascading list animations |
| Modifier | Description |
|---|---|
Modifier.shimmer() |
Basic shimmer effect |
Modifier.shimmer(state) |
Shimmer with custom state |
Modifier.shimmerWithBrush() |
Custom shimmer brush |
Modifier.skeletonSemantics() |
Accessibility semantics |
Contributions are welcome! Please feel free to submit a Pull Request.
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)Copyright 2026 Ebin
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
A feature-rich, Kotlin Multiplatform Skeleton & Shimmer library for Android, Desktop & iOS with advanced animation controls.
| Feed Screen | Profile Screen | Dashboard Screen |
|---|---|---|
![]() |
![]() |
![]() |
Add the dependency to your module's build.gradle.kts:
dependencies {
implementation("io.github.ebinjoy999:compose-skeleton-shimmer:2.0.1")
}Or using version catalog (libs.versions.toml):
[versions]
skeleton = "2.0.1"
[libraries]
skeleton-core = { group = "io.github.ebinjoy999", name = "compose-skeleton-shimmer", version.ref = "skeleton" }// Rectangular skeleton with rounded corners
SkeletonBox(
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
shape = RoundedCornerShape(12.dp)
)
// Circular skeleton for avatars
SkeletonCircle(size = 64.dp)
// Text line skeleton
SkeletonLine(
modifier = Modifier.fillMaxWidth(0.7f),
height = 16.dp
)// Card skeleton with image, title, and description
SkeletonCard(
imageHeight = 180.dp,
titleLines = 1,
showDescription = true,
descriptionLines = 2
)
// List item skeleton with avatar and text
SkeletonListItem(
leadingSize = 48.dp,
isLeadingCircle = true,
showSubtitle = true
)
// Profile skeleton with avatar, name, bio, and buttons
SkeletonProfile(
avatarSize = 80.dp,
showBio = true,
actionButtonCount = 2
)
// Grid tile skeleton
SkeletonTile(
modifier = Modifier.aspectRatio(1f),
showLabel = true
)Skeleton(
isLoading = viewModel.isLoading,
skeleton = { SkeletonCard() }
) {
ActualCard(data = viewModel.data)
}
// With crossfade transition
Skeleton(
isLoading = isLoading,
transition = SkeletonTransition.Crossfade,
transitionDurationMs = 400,
skeleton = { SkeletonProfile() }
) {
UserProfile(user = user)
}SkeletonIfNull(
data = user,
skeleton = { SkeletonProfile() }
) { user ->
UserProfile(user = user)
}LazyColumn {
skeletonItems(
isLoading = isLoading,
count = 5
) {
SkeletonListItem()
}
// Real items
items(actualItems) { item ->
ListItem(item)
}
}LazyVerticalGrid(columns = GridCells.Fixed(2)) {
skeletonGridItems(
isLoading = isLoading,
count = 6
) {
SkeletonTile(
modifier = Modifier.aspectRatio(1f)
)
}
}Box(
modifier = Modifier
.size(100.dp)
.background(Color.LightGray)
.shimmer()
)val shimmerState = rememberShimmerState(
durationMillis = 1200,
direction = ShimmerDirection.LeftToRight
)
SkeletonCard(shimmerState = shimmerState)
SkeletonListItem(shimmerState = shimmerState)// Horizontal shimmer
rememberShimmerState(direction = ShimmerDirection.LeftToRight)
rememberShimmerState(direction = ShimmerDirection.RightToLeft)
// Vertical shimmer
rememberShimmerState(direction = ShimmerDirection.TopToBottom)
rememberShimmerState(direction = ShimmerDirection.BottomToTop)Choose from four distinct shimmer animation styles:
// Linear shimmer (default) - Traditional left-to-right sweep
val linearConfig = ShimmerConfig(shimmerType = ShimmerType.Linear)
// Radial/Spotlight shimmer - Expanding circle effect
val radialConfig = ShimmerConfig(shimmerType = ShimmerType.Radial)
// Pulse/Breathing shimmer - Soft fade in/out effect
val pulseConfig = ShimmerConfig(shimmerType = ShimmerType.Pulse)
// Wave shimmer - Multiple wave ripples
val waveConfig = ShimmerConfig(shimmerType = ShimmerType.Wave, waveCount = 3)// Use ready-made configurations
ShimmerConfig.Default // Standard linear shimmer
ShimmerConfig.Subtle // Gentle, slow shimmer
ShimmerConfig.Prominent // Bold, fast shimmer
ShimmerConfig.Pulse // Breathing effect
ShimmerConfig.Spotlight // Radial spotlight
ShimmerConfig.MultiWave // Multiple wave ripples
ShimmerConfig.Accessible // Reduced motion safeval customConfig = ShimmerConfig(
shimmerType = ShimmerType.Linear,
direction = ShimmerDirection.LeftToRight,
durationMillis = 1200,
angle = 20f, // Tilt angle in degrees
shimmerWidth = 200f, // Width of shimmer band
intensity = 0.7f, // Highlight intensity (0.0-1.0)
dropOff = ShimmerDropOff.Soft, // Gradient falloff style
easing = ShimmerEasing.EaseInOut,
repeatMode = ShimmerRepeatMode.Restart,
staggerDelayMillis = 0, // Delay for list items
respectReducedMotion = true // Accessibility support
)
val shimmerState = rememberShimmerState(config = customConfig)
SkeletonCard(shimmerState = shimmerState)// Angled shimmer (e.g., diagonal sweep)
val angledConfig = ShimmerConfig(
angle = 30f // 30 degrees tilt
)Control how the shimmer gradient fades at edges:
ShimmerDropOff.Linear // Standard linear falloff
ShimmerDropOff.Soft // Gentle Gaussian-like falloff
ShimmerDropOff.Sharp // Quick, crisp edgesShimmerEasing.Linear // Constant speed
ShimmerEasing.EaseIn // Slow start, fast end
ShimmerEasing.EaseOut // Fast start, slow end
ShimmerEasing.EaseInOut // Slow start & end, fast middle
ShimmerEasing.Spring // Bouncy spring effectPause all shimmer animations for battery optimization:
val controller = rememberShimmerController()
// Pause all shimmers
controller.pause()
// Resume all shimmers
controller.resume()
// Create states that respect the controller
val shimmerState = rememberShimmerState(controller = controller)Automatically pause when app goes to background:
val shimmerState = rememberLifecycleAwareShimmerState(
config = ShimmerConfig.Default
)Stop shimmer after N iterations:
val shimmerState = rememberLimitedShimmerState(
maxIterations = 5,
onComplete = { /* Animation finished */ }
)val shimmerState = rememberShimmerStateWithCallbacks(
onAnimationStart = { /* Started */ },
onAnimationIteration = { iteration -> /* Iteration $iteration */ },
onAnimationEnd = { /* Stopped */ }
)Create cascading shimmer effects for list items:
// Individual stagger delay
val shimmerState1 = rememberShimmerState(
config = ShimmerConfig(staggerDelayMillis = 0)
)
val shimmerState2 = rememberShimmerState(
config = ShimmerConfig(staggerDelayMillis = 100)
)
val shimmerState3 = rememberShimmerState(
config = ShimmerConfig(staggerDelayMillis = 200)
)
// Or use the helper function
val states = rememberStaggeredShimmerStates(
count = 5,
staggerDelayMillis = 100,
config = ShimmerConfig.Default
)
states.forEachIndexed { index, state ->
SkeletonListItem(shimmerState = state)
}val shimmerState = rememberShimmerState(
baseColor = Color(0xFFE0E0E0),
highlightColor = Color(0xFFF5F5F5)
)Box(
modifier = Modifier
.size(100.dp)
.shimmerWithBrush { progress, size ->
Brush.linearGradient(
colors = listOf(Color.Red, Color.Blue, Color.Red),
start = Offset(-size.width + size.width * 2 * progress, 0f),
end = Offset(size.width * 2 * progress, size.height)
)
}
)The library automatically adapts to light/dark mode:
// Light mode defaults
baseColor = Color(0xFFE0E0E0)
highlightColor = Color(0xFFF5F5F5)
// Dark mode defaults
baseColor = Color(0xFF3A3A3A)
highlightColor = Color(0xFF4A4A4A)// Use Material 3 surface colors
val colors = materialSkeletonColors()
SkeletonTheme(colors = colors) {
SkeletonCard()
}val customColors = customSkeletonColors(
baseColor = MaterialTheme.colorScheme.surfaceVariant,
highlightColor = MaterialTheme.colorScheme.surface
)
SkeletonTheme(colors = customColors) {
// All skeleton components will use these colors
SkeletonCard()
SkeletonListItem()
}Automatically respect system accessibility settings:
// Config that disables animation when reduced motion is enabled
val config = ShimmerConfig(respectReducedMotion = true)
// Or use the accessible preset
val accessibleState = rememberShimmerStateWithPreset(ShimmerConfig.Accessible)
// Check system setting manually
val reduceMotion = rememberReduceMotionEnabled()
if (reduceMotion) {
// Show static placeholder
}Add screen reader support:
SkeletonBox(
modifier = Modifier
.skeletonSemantics(
type = SkeletonSemanticType.Image,
description = "Loading profile picture"
)
)// Get an accessible shimmer config based on system settings
val accessibleConfig = rememberAccessibleShimmerConfig()compose-skeleton/
βββ skeleton-core/ # Library module
β βββ src/main/java/com/ebin/skeleton/
β βββ shimmer/
β β βββ ShimmerConfig.kt # Advanced configuration
β β βββ ShimmerState.kt # State management
β β βββ ShimmerEffects.kt # Lifecycle & callbacks
β βββ skeleton/
β β βββ SkeletonPrimitives.kt # Basic shapes
β β βββ SkeletonComponents.kt # Pre-built components
β β βββ SkeletonController.kt # Visibility controllers
β β βββ LazySkeletonItems.kt # List/Grid extensions
β β βββ SkeletonAccessibility.kt # Accessibility utils
β β βββ SkeletonSemantics.kt # Screen reader support
β βββ modifier/
β β βββ ShimmerModifier.kt # Modifier extensions
β βββ theme/
β βββ SkeletonColors.kt # Color definitions
β βββ SkeletonTheme.kt # Theme provider
βββ sample-app/ # Demo application
β βββ src/main/java/com/ebin/skeleton/sample/
β βββ screens/
β β βββ FeedScreen.kt # Staggered shimmer demo
β β βββ ProfileScreen.kt # Pulse shimmer demo
β β βββ DashboardScreen.kt # Radial shimmer demo
β βββ components/
β β βββ RealComponents.kt # Actual content
β βββ MainActivity.kt # Entry point
βββ README.md
Shared Shimmer State - Use a single ShimmerState for multiple skeletons to synchronize animations and reduce computation:
val shimmerState = rememberShimmerState()
repeat(5) {
SkeletonListItem(shimmerState = shimmerState)
}Avoid Unnecessary Recompositions - The library is designed to minimize recompositions. Skeleton components only recompose when their parameters change.
GPU-Friendly Animations - Shimmer uses Brush.linearGradient which is hardware-accelerated and doesn't create bitmaps.
Efficient List Rendering - Use skeletonItems with proper keys for optimal RecyclerView-style performance:
skeletonItems(
isLoading = isLoading,
count = 10,
key = { "skeleton_$it" } // Stable keys
) {
SkeletonListItem()
}Skeleton screens (also known as "content placeholders") improve perceived performance and user experience:
| Metric | Spinner | Skeleton |
|---|---|---|
| Perceived Load Time | Feels longer | Feels shorter |
| User Anxiety | Higher | Lower |
| Content Preview | None | Layout hint |
| Professional Feel | Generic | Polished |
Research shows that skeleton screens can reduce perceived wait time by up to 30% compared to traditional spinners.
| Property | Type | Default | Description |
|---|---|---|---|
shimmerType |
ShimmerType |
Linear |
Animation style (Linear, Radial, Pulse, Wave) |
direction |
ShimmerDirection |
LeftToRight |
Animation direction |
durationMillis |
Int |
1200 |
Animation cycle duration |
angle |
Float |
0f |
Tilt angle in degrees |
shimmerWidth |
Float |
200f |
Width of shimmer band |
intensity |
Float |
0.6f |
Highlight brightness (0.0-1.0) |
dropOff |
ShimmerDropOff |
Linear |
Gradient falloff style |
easing |
ShimmerEasing |
Linear |
Animation easing function |
repeatMode |
ShimmerRepeatMode |
Restart |
How animation repeats |
waveCount |
Int |
2 |
Number of waves (Wave type only) |
staggerDelayMillis |
Int |
0 |
Delay for cascading effects |
respectReducedMotion |
Boolean |
true |
Honor accessibility settings |
| Component | Description |
|---|---|
SkeletonBox |
Rectangular placeholder |
SkeletonCircle |
Circular placeholder |
SkeletonLine |
Text line placeholder |
SkeletonParagraph |
Multiple text lines |
SkeletonCard |
Card with image & text |
SkeletonListItem |
List item with avatar |
SkeletonProfile |
Profile header |
SkeletonTile |
Grid tile |
| Component | Description |
|---|---|
Skeleton |
Show skeleton or content |
SkeletonIfNull |
Skeleton until data loads |
SkeletonIfEmpty |
Skeleton until list has items |
ShimmerController |
Global pause/resume control |
| Function | Description |
|---|---|
rememberShimmerState() |
Basic shimmer state |
rememberShimmerStateWithPreset() |
State with config preset |
rememberShimmerController() |
Global shimmer controller |
rememberLifecycleAwareShimmerState() |
Auto-pause on background |
rememberLimitedShimmerState() |
Stop after N iterations |
rememberStaggeredShimmerStates() |
Cascading list animations |
| Modifier | Description |
|---|---|
Modifier.shimmer() |
Basic shimmer effect |
Modifier.shimmer(state) |
Shimmer with custom state |
Modifier.shimmerWithBrush() |
Custom shimmer brush |
Modifier.skeletonSemantics() |
Accessibility semantics |
Contributions are welcome! Please feel free to submit a Pull Request.
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)Copyright 2026 Ebin
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.