Hand-drawn, animated illustrations & empty states for Jetpack Compose.
Sketchy is a Kotlin/Compose library of sketch-style, line-art artwork β the
kind of warm, human illustration you'd want for onboarding flows and empty
states β drawn entirely on Canvas, animated out of the box, and fully
themeable to match your app.
sketchy_new.mp4
Most illustration libraries ship static SVGs or Lottie files β dependencies outside Compose's own rendering pipeline, hard to restyle, and rarely animated in a way that feels alive. Sketchy takes a different approach:
- Pure Compose, zero image assets. Every illustration is vector line-art
drawn with
DrawScope, so it's crisp at any size and ships as pure Kotlin β no drawables, no Lottie JSON, no binary bloat. - Animated by default. Each scene has its own small, purposeful motion β
a sweeping stopwatch hand, a ringing bell, a heartbeat pulse β not just a
generic fade-in. Turn it off with a single
animate = falsewhen you want a static frame instead. - Hand-drawn by default, painted on request. Every scene renders as a
pure outline sketch β ink lines on a transparent canvas, nothing filled in,
lifted only by small accent marks. Pass
colorful = trueand the same scene is painted in full: shaded surfaces, contact shadows, highlights. No second set of artwork, and never a background of its own either way. - Genuinely reskinnable. Both catalogs take color, size, and copy as
plain parameters (
SketchyColors,Dp,String,TextStyle) β no XML theming, no design-system lock-in. It doesn't even depend on Material.
| Catalog | Count | Entry point |
|---|---|---|
| Featured illustrations | 5 elaborate full scenes | SketchyIllustration(modifier, sketch, animate, colorful, colors) |
| Onboarding illustrations | 15, across 6 categories | SketchyIllustration(modifier, sketch, animate, colorful, colors) |
| Empty states | 20, across 4 categories | SketchyEmptyState(state, modifier, animate, colorful, colors, illustrationSize, title, subtitle, titleStyle, subtitleStyle, spacing) |
Every one of the 40 scenes draws two ways from the same code β a colourless
hand-drawn outline by default, or fully painted with colorful = true.
A :app module ships alongside the library as a live, searchable catalog of
every illustration and empty state β the fastest way to browse what's
available and copy the exact usage snippet for whatever you pick.
Sketchy never ships two sets of artwork. Each scene is written once, and the
colorful flag decides how the palette hands its colours back to the drawing
code:
colorful = false (default) |
colorful = true |
|
|---|---|---|
| Look | Pure line-art β ink outlines, nothing filled in | Gradient-shaded surfaces, contact shadows, highlights, under the same outlines |
| Palette slots used | ink, inkSoft, inkFaint + the four accents |
All of the above plus the light and material slots |
| Colour | Only small accent marks β a sparkle, a glint, one highlighted bar | Full colour throughout |
| Background | Transparent | Transparent |
| Best for | Minimal / monochrome UIs, dark mode, matching a single brand ink | Onboarding heroes, marketing screens, playful apps |
Neither mode ever paints a background of its own, so a scene drops onto whatever surface your screen already has.
How it works: when a scene is outlined, every surface, light and material slot
of the palette reads back as Color.Transparent, so the fill simply isn't
painted and what survives is the ink outline of the same shape. That's why
colorful is one boolean and not a second catalog.
Sketchy is published on JitPack.
Add the JitPack repository:
// settings.gradle
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}Then add the dependency:
// app/build.gradle
dependencies {
implementation 'com.github.Ali0092:Sketchy:1.0.2'
}import com.sketchy.library.illustrations.Sketch
import com.sketchy.library.illustrations.SketchyIllustration
SketchyIllustration(
sketch = Sketch.PlanTasks,
modifier = Modifier.size(280.dp)
)Every Sketch scales to whatever size you give it and animates on a loop by
default. Pass animate = false to freeze it on its resting frame β handy for
lower-power devices or a still hero image.
// Colourless β the default. Ink outlines, a few accent marks, nothing filled in.
SketchyIllustration(
sketch = Sketch.MorningCoffee,
modifier = Modifier.size(280.dp),
)
// Colourful β the same scene, painted: shaded surfaces, shadows, highlights.
SketchyIllustration(
sketch = Sketch.MorningCoffee,
modifier = Modifier.size(280.dp),
colorful = true,
)colorful works on every illustration and every empty state, and it's an
ordinary parameter β drive it from a setting, from isSystemInDarkTheme(), or
from a single app-wide constant:
SketchyEmptyState(
state = EmptyState.EmptyCart,
colorful = !isSystemInDarkTheme(),
)The five Featured scenes are the elaborate ones β a whole scene rather than a single motif, one source file each, built on the shading primitives the painted mode uses. They're the ones worth giving a full-bleed hero slot:
SketchyIllustration(
sketch = Sketch.ReadingNook, // or MorningCoffee, HomeWorkspace, GroceryRun, RainyWindow
modifier = Modifier.fillMaxWidth().aspectRatio(1f),
colorful = true,
)They take exactly the same parameters as every other Sketch β animate,
colorful and colors all behave identically β and they render colourless
just as happily if that's your look. Because they carry the most detail, they
also gain the most from colorful = true, so they're the best place to start
if you're deciding between the two modes.
colors is a plain SketchyColors parameter β override any subset of it and
every stroke, fill, and sparkle in the scene repaints to match, no XML themes
or design-system lock-in required.
A colourless scene only ever draws from the first block of the palette β ink,
inkSoft, inkFaint and the four accents (accent, accentGreen,
accentBlue, accentRed). The light and material slots below them are what
the colourful version fills with, so reskinning a colourless app means changing
the ink and the accents and nothing else:
import com.sketchy.library.SketchyColors
import com.sketchy.library.illustrations.Sketch
import com.sketchy.library.illustrations.SketchyIllustration
SketchyIllustration(
sketch = Sketch.BuildBetterHabits,
modifier = Modifier.size(280.dp),
colors = SketchyColors(
ink = MaterialTheme.colorScheme.onSurface,
accent = MaterialTheme.colorScheme.primary,
accentBlue = MaterialTheme.colorScheme.secondary,
)
)If you use colorful = true, the rest of the palette is worth a look too β
it's grouped so you can retint a whole painted scene a few slots at a time:
| Block | Slots | What it paints |
|---|---|---|
| Ink | ink, inkSoft, inkFaint |
Every outline, in both modes |
| Accents | accent, accentGreen, accentBlue, accentRed |
Small coloured marks, in both modes |
| Light & atmosphere | paper, sun, sunDeep, glow, sky, skyDeep, shade, shadeSoft |
Key light, bloom, shadows β colourful only |
| Materials | wood, woodDark, leaf, leafDark, terracotta, clay, fabric, fabricDark, metal, metalDark, skin, skinDark, hair, coffee |
The surfaces themselves β colourful only |
Overriding a material slot while a scene is colourless is harmless β it's simply never read.
import com.sketchy.library.SketchyColors
import com.sketchy.library.emptystates.EmptyState
import com.sketchy.library.emptystates.SketchyEmptyState
SketchyEmptyState(
state = EmptyState.NoInternet,
colors = SketchyColors(
ink = MaterialTheme.colorScheme.onSurface,
accent = MaterialTheme.colorScheme.primary,
),
illustrationSize = 180.dp,
title = "You're offline",
subtitle = "We'll sync everything as soon as you're back online.",
titleStyle = MaterialTheme.typography.titleMedium,
subtitleStyle = MaterialTheme.typography.bodyMedium,
)Every knob β color, size, copy, typography β is an ordinary parameter with a
sensible default. Omit title/subtitle entirely (pass null) for an
icon-only illustration. SketchyColors is the same type for both catalogs, so
one palette restyles your entire onboarding flow and empty-state set at once.
Every entry below draws both ways β colourless by default, painted with
colorful = true.
Featured illustrations (5)
Sketch |
Scene |
|---|---|
Sketch.MorningCoffee |
A Slow Morning Coffee |
Sketch.HomeWorkspace |
Your Workspace at Home |
Sketch.GroceryRun |
The Weekly Grocery Run |
Sketch.ReadingNook |
A Quiet Reading Corner |
Sketch.RainyWindow |
Rainy Day Indoors |
Onboarding illustrations (15)
| Category | Illustrations |
|---|---|
| Productivity | Plan Every Task Β· Find Your Focus Β· Never Miss a Meeting Β· Capture Every Thought Β· Build Better Habits |
| Finance | Track Every Expense Β· Watch Your Savings Grow |
| Fitness | Train Anywhere, Anytime Β· See Your Progress |
| Food Delivery | Order Your Favorites Β· Fast, Fresh Delivery |
| Travel | Plan Your Perfect Trip Β· Explore The World |
| Music | Your Soundtrack, Anywhere Β· Discover New Sounds |
Empty states (20)
| Category | Empty states |
|---|---|
| Connectivity & Errors | No Internet Β· Server Error Β· Sync Failed Β· Under Maintenance Β· Location Not Found |
| Content & Search | No Results Β· No Data Β· No Comments Β· No Messages Β· Page Not Found (404) |
| Saved & Commerce | Empty Cart Β· Empty Wishlist Β· No Favorites Β· No Bookmarks Β· No Downloads |
| Everyday & Productivity | Empty Inbox Β· No Notifications Β· Empty Calendar Β· No Photos Β· All Done |
Sketchy/
βββ library/ # The published artifact β pure Compose, no app dependencies
β βββ src/main/java/com/sketchy/library/
β βββ SketchyColors.kt # Shared reskinnable palette (both catalogs)
β βββ SketchyStyle.kt # Outlined vs. painted β how one scene renders both ways
β βββ illustrations/
β β βββ SketchyIllustrations.kt # Sketch enum, SketchyIllustration composable
β β βββ Featured*.kt # β¦the elaborate full scenes, one file each
β β βββ Onboarding*.kt # β¦grouped by category, 2-5 scenes each
β βββ emptystates/
β β βββ EmptyState.kt # EmptyState enum, SketchyEmptyState composable
β β βββ EmptyStates*.kt # β¦grouped by category, 5 scenes each
β βββ utils/
β βββ Extensions.kt # DrawScope drawing extensions (stroke, sketchLine, β¦)
β βββ Painting.kt # Fills, shading, brushes, limbs β the painted half
β βββ Utils.kt # Ink/accent color constants, wave(), DesignSize
βββ app/ # Demo app β searchable, categorized gallery of everything above
New illustrations are very welcome. A new scene is just an internal
DrawScope function plus one enum entry β no boilerplate beyond that:
- Pick a category (or propose a new one) and add an entry to the
SketchorEmptyStateenum with its display copy and category. - Write the scene as
internal fun DrawScope.drawYourScene(t: Float, colors: SketchyStyle)in the matchingillustrations/Onboarding*.ktoremptystates/EmptyStates*.ktfile, reusing the shared extensions fromutils/Extensions.kt(stroke,sketchLine,sketchCircle,twinkle,wave,groundHint/groundLine) for a consistent hand-drawn look β and always paint fromcolors.*(never the rawInk/Accentconstants inutils/Utils.kt) so the scene stays themeable. - Wire it into the
whendispatcher and open a PR.
Please keep new scenes framework-agnostic (no Material/Material3 imports
inside :library) so the empty-state API stays usable in any design system.
Sketchy is available under the MIT License.
β€οΈ Created with love by Muhammad Ali
"Empty screens deserve better than a spinner."