dioxus-modal 0.3.2

Modal composable for Dioxus
Documentation

We're dsplce.co, check out our work on our website: dsplce.co πŸ–€

dioxus-modal

Dioxus crates.io downloads crates.io size License crates.io

πŸ–€ Type-safe, portal-rendered modals for Dioxus β€” 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

βΈ»

πŸ“¦ Installation

Add to your Cargo.toml:

[dependencies]
dioxus-modal = "0.3"

This crate requires Rust 2024 edition and Dioxus 0.7. Check the compatibility table 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:

[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.

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:

#[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:

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:

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:

// 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:

dioxus_modal::close();

Defining a modal

With both context and input:

#[modal]
pub fn ModalComponent(input: Input, ctx: Context, close: fn()) -> Element {
    rsx! {
        // ...
    }
}

let modal = use_modal!(ModalComponent, context);
modal.open(input)

Skipping context:

#[modal]
pub fn ModalComponent(input: Input, ctx: (), close: fn()) -> Element {
    rsx! {
        // ...
    }
}

let modal = use_modal!(ModalComponent);
modal.open(input)

Skipping input:

#[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 πŸ“¦ Crate: https://crates.io/crates/dioxus-modal

Contributions, issues, ideas? Hit us up πŸ–€

βΈ»

πŸ“„ License

MIT or Apache-2.0, at your option.