VOOZH about

URL: https://blog.logrocket.com/build-pomodoro-timer-tauri-using-react-and-vite/

โ‡ฑ Building a pomodoro timer with Tauri using React and Vite - LogRocket Blog


2022-09-07
1664
#react
Rahul Padalkar
130829
๐Ÿ‘ Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

Tauri is a set of tools that lets you build cross-platform desktop apps using frontend frameworks. When combined with React and Vite, it can be used to build extremely fast binaries for all desktop platforms.

๐Ÿ‘ Building A Pomodoro Timer With Tauri Using React And Vite

Developers can use Tauri to write secure, lean, and fast cross-platform desktop apps with web technologies. Electron has for many developers typically been the go-to framework for these apps, but Tauri is now well-positioned to compete.

In this post, we will build a simple pomodoro timer, and I invite you to follow along with this tutorial at your own pace.

๐Ÿš€ Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

What is Tauri?

It is โ€œblazingly fastโ€ (their words, not mine; but follow the link to see benchmarks and decide for yourself) because it uses Rust as its backend (vs. Node), produces smaller binaries, and is more secure than Node is.

It uses the WebView that the underlying OS provides to render the applicationโ€™s UI โ€” this is one of the reasons why the application binaries are smaller (as compared to Electron). The WRY library from the Tauri toolkit provides a unified interface to interact with WebViews provided by different operating systems. The WRY library uses the Tao crate for cross-platform window management.

Tauri brings it all together and enables developers to write powerful and performant desktop applications.

Prerequisites

Before we get started, we need to install a couple of things.

Rust

To install Rust, open your terminal and run:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

If you are on Windows, please follow these instructions.

npm

npm is a package manager for Node. It comes bundled with Node, so if you have Node installed on your system, you are likely to have npm as well.

If you donโ€™t have Node, install it using Homebrew,

brew install node

With npm and Rust installed, we are all set to start developing apps using Tauri!

Scaffold a Tauri app with create-tauri-app

Since the prerequisites are now installed, we can start developing our pomodoro timer desktop app. The folks at Tauri have made scaffolding an app super easy with the create-tauri-app npm package.

To get started, run:

npx create-tauri-app

After running this command, you will be required to enter some information:

  • The name of the project
  • The title of the window in which the app will load
  • The UI recipe
  • Template to use

For this tutorial app, I went with the following, found below ๐Ÿ‘‡

  • Name of the project as pomodoro
  • Title of the window as Pomodoro Timer App
  • UI recipe as create-vite
  • Template to use react-ts

๐Ÿ‘ Tauri Project Dependencies Installation

Hitting โ€œEnterโ€ will then install all the necessary packages and output a scaffolded project under the folder with the same name as the project name.

We are now all set to run the project for the very first time!

To run the project, run:

cd pomodoro // cding into the project folder
npm run tauri dev //running the app

This will first start the frontend server and then will download crates (if necessary) and compile the Rust backend. Once compiled without any errors, the app will start and you will be greeted with something like this:

๐Ÿ‘ Vite And React App Example

The project folder contains a lot of important files and folders. The two that we will deal with in this tutorial are:

  • src folder
  • src-tauri/tauri.config.json file

Running the app for the first time will take some time because the app needs to download and compile the necessary rust crates.

(Note: for folks using macOS for development: if you run the command to start the app and the app window appears over a full-screen app, you wonโ€™t be able to move the app window. This is a known bug and can be tracked here)

Building the frontend

We will use ChakraUI for UI components and styling. To set up ChakraUI, follow their getting started guide.

We will be building the UI below.


Over 200k developers use LogRocket to create better digital experiences

๐Ÿ‘ Image
Learn more โ†’

๐Ÿ‘ Timer App UI Example

Below are the requirements for our app:

  • Should have a โ€œStartโ€ and โ€œPauseโ€ button, which starts and stops the timer
  • Once the timer hits โ€œ0โ€, trigger a notification to the user
  • Option to choose interval lengths, i.e., 15 min., 30 min., and 60 min.
  • A โ€œResetโ€ button to reset the timer to the initial value

To start, head over to src/App.tsx.

// src/App.tsx
import { Button, Flex, Text } from "@chakra-ui/react";
import { useEffect, useState } from "react";

function App() {
 const [time, setTime] = useState(0);
 const [timerStart, setTimerStart] = useState(false);
 const buttons = [
 {
 value: 900,
 display: "15 minutes",
 },
 {
 value: 1800,
 display: "30 minutes",
 },
 {
 value: 3600,
 display: "60 minutes",
 },
 ];
 const toggleTimer = () => {
 setTimerStart(!timerStart);
 };
 useEffect(() => {
 const interval = setInterval(() => {
 if (timerStart) {
 if (time > 0) {
 setTime(time - 1);
 } else if (time === 0) {
 // TODO: Send notification to user.
 clearInterval(interval);
 }
 }
 }, 1000);
 return () => clearInterval(interval);
 }, [timerStart, time]);
 return (
 <div className="App" style={{ height: "100%" }}>
 <Flex
 background="gray.700"
 height="100%"
 alignItems="center"
 flexDirection="column"
 >
 <Text color="white" fontWeight="bold" marginTop="20" fontSize="35">
 Pomodoro Timer
 </Text>
 <Text fontWeight="bold" fontSize="7xl" color="white">
 {`${
 Math.floor(time / 60) < 10
 ? `0${Math.floor(time / 60)}`
 : `${Math.floor(time / 60)}`
 }:${time % 60 < 10 ? `0${time % 60}` : time % 60}`}
 </Text>
 <Flex>
 <Button
 width="7rem"
 background="tomato"
 color="white"
 onClick={toggleTimer}
 >
 {!timerStart ? "Start" : "Pause"}
 </Button>
 {/* TODO: Add Button to reset timer */}
 </Flex>
 <Flex marginTop={10}>
 {buttons.map(({ value, display }) => (
 <Button
 marginX={4}
 background="green.300"
 color="white"
 onClick={() => {
 setTimerStart(false);
 setTime(value);
 }}
 >
 {display}
 </Button>
 ))}
 </Flex>
 </Flex>
 </div>
 );
}
export default App;

Here, we are using ChakraUIโ€™s built-in components for setting up the layout for buttons and text. The setTimeout in the useEffect Hook drives the UI by setting state every passing second.

The effect reruns:

  • When the user clicks on the duration buttons (this sets both the timerStart and time state values)
  • When the user clicks on the Start/Pause buttons (this sets the timerStart state value)
  • Every passing second (the setTimeout triggers an update to the time value, see line number 29)

To display the time in consistent format (mm:ss), we need to do some math gymnastics. Itโ€™s certainly not perfect, but it does the job.

There are two to-dos here:

  • Adding a Reset button
    • On clicking this, a native dialog triggers, asking the user to confirm the action
  • Adding a notification trigger
    • When the timer hits 0, a notification needs to be sent to the user

But, before we start working on these items, we need to add the native APIs we will be calling in the src-tauri/tauri.config.json file. If we donโ€™t do this step, we wonโ€™t be able to trigger the native elements.

So, head over to src-tauri/tauri.config.json and add this to tauri.allowlist:

 "tauri": {
 "allowlist": {
 // other allowed items
 "notification": {
 "all": true
 },
 "dialog": {
 "all": true
 }
 } 
 }

(Note: for the sake of simplicity, everything is allowed for dialog and notification. We can be more specific to avoid unwanted access)

Triggering a notification and adding a reset timer button

// src/App.tsx
import { Button, Flex, Text } from "@chakra-ui/react";
import { useEffect, useState } from "react";
+ import { sendNotification } from "@tauri-apps/api/notification";
+ import { ask } from "@tauri-apps/api/dialog";
function App() {
 const [time, setTime] = useState(0);
 const [timerStart, setTimerStart] = useState(false);
 const buttons = [
 {
 value: 900,
 display: "15 minutes",
 },
 {
 value: 1800,
 display: "30 minutes",
 },
 {
 value: 3600,
 display: "60 minutes",
 },
 ];
 const toggleTimer = () => {
 setTimerStart(!timerStart);
 };
+ const triggerResetDialog = async () => {
+ let shouldReset = await ask("Do you want to reset timer?", {
+ title: "Pomodoro Timer App",
+ type: "warning",
+ });
+ if (shouldReset) {
+ setTime(900);
+ setTimerStart(false);
+ }
+ };
 useEffect(() => {
 const interval = setInterval(() => {
 if (timerStart) {
 if (time > 0) {
 setTime(time - 1);
 } else if (time === 0) {
+ sendNotification({
+ title: `Time's up!`,
+ body: `Congrats on completing a session!๐ŸŽ‰`,
+ });
 clearInterval(interval);
 }
 }
 }, 1000);
 return () => clearInterval(interval);
 }, [timerStart, time]);
 return (
 <div className="App" style={{ height: "100%" }}>
 <Flex
 background="gray.700"
 height="100%"
 alignItems="center"
 flexDirection="column"
 >
 <Text color="white" fontWeight="bold" marginTop="20" fontSize="35">
 Pomodoro Timer
 </Text>
 <Text fontWeight="bold" fontSize="7xl" color="white">
 {`${
 Math.floor(time / 60) < 10
 ? `0${Math.floor(time / 60)}`
 : `${Math.floor(time / 60)}`
 }:${time % 60 < 10 ? `0${time % 60}` : time % 60}`}
 </Text>
 <Flex>
 <Button
 width="7rem"
 background="tomato"
 color="white"
 onClick={toggleTimer}
 >
 {!timerStart ? "Start" : "Pause"}
 </Button>
+ <Button
+ background="blue.300"
+ marginX={5}
+ onClick={triggerResetDialog}
+ >
+ Reset
+ </Button>
 </Flex>
 <Flex marginTop={10}>
 {buttons.map(({ value, display }) => (
 <Button
 marginX={4}
 background="green.300"
 color="white"
 onClick={() => {
 setTimerStart(false);
 setTime(value);
 }}
 >
 {display}
 </Button>
 ))}
 </Flex>
 </Flex>
 </div>
 );
}
export default App;

Tauri offers a JS/TS API package for calling functions from the Rust backend. The necessary packages are already installed by the create-tauri-app utility, so we can directly import the npm package and use it in our frontend code.



For triggering a notification:

  • Import the sendNotification function from the notification module
  • Call the function in the frontend code (see line 42). More on the API here

For triggering a dialog:

  • Import the ask function from the dialog module
  • Call the function in the frontend code. The first parameter is the text that will appear in the dialog box (see line 27). More on the API here
  • Since it requires user input, the function returns a promise with a boolean value, so we can await it to get the value the user clicked on
  • If the user accepts, we reset the time value to 15 min. and stop the timer if it is running already

Finally, to run the app, run npm run tauri dev.

๐Ÿ‘ Pomodoro Timer Final

Building the app

Building the application for distribution can be achieved by running:

cargo tauri build

This will build the application and create a binary under src-tauri/target/release.

Now, the app is ready to be published!

Conclusion

Thatโ€™s it! Thank you for reading my walkthrough on building an app using Tauri with Vite and React. If you have any questions/suggestions feel free to reach out to me on Twitter @rahulnpadalkar. You can find the GitHub repo with all the code here.

Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID
  2. Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not server-side

    $ npm i --save logrocket 
    
    // Code:
    
    import LogRocket from 'logrocket'; 
    LogRocket.init('app/id');
     
    // Add to your HTML:
    
    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
     
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin
Get started now
๐Ÿ‘ Image
๐Ÿ‘ Image
๐Ÿ‘ Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

Stop hardcoding LLM SDKs: Dynamic LLM routing with OpenRouter and Next.js

Build dynamic LLM routing in Next.js with OpenRouter, TanStack AI, task classification, model fallbacks, and cost-aware routing.

๐Ÿ‘ Image
Chizaram Ken
Jun 16, 2026 โ‹… 13 min read

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension โ€” no new framework required.

๐Ÿ‘ Image
Ikeh Akinyemi
Jun 12, 2026 โ‹… 6 min read

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo โ€” with email/password login, Google OAuth, session persistence, and protected routes.

๐Ÿ‘ Image
Chinwike Maduabuchi
Jun 9, 2026 โ‹… 13 min read

AI dev tool power rankings & comparison [June 2026]

Compare the top AI development tools and models of June 2026. View updated rankings, feature breakdowns, and find the best fit for you.

๐Ÿ‘ Image
Chizaram Ken
Jun 8, 2026 โ‹… 11 min read
View all posts

Hey there, want to help make our blog better?

Join LogRocketโ€™s Content Advisory Board. Youโ€™ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now