Though most Tauri applications will use a full-fledged JavaScript framework, most apps require only simple templates, forms, buttons and links. If you want to create a tauri app and write most of your code in Rust, and you don't want to learn one of the WASM frameworks, you can build your tauri app using htmx.
What is htmx?
htmx is a library that allows developers to use a handful of directives in html to make ajax calls to a server and swap a target element with html that comes back from the server's response. Here's an example of htmx.
<button hx-post="/hello" hx-swap="outerHTML"> Click Me</button>
In this example when the button is clicked htmx will send a post request to /hello
when htmx gets a response back it will swap out the outerHTML (the whole <button>
) with what comes back. So, the end result could be like this.
<h2>Hello, you clicked the button!</h2>
But, tauri doesn't work on http requests, so to talk to our rust code we need to use tauri's api like the invoke
function. So, this is where the tauri extension for htmx comes in.
Tauri htmx extension
To use htmx in tauri you'll need to install htmx and the extension by copying the files including them in your project. You can find the latest htmx version and the extension on GitHub. Then include the script files in the head of your index.html
<script type="module" src="/tauri-ext.js" defer></script><script type="module" src="/htmx.js" defer></script>
Then enable the extension on the body tag.
<body hx-ext="tauri"></body>
From here you can use tauri-invoke
and tauri-listen
directives to interact with and receive html from your rust code.
Invoke
To invoke a command all you need to do is add tauri-invoke
on any html element. By default it will handle the click event on most elements and the submit event on forms.
<button tauri-invoke="hello" hx-target="#my-div"> Click me</button><div id="my-div"></div>
#[tauri::command]fn hello() -> String { "Hello, world!".to_string()}
This is the same as calling invoke('hello')
then it will insert "Hello, world!" into #my-div
.
It also works with forms.
<form tauri-invoke="save" hx-swap="outerHTML"> <input type="text" name="name" /> <button type="submit">Save</button></form>
#[tauri::command]async fn save(name: &str) -> Result<String, String> { do_save(name).await; format!("Saved {name} to contacts")}
This is the same as calling invoke('save', {name})
, hx-swap will swap the form for our success message.
Listen for events
You can also listen to events. For example, if you have a background task working and wish to update the frontend with progress.
<div tauri-listen="progress"></div>
fn do_stuff(app: AppHandle) { thread::spawn(move || { for i in 0..=100 { app.emit("progress", format!("progress: {i}%")).unwrap(); thread::sleep(Duration::from_millis(100)); } });}
This uses the event.listen()
tauri function and will use htmx to swap the innerHTML of the div. You can still use hx-target.
Examples
There are a few example apps in the extension's repo.
Rust templating
When it comes to templating your html with rust there are several good options such as shtml, maud, and askama.
Here's snippet from the pomodoro example using shtml.
pub fn Main() -> Component { html! { <div> <form tauri-invoke="set_task_description" class="flex flex-col space-y-4 mb-8"> <label for="description"> Whatcha working on? </label> <input type="text" name="description" class="bg-gray-400 text-black" /> <button type="submit" class="bg-gray-700">Set</button> </form> <div> <button tauri-invoke="pomodoro" hx-swap="outerHTML" class="bg-gray-700 p-2"> Start working </button> <div tauri-listen="tick"></div> </div> </div> }}
The pros and cons of htmx with tauri
The pros of htmx are the same in tauri as on the web, less JavaScript, simple code, you can use Rust for your templating without using a WASM framework. Also, will reduce the final build size of your application having fewer JavaScript dependencies.
The cons in the case of tauri would be development experience. Every time you update a template the app will restart, this can be tedious while making small changes such as styling with tailwind.