DEV Community

Cover image for 8 Tips for Creating a Native Look and Feel in Tauri Applications
UEDA Akira
UEDA Akira

Posted on

8 Tips for Creating a Native Look and Feel in Tauri Applications

I recently released Jomai, a Markdown-focused desktop search app made in Tauri.
Apps made with Tauri is, of course, native app. But its UI part runs on WebView, so its look and feel tend to be like a web app. I designed Jomai to behave as much as possible like a native app, so I will summarize that here.
Jomai is currently available only for macOS, so this article is also intended for macOS.

Environment:

  • Tauri: 1.1.1
  • Platform: macOS

Contents

Get rid of the beep sound on keystrokes for non-input fields

A beep sounds when keystrokes are made while the focus is on a non-input item other than <input> or <textarea>. This issue occurs in WebView on macOS and has a GitHub issue, which has not been resolved as of October 6, 2022.
https://github.com/tauri-apps/tauri/issues/2626

You can suppress the beep sound by calling preventDefault() on keydown event, but this will also disable all input to <input>, etc., so it is necessary to suppress it selectively.

Jomai uses the following code.

export const useKey = (callback: UseKeyCallback) => {
  const onKeydown = useCallback(
    (event: KeyboardEvent) => {
    // callback should return true if processed
      const consumed = callback(event.code, {
        ctrl: event.ctrlKey,
        shift: event.shiftKey,
      });

      const e = event.composedPath()[0];
      if (
        !(e instanceof HTMLInputElement || e instanceof HTMLAreaElement)
        || consumed
      ) {
        // preventDefault() if it is not an input item
        // also preventDefault() if it was handled by a callback
        applyBeepSoundWorkaround(event);
      }
    },
    [callback],
  );

  useEffect(() => {
    window.addEventListener('keydown', onKeydown);
    return () => {
      window.removeEventListener('keydown', onKeydown);
    };
  }, [onKeydown]);
};
Enter fullscreen mode Exit fullscreen mode

It solves most cases of the problem to call preventDefault() if the event source is not an HTMLInputElement or HTMLAreaEelement. However, when the focus is on one of these input items and the keyboard shortcut is used to move the focus to a non-input item (yes, it isn't very easy), there will be the sound. So I've done more to fix the problem.

Make text in UI not selectable

While it is normal to be able to select text on a web page, this is generally not the case in native apps. To avoid unintentional text selection when trying to manipulate the UI, use user-select: none at the base of DOM elements such as <body>.

body {
  user-select: none;
}
Enter fullscreen mode Exit fullscreen mode

Set mouse cursor to default

Since the previous section has limited the operation of UI elements to button pressing, etc., let's express it that way for the mouse cursor as well.

body {
  cursor: default;
}
Enter fullscreen mode Exit fullscreen mode

cursor: default prevents I-beam when hovering text, for example. The cursor attribute should be explicitly specified if the UI element is manipulatable, such as a button.

button {
  cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

Do not scroll the entire screen

When content is larger than the size of the screen, it's usual for web pages to scroll the entire screen. In native apps, it is natural to display fixed UI components and make only some scrollable.

In Jomai, tabs and forms at the top of the screen are always visible, and only the content part at the bottom is scrollable.

scrollable and non-scarollable areas in the app
See it in action.

only contents area is scrollable

To achieve this, set overflow: hidden for the entire screen, and set overflow: scroll and height for the scrollable area.

.page {
  overflow: hidden;
}

.contents {
  overflow: scroll;
  height: calc(100vh - 40px)
}
Enter fullscreen mode Exit fullscreen mode

Suppress bounce scrolling across the screen

Bounce scrolling is that thing that makes a spring-like motion when scrolling to the edge of the screen. By default, the entire screen is scrollable with the bounce motion. See this video.

bounce scroll

Bounce scrolling is associated with scrollability, so it should be suppressed if the entire screen should not be scrollable.

body {
  overflow: hidden;
}
Enter fullscreen mode Exit fullscreen mode

Prepare your own menu

Tauri provides a basic menu by default.

default menus

If you need customization, you will need to build these menus yourself, which is a bit tedious. See tauri::Menu::os_default for reference.

Prepare your own keyboard shortcuts

Be careful when using your own keyboard shortcuts on web pages, as they can confuse users, but be proactive about introducing them in native apps.

Support dark mode

I am a fan of dark mode and would like to see dark mode support in apps I use regularly.
Tauri has appWindow.theme() to get the current theme and appWindow.onThemeChanged() to monitor change events. Using these, you can change the app to match the OS theme settings.

Here is an example implementation in React.

import { appWindow, Theme } from '@tauri-apps/api/window';

export const useTheme = () => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  useEffect(() => {
    let unlisten: UnlistenFn | undefined;

    (async () => {
      setTheme(await appWindow.theme());

      unlisten = await appWindow.onThemeChanged(({ payload: theme }) => {
        console.log(`theme changed to ${theme}`);
        setTheme(theme);
      });
    })();

    return () => {
      if (unlisten != null) {
        unlisten();
      }
    };
  }, []);

  return theme;
};
Enter fullscreen mode Exit fullscreen mode

Summary

In this article, I have introduced some tips to make Tauri-made apps more native-like. A little effort will improve the usability of your apps. I hope you find them helpful.

Overall, developing with Tauri has been a great experience, and I look forward to creating many more apps with Tauri.

Please also check out Jomai, which I'm developing using these techniques.

Top comments (0)