...OVERVIEW...
What it is
Every script eventually needs to ask a question. A shell script wants a picker; a Rust tool wants a confirm box; a window manager binding wants a launcher. r-modals turns that into one blocking call: Modal::new("Title").run() returns the pick, or None when you cancel.
Under the hood it is eframe/egui, so the window is native, borderless and fast on both X11 and Wayland. You do not need to learn an immediate-mode GUI to use it.
A picker that does more than pick
- 01
Options with labels, descriptions and icons
Icons can be a file path, raw bytes, or RGBA data.
- 02
Submenus
Drill down with
sub_optioninstead of flattening every choice into one list. - 03
Actions
Run a command, a shell one-liner, or a Rust closure when an option is selected.
- 04
Search
Substring or fuzzy matching across label, id, description and keywords. Multi-select, empty states, custom input and MRU history are opt-in.
The parts that make it feel native
Confirm and Prompt dialogs return bool and Option<String> with the same styling and keyboard behavior. Workspaces can place a dialog on a specific Hyprland, Sway, i3, niri, KDE or generic X11 workspace — and optionally do it without stealing focus.
A spawnable dialog API lets a program start itself as a child dialog and stream options in. That is how shell scripts get the same contract through --menu, --ask, --confirm and --error. Themes are plain CSS, not a bespoke styling layer: Theme::dark(), Theme::light(), Theme::transparent_dark() and Theme::auto() cover the common cases.
Shortcuts and prefixes
One config.toml can bind keys to options or terminal commands, and bind the start of a query to an action. Prefixes complete with Tab while you type: >s completes to >sync, >sh to >show items. If the query does not start a registered prefix, Tab completes the focused row's label instead.
Shortcut keys are consumed before the built-in keys, so you can rebind Enter if you really want to. Option shortcuts render as row hints automatically.
Command-line mode
Piping stdin to r-modals --menu turns it into a dmenu replacement. The pick goes to stdout and the exit code says what happened: 0 for a pick, 1 for cancel, 2 for an error. That small contract is what makes it usable from shell scripts without a wrapper.