| Crates.io | leptos-modal |
| lib.rs | leptos-modal |
| version | 0.3.1 |
| created_at | 2025-11-26 21:22:27.637488+00 |
| updated_at | 2026-01-13 13:54:02.547823+00 |
| description | Modal composable for Leptos |
| homepage | |
| repository | |
| max_upload_size | |
| id | 1952323 |
| size | 73,221 |
We're dsplce.co, check out our work on our website: dsplce.co ๐ค
๐ Modal composable for Leptos โ A minimal and type-safe framework for modals in Leptos applications.
โ
Type-safe modal system with generics
โ
Pass additional context to your modals
โ
Close your modals from anywhere with a global fn
โ
ARIA-compliant
โ
Esc key handling
โ
Portal-style rendering with proper z-index
โ
Proc macro for modal component creation
โ
Zero external CSS dependencies
Add to your Cargo.toml:
[dependencies]
leptos-modal = "0.3"
This crate requires Rust 2024 edition and is compatible with Leptos 0.8.
Wrap your app with ModalCollector to enable modal rendering. This works similar to a provider in React, in that it allows modals to be instantiated and used from the level of any of its descendants.
use leptos::prelude::*;
use leptos_modal::prelude::*;
#[component]
fn App() -> impl IntoView {
view! {
<ModalCollector>
<MainContent />
</ModalCollector>
}
}
Imagine in your application there is a user list view, and you want to add the functionality to delete a user to it. You decide a confirmation dialog would come in handy.
In leptos-modal, your modal component needs to adhere to the following signature:
#[modal]
pub fn ConfirmationModal(input: Input, ctx: Context, close: fn()) -> impl IntoView;
Where:
Input is dynamic data typically not known until the modal's opening is triggered (in our example it would be the user to delete). Should satisfy Clone + Send + Sync + 'staticContext is something constant, passed to the modal on registration and thus not changeable (eg. a function responsible for user deletion). Should satisfy Clone + Copy + Send + Sync + 'staticLet's implement the confirmation dialog:
use leptos_modal::prelude::*;
#[modal]
pub fn ConfirmationModal(user: User, ctx: Callback<String>, close: fn()) -> impl IntoView {
view! {
<div class="confirmation-modal">
<h2>"Confirm Action"</h2>
<p>{move || format!("Are you sure you want to delete {}?", user.name)}</p>
<div class="confirmation-modal__actions">
<button on:click=move |_| close()>"Cancel"</button>
<button on:click=move |_| {
ctx.run(user.id.clone());
close();
}>"Confirm"</button>
</div>
</div>
}
}
Now that you've defined the confirmation modal, let's call it using the use_modal! macro:
#[derive(Clone)]
struct User {
id: String,
name: String,
}
#[component]
fn UsersView(users: Vec<User>) -> impl IntoView {
let delete_user = Callback::new(move |id: String| {
// Deletion logic
});
// Registers the modal
let modal = use_modal!(ConfirmationModal, delete_user);
let on_delete = move |user: User| {
modal.open(user);
};
view! {
// โ Notice the `ConfirmationModal` is not mounted directly
// anywhere โ it is the `ModalCollector`'s job to render modals
<ul>
{
move || users.iter().map(|user| view! {
<li>
{user.name.clone()}
<button
on:click={
let user = user.clone();
move |_| {
on_delete(user.clone());
}
}
>"Delete"</button>
</li>
}).collect::<Vec<_>>()
}
</ul>
}
}
ModalCollectorSingleton component that manages modal state and rendering. We recommend that it wraps your app root.
#[modal]Proc macro that helps the ModalCollector render your modals.
use_modal!Creates a typed modal controller:
// Without context
let modal = use_modal!(ModalComponent);
// With context
let modal = use_modal!(ModalComponent, context);
Returns a modal struct with the methods:
open(args) - Opens the modal with provided argumentsclose() - Closes the modalcloseClose the modal from any component (that is a descendant of ModalCollector) in your application:
leptos_modal::close();
#[modal]
pub fn ModalComponent(input: Input, ctx: Context, close: fn()) -> impl IntoView {
view! {
// ...
}
}
let modal = use_modal!(ModalComponent, context);
modal.open(input)
#[modal]
pub fn ModalComponent(input: Input, ctx: (), close: fn()) -> impl IntoView {
view! {
// ...
}
}
let modal = use_modal!(ModalComponent);
modal.open(input)
#[modal]
pub fn ModalComponent(input: (), ctx: Context, close: fn()) -> impl IntoView {
view! {
// ...
}
}
let modal = use_modal!(ModalComponent, context);
modal.open(())
role="dialog", aria-modal)๐ฆ Crate: crates.io/crates/leptos-modal
๐ ๏ธ Repo: github.com/dsplce-co/leptos-modal
Contributions, issues, ideas? Hit us up ๐ค
MIT or Apache-2.0, at your option.