> We're dsplce.co, check out our work on our website: [dsplce.co](https://dsplce.co) π€
# dioxus-modal
[](https://dioxuslabs.com/)
[](https://crates.io/crates/dioxus-modal)
[](https://crates.io/crates/dioxus-modal)
[](https://crates.io/crates/dioxus-modal)
[](https://crates.io/crates/dioxus-modal)
π€ Type-safe, portal-rendered modals for [Dioxus](https://dioxuslabs.com/) β declare once, open from anywhere, close from anywhere.
`dioxus-modal` is a minimal modal framework: you describe a modal component with the `#[modal]` macro, register it with a hook, and open it from anywhere in your tree. The library owns the rendering, the overlay, the z-index and the Esc key β so you don't.
## π€ Features
- **Type-safe, generics all the way down** β a modal's `Input` and `Context` are real types; the compiler catches the mismatch before your users do
- **Close from anywhere** β one global `dioxus_modal::close()`, no setter prop-drilled through five components that don't care about it
- **Hand the modal its context once** β pass dependencies (a callback, an API client) on registration; they're baked in, not re-threaded on every open
- **`#[modal]` and you're done** β a proc macro writes the wiring, you write the `rsx!`
- **Zero external CSS** β nothing to import, no stylesheet to ship; the styles live inline
And the things you'd expect to _just work_:
- ARIA-compliant out of the box (`role="dialog"`, `aria-modal`)
- Esc closes the modal, no wiring needed
- Portal-style rendering at the top of the stack β one render site, sane z-index
- Built-in enter/leave transitions
---
## Table of Contents
- [π€ Features](#-features)
- [π¦ Installation](#-installation)
- [Fullstack applications](#fullstack-applications)
- [π§ͺ Usage](#-usage)
- [1. Set up the modal collector](#1-set-up-the-modal-collector)
- [2. Create a modal component](#2-create-a-modal-component)
- [3. Use the modal](#3-use-the-modal)
- [π API Reference](#-api-reference)
- [Compatibility with Dioxus versions](#compatibility-with-dioxus-versions)
- [π Repo & Contributions](#-repo--contributions)
- [π License](#-license)
βΈ»
## π¦ Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
dioxus-modal = "0.3"
```
This crate requires Rust 2024 edition and Dioxus 0.7. Check the [compatibility table](#compatibility-with-dioxus-versions) for other supported Dioxus versions.
### Fullstack applications
You need to enable the crate's `ssr` feature on the server for fullstack apps. In your `Cargo.toml`:
```toml
[features]
server = ["dioxus/server", "dioxus-modal/ssr"]
```
This tells `dioxus-modal` and its dependencies not to perform DOM-related operations at the server-side-rendering stage.
βΈ»
## π§ͺ Usage
### 1. Set up the modal collector
Add the `ModalCollector` component to your app to enable rendering modals.
```rust
use dioxus::prelude::*;
use dioxus_modal::prelude::*;
#[component]
fn App() -> Element {
rsx! {
Router::<Route> {}
ModalCollector {}
}
}
```
### 2. Create a modal component
Imagine your app has a user list view, and you want to add the ability to delete a user. A confirmation dialog would come in handy.
In `dioxus-modal`, your modal component needs to adhere to the following signature:
```rust
#[modal]
pub fn ConfirmationModal(input: Input, ctx: Context, close: fn()) -> Element;
```
Where:
- `Input` is dynamic data typically not known until the modal's opening is triggered (in our example, the user to delete). Must satisfy `'static`.
- `Context` is something constant, passed to the modal on registration and thus not changeable (e.g. a function responsible for user deletion). Must satisfy `'static + Clone`.
- `close` is a `fn()` you can call to close the modal from inside it.
Let's implement the confirmation dialog:
```rust
use dioxus_modal::prelude::*;
#[modal]
pub fn ConfirmationModal(user: User, delete_callback: fn(String), close: fn()) -> Element {
rsx! {
div {
class: "confirmation-modal",
h2 { "Confirm Action" }
p { "Are you sure you want to delete {user.name}?" }
div {
class: "confirmation-modal__actions",
button {
onclick: move |_| close(),
"Cancel"
}
button {
onclick: move |_| {
delete_callback(user.id.clone());
close();
},
"Confirm"
}
}
}
}
}
```
### 3. Use the modal
Now that the confirmation modal is defined, register it with the `use_modal!` macro and open it:
```rust
use dioxus::{logger::tracing, prelude::*};
use dioxus_modal::prelude::*;
#[derive(Clone, PartialEq)]
struct User {
id: String,
name: String,
}
#[component]
fn UsersView(users: Vec<User>) -> Element {
let delete_user = move |id: String| {
tracing::info!("Deleting user with id {id}");
};
// Registers the modal
let modal = use_modal!(ConfirmationModal, delete_user);
let on_delete = EventHandler::new(move |user: User| {
modal.open(user.clone());
});
rsx! {
// β Notice the `ConfirmationModal` is not mounted directly
// anywhere β it is the `ModalCollector`'s job to render modals
ul {
for user in users {
li {
key: "{user.id}",
"{user.name}"
button {
onclick: move |_| on_delete.call(user.clone()),
"Delete"
}
}
}
}
}
}
```
βΈ»
## π API Reference
### `ModalCollector`
Singleton component that manages modal state and rendering. Mount it once, near the root.
### `#[modal]`
Proc macro that helps the `ModalCollector` render your modals. Wraps your function so it fits the collector's render pipeline.
### `use_modal!`
Registers a modal and returns a typed controller:
```rust
// Without context
let modal = use_modal!(ModalComponent);
// With context
let modal = use_modal!(ModalComponent, context);
```
The controller exposes:
- `open(input)` β opens the modal with the provided input
To close, call the global `dioxus_modal::close()` (see below) or the `close` fn handed to your modal component β that's the `close` parameter in the `#[modal]` signature, used in the example above.
### `close`
Close the modal from anywhere in your application:
```rust
dioxus_modal::close();
```
### Defining a modal
#### With both context and input:
```rust
#[modal]
pub fn ModalComponent(input: Input, ctx: Context, close: fn()) -> Element {
rsx! {
// ...
}
}
let modal = use_modal!(ModalComponent, context);
modal.open(input)
```
#### Skipping context:
```rust
#[modal]
pub fn ModalComponent(input: Input, ctx: (), close: fn()) -> Element {
rsx! {
// ...
}
}
let modal = use_modal!(ModalComponent);
modal.open(input)
```
#### Skipping input:
```rust
#[modal]
pub fn ModalComponent(input: (), ctx: Context, close: fn()) -> Element {
rsx! {
// ...
}
}
let modal = use_modal!(ModalComponent, context);
modal.open(())
```
### Modal behaviour
- **Accessibility**: proper ARIA attributes (`role="dialog"`, `aria-modal`)
- **Keyboard navigation**: Esc closes the modal out of the box
- **Global state**: modals have a single place to render, and there is always one modal visible at a time (why would you want to show more than one modal at a time? π€¨)
- **Overlay**: semi-transparent backdrop with proper positioning
- **Responsive**: full viewport coverage with centered content
- **Transitions**: built-in smooth enter/leave modal transitions
βΈ»
## Compatibility with Dioxus versions
| `0.7` | `0.3` |
| `0.6` | `0.2` |
βΈ»
## π Repo & Contributions
π οΈ **Repo**: [https://github.com/dsplce-co/dioxus-modal](https://github.com/dsplce-co/dioxus-modal)<br>
π¦ **Crate**: [https://crates.io/crates/dioxus-modal](https://crates.io/crates/dioxus-modal)
Contributions, issues, ideas? Hit us up π€
βΈ»
## π License
MIT or Apache-2.0, at your option.