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
- Make text in UI not selectable
- Set mouse cursor to default
- Do not scroll the entire screen
- Suppress bounce scrolling across the screen
- Prepare your own menu
- Prepare your own keyboard shortcuts
- Support dark mode
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]);
};
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;
}
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;
}
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;
}
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.
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)
}
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 scrolling is associated with scrollability, so it should be suppressed if the entire screen should not be scrollable.
body {
overflow: hidden;
}
Prepare your own menu
Tauri provides a basic menu by default.
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;
};
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)