dioxus-modal 0.3.2

Modal composable for Dioxus
Documentation
> We're dsplce.co, check out our work on our website: [dsplce.co]https://dsplce.co πŸ–€

# dioxus-modal

[![Dioxus](https://img.shields.io/badge/Dioxus-0.7-EF4A00?style=for-the-badge&logo=rust&logoColor=white)](https://dioxuslabs.com/)
[![crates.io downloads](https://img.shields.io/crates/d/dioxus-modal?style=for-the-badge&color=%23FF0346)](https://crates.io/crates/dioxus-modal)
[![crates.io size](https://img.shields.io/crates/size/dioxus-modal?style=for-the-badge)](https://crates.io/crates/dioxus-modal)
[![License](https://img.shields.io/crates/l/dioxus-modal.svg?style=for-the-badge)](https://crates.io/crates/dioxus-modal)
[![crates.io](https://img.shields.io/crates/v/dioxus-modal?style=for-the-badge&color=%230F80C1)](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

| Dioxus version | `dioxus-modal` version |
|:---------------|:-----------------------|
| `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.