AestheticDialogs: a Jetpack Compose dialog design system
📱 An Android Library built with Jetpack Compose for 💫 fluid, 😍 beautiful, 🎨 custom Dialogs.
At a glance
- What is it?
- AestheticDialogs is an Android library for Compose that replaces the usual pile of slightly different dialogs with a set of component families sharing one frame, one theme and one set of accessibility rules. Version 3 gives every component a callback per interaction, so the library never decides on its own that a dialog should close.
- Who is it for?
- Adopt AestheticDialogs if you build Android apps with Jetpack Compose and keep rewriting confirmation, alert and banner screens that behave slightly differently each time, and you want the accessibility and adaptive width behaviour decided for you. Skip it if you need Material 3 dialogs with a public Material signature, since the library keeps Material as an implementation detail, or if your minSdk is below 24.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 72 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A design system, not a decorated AlertDialog
AestheticDialogs describes itself as a Jetpack Compose dialog design system built on a three-layer UI architecture of Component, Variant, Primitive and Tokens. That layering is the design statement, and the history explains why it was written.
Version 1.x was a set of eight decorated AlertDialogs. Version 2.x was a rewrite that replaced them with seven component families, six dialogs and a set of banners, all sharing one frame, one theme and one set of accessibility guarantees.
Version 3 changes two things. Every component exposes one callback per interaction, onConfirm, onCancel and onDismiss, instead of a single onSignal taking a sealed type. And the banners gained seven behaviours their previous model could not express: a trailing action, a docked status strip, swipe to dismiss, a queue policy, background progress, a presence dot and a visible countdown. The README notes that nothing which already existed changed how it looks.
For a team, that is the difference between a component library and a design system. The visual identity is the easy part to copy. The hard part is that every screen ends up with a slightly different idea of what a cancel button does, and this project is a bet that fixing the behaviour is worth more than the styling.
The six promises the library makes instead of a nicer button
The section explaining why the project exists is the most useful part of the README, because it states the guarantees in terms you can test.
The first is adaptive: the width comes from the space available, not from a device check. The second is accessibility, with pane titles, headings, live regions, 48dp targets and support for 200% font. The third is stateless, and the wording is precise about what that means: the library never decides that your dialog should close. The fourth is theming, one wrapper, light and dark, brandable by copying a value. The fifth is quiet under reduced motion, where transitions become cuts when the user asks for that. The sixth is light, with no icon library, no drawable resources, and Material 3 never appearing in a public signature, so you never have to write Material to use the library.
The stateless guarantee is the one that changes how code is written, and it comes back in the quick start. It also explains the library's tone: it is opinionated about behaviour and deliberately unhelpful about the parts where apps legitimately differ.
Adding it from Maven Central, three ways
The dependency is published on Maven Central as com.gabrielthecode:aestheticdialogs, and the README gives three ways to wire it up.
The first is a plain declaration, with the repository block shown for reference since Maven Central is usually already present:
// build.gradle.kts
dependencies {
implementation("com.gabrielthecode:aestheticdialogs:3.0.0")
}The second is a version catalog entry:
# gradle/libs.versions.toml
[versions]
aestheticdialogs = "3.0.0"
[libraries]
aestheticdialogs = { module = "com.gabrielthecode:aestheticdialogs", version.ref = "aestheticdialogs" }with the dependency then referenced as implementation(libs.aestheticdialogs).
The requirements are short. The library needs minSdk 24 and a Compose-enabled module, and Material 3 is not forced on you, since the library keeps it as an implementation detail. The badges in the README give the rest of the shape: API 24 and above, Kotlin 2.4, Jetpack Compose BOM 2026.06, and the Apache 2.0 licence.
The minSdk floor is the one constraint that can end the discussion on an older project, since a library cannot lower the minimum your app supports.
The theme wrapper is four CompositionLocals and nothing else
The quick start begins with a wrapper inside your own theme:
setContent {
MyAppTheme { // yours: untouched
AestheticDialogsTheme { // ours: four CompositionLocals, nothing else
AppNavHost()
}
}
}The comments do the explaining. Your theme is untouched, and the library's wrapper is four CompositionLocals and nothing else.
Wrapping is also optional. The README says components resolve light or dark from the system setting when no theme is present, and that wrapping is what lets you brand them. So the wrapper is not a mechanism for making the dialogs look right, since they already do, it is the hook for making them look like your product.
That is a small and pleasant design. A dialog library that forces its own typography or colour scheme onto the host app is a common complaint about UI kits, and a wrapper whose entire public surface is four CompositionLocals keeps the coupling in one place.
There is no show and there is no dismiss
The state model is the part of the API that will change how you write your screens, and the README states it without a show method or a dismiss method in sight. A dialog is on screen while it is in the composition, and every callback, including onDismiss, is a request you decide what to do with.
A confirmation dialog therefore looks like this:
if (uiState.showDeleteConfirmation) {
AestheticConfirmationDialog(
uiModel = ConfirmationDialogUiModel.Destructive(
title = "Delete this album?",
message = "The 24 photos inside it will be deleted too.",
confirmLabel = "Delete album",
cancelLabel = "Keep it",
isConfirming = uiState.isDeleting,
),
onConfirm = { viewModel.deleteAlbum() },
onCancel = { viewModel.dismissDialog() },
onDismiss = { viewModel.dismissDialog() },
)
}The presence of the component in the composition is the visibility. The callbacks are ordinary lambdas that call into your view model, and in the example the cancel and dismiss paths both end in the same dismissDialog call, which is a decision your code makes rather than one the library imposes.
The stateless promise is what makes this work. Since the library never closes itself, a dialog cannot disappear behind your back because a tap landed outside it, and the only paths out are the ones you wrote.
Destructive is guaranteed and isConfirming locks the whole dialog
Two behaviours in the confirmation component are worth describing precisely, because they are the cases the library was written for.
The first is that the destructive treatment is guaranteed rather than configured, so a delete confirmation cannot accidentally ship with a neutral button. The two variants are Default for ordinary questions and Destructive for things that cannot be undone, and the destructive look is not a parameter you can forget to set.
The second is isConfirming. Setting it to true puts a spinner on the confirm button and locks the rest of the dialog, including cancel, so a half-finished operation cannot be abandoned mid-flight. That is the concrete answer to the failure the README describes in its opening, an app that lets you cancel a half-finished delete.
The alert component follows a related pattern, with a tone on the model and one or two actions:
AestheticAlertDialog(
uiModel = AlertDialogUiModel.Default(
title = "You are offline",
message = "We will sync your changes as soon as you reconnect.",
tone = DialogTone.Warning,
primaryAction = DialogAction("Retry"),
secondaryAction = DialogAction("Dismiss", DialogActionEmphasis.Text),
),
onPrimaryAction = { viewModel.retry() },
onDismiss = { viewModel.dismissDialog() },The tone and the action emphasis are typed values on the model, so the emphasis of a secondary action is visible in the call site rather than implied by a string.
Generated documentation images and a fast moving release line
The repository layout supports the claim that this is a maintained design system rather than a one-off component. The top level holds the library module in aestheticdialogs/, a sample app in app/, docs/, scripts/, the Gradle wrapper and build files, gradle.properties, a .idea directory, an .editorconfig, a CLAUDE.md and a CONTRIBUTING.md.
One detail in the dialogs section is worth calling out. Every image in the documentation is rendered from the real component by scripts/generate-docs-images.sh, and the README says that it therefore cannot drift from the library. That is a small piece of automation with a large effect on trust in a UI library, where screenshots of components that no longer exist are the usual failure mode.
The release history is the other thing to note. Version 1.3.8 dates from 2023-08-19, then 2.0.0 shipped on 2026-08-11 and 3.0.0 the following day, 2026-08-12, with the last push on 2026-08-12. So this is a library that was quiet for a long stretch and then changed shape twice in two days, and the README points anyone upgrading to a migration guide in docs/MIGRATION.md for both the 1.x and the 2.0 paths.
The licence is Apache-2.0, which for a UI library means permissive use with the usual notice and patent grant, and for a design system it means a fork is a legitimate option if your brand diverges from the defaults.
Editorial conclusion
Adopt AestheticDialogs if you build Android apps with Jetpack Compose and keep rewriting confirmation, alert and banner screens that behave slightly differently each time, and you want the accessibility and adaptive width behaviour decided for you. Skip it if you need Material 3 dialogs with a public Material signature, since the library keeps Material as an implementation detail, or if your minSdk is below 24. Before you add the dependency, read docs/MIGRATION.md if you are coming from 1.x or 2.0, check the release list because 2.0.0 and 3.0.0 both landed in August 2026, and confirm your project uses a Compose-enabled module. The last push was on 2026-08-12 and the licence is Apache-2.0.
Frequently asked questions
How do I add the AestheticDialogs library to an Android project?
Add implementation("com.gabrielthecode:aestheticdialogs:3.0.0") from Maven Central, or a version catalog entry. The library requires minSdk 24 and a Compose-enabled module, and it does not force Material 3 on your project.
How does an AestheticDialogs dialog get shown and dismissed?
There is no show or dismiss method. The dialog is on screen while it is in the composition, and onConfirm, onCancel and onDismiss are callbacks you handle, with the library never deciding on its own to close a dialog.
What does isConfirming do in AestheticDialogs?
Setting isConfirming to true puts a spinner on the confirm button and locks the rest of the dialog, including cancel, so a half-finished operation cannot be abandoned mid-flight.
Do I need to wrap my app in AestheticDialogsTheme?
No. Components resolve light or dark from the system setting when no theme is present. Wrapping your app in AestheticDialogsTheme inside your own theme is what lets you brand the dialogs.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/gabriel-thecode-aestheticdialogs)Was this useful?