Agent skill
adding-tauri-splashscreen
Guides the user through adding a Tauri splashscreen, splash screen, loading screen, or startup screen to their application. Covers configuration, custom splash HTML, closing the splash when ready, and styling.
Install this agent skill to your Project
npx add-skill https://github.com/dchuk/claude-code-tauri-skills/tree/main/tauri/tauri-splashscreen
SKILL.md
Tauri Splashscreen Implementation
This skill covers implementing splash screens in Tauri v2 applications. A splash screen displays during application startup while the main window loads and initializes.
Overview
The splash screen pattern involves:
- Showing a splash window immediately on launch
- Hiding the main window until ready
- Performing initialization tasks (frontend and backend)
- Closing splash and showing main window when complete
Configuration
Window Configuration
Configure both windows in tauri.conf.json:
{
"app": {
"windows": [
{
"label": "main",
"title": "My Application",
"width": 1200,
"height": 800,
"visible": false
},
{
"label": "splashscreen",
"title": "Loading",
"url": "splashscreen.html",
"width": 400,
"height": 300,
"center": true,
"resizable": false,
"decorations": false,
"transparent": true,
"alwaysOnTop": true
}
]
}
}
Key settings:
"visible": falseon main window - hides it until ready"url": "splashscreen.html"- points to splash screen HTML"decorations": false- removes window chrome for cleaner look"transparent": true- enables transparent backgrounds"alwaysOnTop": true- keeps splash visible during loading
Splash Screen HTML
Create splashscreen.html in your frontend source directory (e.g., src/ or public/):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loading</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
overflow: hidden;
background: transparent;
}
.splash-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border-radius: 12px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.logo {
width: 80px;
height: 80px;
margin-bottom: 24px;
}
.app-name {
font-size: 24px;
font-weight: 600;
margin-bottom: 16px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top-color: #4f46e5;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
margin-top: 16px;
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
}
</style>
</head>
<body>
<div class="splash-container">
<!-- Replace with your logo -->
<svg class="logo" viewBox="0 0 100 100" fill="none">
<circle cx="50" cy="50" r="45" stroke="#4f46e5" stroke-width="4"/>
<path d="M30 50 L45 65 L70 35" stroke="#4f46e5" stroke-width="4" fill="none"/>
</svg>
<div class="app-name">My Application</div>
<div class="loading-spinner"></div>
<div class="loading-text">Loading...</div>
</div>
</body>
</html>
Frontend Setup
TypeScript/JavaScript Implementation
In your main entry file (e.g., src/main.ts):
import { invoke } from '@tauri-apps/api/core';
// Helper function for delays
function sleep(seconds: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
// Frontend initialization
async function initializeFrontend(): Promise<void> {
// Perform frontend setup tasks here:
// - Load configuration
// - Initialize state management
// - Set up routing
// - Preload critical assets
// Example: simulate initialization time
await sleep(1);
// Notify backend that frontend is ready
await invoke('set_complete', { task: 'frontend' });
}
// Start initialization when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
initializeFrontend().catch(console.error);
});
Alternative: Using Window Events
import { invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
async function initializeFrontend(): Promise<void> {
// Your initialization logic
const config = await loadConfig();
await setupRouter();
await preloadAssets();
// Signal completion
await invoke('set_complete', { task: 'frontend' });
}
// Wait for window to be fully ready
getCurrentWindow().once('tauri://created', () => {
initializeFrontend();
});
Backend Setup
Add Tokio Dependency
cargo add tokio --features time
Rust Implementation
In src-tauri/src/lib.rs:
use std::sync::Mutex;
use tauri::{AppHandle, Manager, State};
// Track initialization state
struct SetupState {
frontend_task: bool,
backend_task: bool,
}
impl Default for SetupState {
fn default() -> Self {
Self {
frontend_task: false,
backend_task: false,
}
}
}
// Command to mark tasks complete
#[tauri::command]
async fn set_complete(
app: AppHandle,
state: State<'_, Mutex<SetupState>>,
task: String,
) -> Result<(), String> {
let mut state = state.lock().map_err(|e| e.to_string())?;
match task.as_str() {
"frontend" => state.frontend_task = true,
"backend" => state.backend_task = true,
_ => return Err(format!("Unknown task: {}", task)),
}
// Check if all tasks are complete
if state.frontend_task && state.backend_task {
// Close splash and show main window
if let Some(splash) = app.get_webview_window("splashscreen") {
splash.close().map_err(|e| e.to_string())?;
}
if let Some(main) = app.get_webview_window("main") {
main.show().map_err(|e| e.to_string())?;
main.set_focus().map_err(|e| e.to_string())?;
}
}
Ok(())
}
// Backend initialization
async fn setup_backend(app: AppHandle) {
// IMPORTANT: Use tokio::time::sleep, NOT std::thread::sleep
// std::thread::sleep blocks the entire async runtime
// Perform backend initialization:
// - Database connections
// - Load configuration
// - Initialize services
// Example: simulate work
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
// Mark backend as complete
if let Some(state) = app.try_state::<Mutex<SetupState>>() {
let _ = set_complete(
app.clone(),
state,
"backend".to_string(),
).await;
}
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.manage(Mutex::new(SetupState::default()))
.invoke_handler(tauri::generate_handler![set_complete])
.setup(|app| {
let handle = app.handle().clone();
// Spawn backend initialization
tauri::async_runtime::spawn(async move {
setup_backend(handle).await;
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Simple Implementation
For simpler cases where you only need to wait for the frontend:
Configuration
{
"app": {
"windows": [
{
"label": "main",
"visible": false
},
{
"label": "splashscreen",
"url": "splashscreen.html",
"width": 400,
"height": 300,
"decorations": false
}
]
}
}
Frontend
import { invoke } from '@tauri-apps/api/core';
async function init() {
// Initialize your app
await setupApp();
// Close splash, show main
await invoke('close_splashscreen');
}
document.addEventListener('DOMContentLoaded', init);
Backend
use tauri::{AppHandle, Manager};
#[tauri::command]
async fn close_splashscreen(app: AppHandle) -> Result<(), String> {
if let Some(splash) = app.get_webview_window("splashscreen") {
splash.close().map_err(|e| e.to_string())?;
}
if let Some(main) = app.get_webview_window("main") {
main.show().map_err(|e| e.to_string())?;
main.set_focus().map_err(|e| e.to_string())?;
}
Ok(())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![close_splashscreen])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Styling Variations
Minimal Splash
<style>
.splash-container {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
background: #ffffff;
}
.logo {
width: 120px;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(0.95); }
}
</style>
Progress Bar Splash
<style>
.progress-container {
width: 200px;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
overflow: hidden;
margin-top: 20px;
}
.progress-bar {
height: 100%;
background: #4f46e5;
animation: progress 2s ease-in-out infinite;
}
@keyframes progress {
0% { width: 0%; }
50% { width: 70%; }
100% { width: 100%; }
}
</style>
<div class="progress-container">
<div class="progress-bar"></div>
</div>
Dark Theme with Glow
<style>
.splash-container {
background: #0a0a0a;
color: #ffffff;
}
.logo {
filter: drop-shadow(0 0 20px rgba(79, 70, 229, 0.5));
}
.app-name {
text-shadow: 0 0 20px rgba(79, 70, 229, 0.5);
}
</style>
Important Notes
-
Async Sleep: Always use
tokio::time::sleepin async Rust code, neverstd::thread::sleep. The latter blocks the entire runtime. -
Window Labels: Ensure window labels in code match those in
tauri.conf.json. -
Error Handling: The splash screen should handle errors gracefully. If initialization fails, show the main window anyway with an error state.
-
Timing: Keep splash screen visible long enough for branding but not so long it frustrates users. Aim for 1-3 seconds minimum.
-
Transparent Windows: When using
transparent: true, ensure your HTML hasbackground: transparentonhtmlandbodyelements. -
Mobile Considerations: On mobile platforms, splash screens work differently. Consider using platform-native splash screens for iOS and Android.
Troubleshooting
Splash screen doesn't appear:
- Verify the URL path is correct in
tauri.conf.json - Check that the HTML file exists in the correct location
Main window shows too early:
- Ensure
visible: falseis set on the main window - Verify the
set_completecommand is being called correctly
Transparent background not working:
- Set
transparent: truein window config - Set
background: transparentin CSS for html and body - On some platforms, you may need
decorations: false
Window position issues:
- Use
center: truefor centered splash screens - Or specify explicit
xandycoordinates
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
understanding-tauri-ipc
Teaches the assistant about Tauri IPC (Inter-Process Communication) patterns including brownfield and isolation approaches for secure message passing between frontend and Rust backend.
testing-tauri-apps
Guides developers through testing Tauri applications including unit testing with mock runtime, mocking Tauri APIs, WebDriver end-to-end testing with Selenium and WebdriverIO, and CI integration with GitHub Actions.
migrating-tauri-apps
Assists users with migrating Tauri applications from v1 to v2 stable, and from v2 beta to v2 stable, covering breaking changes, configuration updates, API migrations, and plugin system changes.
developing-tauri-plugins
Guides the user through Tauri plugin development, including creating plugin extensions, configuring permissions, and building mobile plugins for iOS and Android platforms.
managing-tauri-plugin-permissions
Guides users through configuring Tauri plugin permissions, capabilities, and security. Covers platform-specific capabilities, window-targeted permissions, using official and community plugin permissions, and writing custom plugin permissions with scopes.
optimizing-tauri-binary-size
Guides users through Tauri binary size optimization techniques to produce small, efficient desktop application bundles using Cargo profile settings and build configurations.
Didn't find tool you were looking for?