Skip to content
omaris

System

Splash Screen

The screen an app shows before it has anything else to show — six looks, seven indicators, eight ways to leave, and no wiring for the common case.

import { SplashScreen } from 'omaris'
Learn

Examples

Basic

The whole of it: a splash over the app, and the app behind it once it goes. In a real layout you would leave open alone — the splash waits for the page to load and takes itself off. Here it is bound to a timer so you can watch it again.

Dashboard

The app behind the splash.

Acme

Getting your workspace ready.

Loading your workspace

<script lang="ts">	import { Button, SplashScreen, Text } from 'omaris';​	let booting = $state(true);​	$effect(() => {		if (!booting) return;		const timer = setTimeout(() => (booting = false), 1800);		return () => clearTimeout(timer);	});</script>​<div class="relative h-full w-full overflow-hidden rounded-shape-lg border border-border">	<div class="flex h-full flex-col items-start gap-3 p-6">		<Text variant="headline-sm" as="h2">Dashboard</Text>		<Text tone="muted">The app behind the splash.</Text>		<Button size="xs" onclick={() => (booting = true)}>Boot again</Button>	</div>​	<SplashScreen		bind:open={booting}		inline		title="Acme"		description="Getting your workspace ready."		pattern="gradient"		message="Loading your workspace"	>		{#snippet logo()}			...		{/snippet}	</SplashScreen></div>

Looks

Two decisions, taken separately: variant is the colour the screen is, pattern is the decorative layer over it. Six by seven, and every pairing is one word each.

grid

dots

aurora

spotlight

mesh

gradient

<script lang="ts">	import { SplashScreen, type SplashScreenPattern, type SplashScreenVariant } from 'omaris';​	const looks: { variant: SplashScreenVariant; pattern: SplashScreenPattern }[] = [		{ variant: 'background', pattern: 'grid' },		{ variant: 'surface', pattern: 'dots' },		{ variant: 'primary', pattern: 'aurora' },		{ variant: 'inverse', pattern: 'spotlight' },		{ variant: 'background', pattern: 'mesh' },		{ variant: 'surface', pattern: 'gradient' }	];</script>​<div class="grid w-full gap-4 sm:grid-cols-2 lg:grid-cols-3">	{#each looks as look (look.variant + look.pattern)}		<div class="relative h-48 overflow-hidden rounded-shape-lg border border-border">			<SplashScreen				open				inline				size="sm"				variant={look.variant}				pattern={look.pattern}				title={look.pattern}				indicator="dots"			/>		</div>	{/each}</div>

Indicators

Seven ways to say "working on it". bar is the only one that can also say how far along it is; the rest are honest about knowing nothing.

spinner

ring

dots

bar

wave

orbit

pulse

<script lang="ts">	import { SplashScreen, type SplashScreenIndicator } from 'omaris';​	const indicators: SplashScreenIndicator[] = [		'spinner',		'ring',		'dots',		'bar',		'wave',		'orbit',		'pulse'	];</script>​<div class="grid w-full gap-4 sm:grid-cols-2 lg:grid-cols-4">	{#each indicators as indicator (indicator)}		<div class="relative h-40 overflow-hidden rounded-shape-lg border border-border">			<SplashScreen open inline size="sm" {indicator} message={indicator} />		</div>	{/each}</div>

Exits

The part anyone remembers. Press one and watch it go: iris opens a hole in the middle and the app is behind it, rise takes the whole screen up like a curtain.

the app

fade

the app

scale

the app

zoom

the app

rise

the app

fall

the app

swipe

the app

blur

the app

iris

<script lang="ts">	import { Button, SplashScreen, Text, type SplashScreenExit } from 'omaris';​	const exits: SplashScreenExit[] = [		'fade',		'scale',		'zoom',		'rise',		'fall',		'swipe',		'blur',		'iris'	];​	/** Which are currently up. Replaying one drops it and puts it back a frame later. */	let up = $state<Record<string, boolean>>(Object.fromEntries(exits.map((e) => [e, true])));​	function replay(exit: string) {		up[exit] = false;		setTimeout(() => (up[exit] = true), 700);	}</script>​<div class="grid w-full gap-4 sm:grid-cols-2 lg:grid-cols-4">	{#each exits as exit (exit)}		<div class="flex flex-col gap-2">			<div class="relative h-40 overflow-hidden rounded-shape-lg border border-border">				<div class="grid h-full place-items-center">					<Text variant="label-sm" tone="muted">the app</Text>				</div>				<SplashScreen					open={up[exit]}					inline					{exit}					size="sm"					pattern="gradient"					title={exit}					indicator="none"					minDuration={0}					duration={700}				/>			</div>			<Button size="xs" variant="outlined" onclick={() => replay(exit)}>Play {exit}</Button>		</div>	{/each}</div>

Boot sequence

createSplash carries open, progress and message together, so the screen says what the app is actually doing. run walks the steps, weights the bar by how slow each one is, and takes the splash down at the end — including when a step throws.

Workspace

Loaded.

Acme

v2.4.0
<script lang="ts">	import { Button, createSplash, SplashScreen, Text } from 'omaris';​	// `open: false` because the app is already up here; a real one starts up.	const splash = createSplash({ open: false });​	const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));​	const boot = () =>		splash.run([			{ label: 'Signing you in', run: () => wait(700) },			{ label: 'Loading your workspace', weight: 2, run: () => wait(1200) },			{ label: 'Almost there', run: () => wait(500) }		]);</script>​<div class="relative h-full w-full overflow-hidden rounded-shape-lg border border-border">	<div class="flex h-full flex-col items-start gap-3 p-6">		<Text variant="headline-sm" as="h2">Workspace</Text>		<Text tone="muted">Loaded.</Text>		<Button size="xs" onclick={boot}>Run the boot sequence</Button>	</div>​	<SplashScreen		{splash}		inline		title="Acme"		pattern="aurora"		indicator="bar"		percent		halo		exit="iris"	>		{#snippet logo()}			...		{/snippet}		{#snippet footer()}			<Text variant="label-sm">v2.4.0</Text>		{/snippet}	</SplashScreen></div>

Overridden

Every part is reachable. This one is left-aligned, bottom-anchored, has a display-sized title, a tertiary indicator and a footer of its own — none of which is a prop the component had to invent.

Ledger

Reconciling accounts

Built by Acme · v2.4.0
<script lang="ts">	import { Button, SplashScreen, Text } from 'omaris';​	let open = $state(true);</script>​<div class="flex h-full w-full flex-col gap-3">	<div class="relative min-h-0 flex-1 overflow-hidden rounded-shape-lg border border-border">		<SplashScreen			{open}			inline			variant="inverse"			pattern="grid"			align="bottom"			enter="reveal"			indicator="wave"			exit="rise"			title="Ledger"			message="Reconciling accounts"			classes={{				content: 'w-full max-w-none items-start px-10 text-start',				title: 'text-display-sm',				indicator: 'text-tertiary',				message: 'opacity-60',				footer: 'items-start px-10 text-start'			}}		>			{#snippet footer()}				<Text variant="label-sm">Built by Acme · v2.4.0</Text>			{/snippet}		</SplashScreen>	</div>​	<Button size="xs" variant="outlined" class="self-start" onclick={() => (open = !open)}>		{open ? 'Hide' : 'Show'}	</Button></div>

When to use it

Use it for

  • The first screen of an app with work to do before it can draw: a session to restore, a config to fetch. Drop <SplashScreen title="Acme" /> in +layout.svelte; it renders on the server, waits for load and removes itself.
  • A boot sequence with steps. createSplash() plus splash.run([{ label: 'Signing you in', run: … }, …]) shows each step and moves the bar as they finish.
  • A first-run screen. createSplash({ id: 'boot', once: true }) shows it once per tab, so a reload goes straight to the app.
  • A brand moment on a phone-sized web app: variant="primary", a logo, exit="iris".
  • A pane that covers its own content while it reloads. inline keeps the screen inside the container instead of over the viewport.

Not for

  • A section of a page still filling in → Skeleton in the shape of what is coming.
  • A known fraction of a job, like an upload or export → Progress, next to the thing.
  • A route change inside a running app → PageTransition. A splash between screens looks like a restart.
  • A short wait after pressing something → the loading state on Button, or Empty for a panel that came back with nothing.
  • An app that failed to boot. The splash comes down either way; say what went wrong with Alert or an Empty with a Retry.

Do

  • Leave open alone unless you know better than the page-load event. Bind it when you do; auto-hide switches off once the splash is controlled.
  • Set appearAfter on a fast app. A boot that finishes inside the delay never shows a splash at all.
  • Keep minDuration as it is. It stops a 40ms boot from flashing.
  • Say what is happening in message: "Loading your workspace". It changes with a fade.
  • Use simulate for an indeterminate wait, and a real progress the moment you have one. Never both.
  • Match exit to the app: iris and rise for a phone-shaped app, fade for a dashboard.
  • Reach the parts through classes, like classes={{ title: '…' }}, rather than wrapping the component.

Don't

  • Hold it up for work the person did not ask for, like analytics or a prefetch.
  • Show one on every navigation. It belongs to the boot, not the router.
  • Expect contrast from variant="primary" with pattern="gradient". Branded variants tint their patterns with the text colour; neutral ones tint with primary.
  • Leave a simulated bar running forever. It stops at 90 on purpose; only the splash coming down puts it on 100.
  • Put a button on it that is not a way out of a failed boot.
  • Give it a z-index. The fixed splash is above the app already; an inline one only needs its container to be relative.

Quick reference

variant
  • background (default)
  • surface
  • primary
  • inverse
  • glass
  • image

The colour the screen is. primary and inverse are the branded ones; glass is for a splash that goes over an app that is already on screen.

pattern
  • none (default)
  • gradient
  • aurora
  • mesh
  • spotlight
  • grid
  • dots

The decorative layer over the colour.

size
  • sm
  • md (default)
  • lg

How big the mark and the type are.

align
  • center (default)
  • top
  • bottom

Where the block sits. top leaves room for a wordmark below it.

API

SplashScreen

The screen an app shows before it has anything else to show.

That is the whole of the common case. With no props it renders on the server — so it is in the HTML, not painted after hydration — waits for the page to finish loading, holds for minDuration so a fast boot doesn't blink, and leaves. Nothing to wire up, nothing to remember to turn off.

Everything past that is a decision you can take back:

- `bind:open` when you know what "ready" means. Auto-hide switches itself off the moment the splash is controlled, so it can never take the screen away from under you. - `splash` — a {@link SplashController} — when booting is a sequence: it carries open, progress and message together, and splash.run([…]) walks the steps and puts each one on screen. - `variant` and `pattern` are the look: a colour scheme and a decorative layer, chosen separately, so a branded splash and a plain one differ by one word rather than by a rewrite. - `exit` is how it leaves, which is the part anyone remembers. iris opens a hole in the middle and the app is behind it. - `inline` puts the whole thing inside its container instead of over the viewport — the same screen for a pane, a dashboard tab or a preview that is still loading.

Two flashes are designed out rather than left to the caller. minDuration keeps a splash that only had 40ms of work to cover on screen long enough to be read instead of blinking, and appearAfter stops it appearing at all when the app was ready before the delay ran out — the two together mean a splash is either absent or deliberate.

import { SplashScreen } from 'omaris'
<!-- +layout.svelte --><SplashScreen title="Acme" />

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
The full-bleed surface. Everything else sits on it.
scrim
The tint over a photograph, so text stays legible on any image.
backdrop
The decorative layer — the pattern, when there is one.
content
Logo, words and indicator, stacked and centred. Full width on purpose: a bar that is wider than the screen it is on has to be clamped by something, and this is the something.
logo
The mark, at the size size asks for.
halo
The ring that leaves the logo when halo is set.
title
No description in the source yet.
description
No description in the source yet.
indicator
Wrapper for whichever indicator is running; sets its colour.
track
The bar's groove. Never wider than the screen it is on.
fill
The filled part of the bar.
message
The line under the indicator — "Loading your workspace".
percent
No description in the source yet.
actions
No description in the source yet.
footer
Pinned to the bottom edge: a version, a byline, a legal line.

Props

open bindable
boolean

Whether the splash is up.

Leave it out and the component runs itself: it starts up and comes down when the page has loaded. Bind it — or pass a splash — and it is yours, auto-hide included.

splash
SplashController

A {@link SplashController}, for a boot sequence. It supplies open, progress and message; any of the three passed as a prop wins.

variant

Defaults to 'background'

SplashScreenVariant

The colour scheme.

background
surface
primary
inverse
glass
image
pattern

Defaults to 'none'

SplashScreenPattern

The decorative layer over it.

none
gradient
aurora
mesh
spotlight
grid
dots
size

Defaults to 'md'

SplashScreenSize

Mark and type size.

sm
md
lg
align

Defaults to 'center'

SplashScreenAlign

Where the block sits.

center
top
bottom
indicator

Defaults to 'spinner'

SplashScreenIndicator

What loops while it is up.

exit

Defaults to 'fade'

SplashScreenExit

How it leaves.

enter

Defaults to 'rise'

SplashScreenEnter

How the content arrives.

title
string

The app's name, under the mark.

description
string

One line under the title. Say what is happening, not "Loading…".

message
string

The line under the indicator. Changing it fades the new one in.

progress
number

How far along, 0–100. Left out, the indicator is indeterminate — which is the honest answer unless you really are counting something.

percent

Defaults to false

boolean

Show the percentage beside the bar. Only with a real progress.

simulate

Defaults to false

boolean

Move an indeterminate bar along on its own, up to about 90%.

A lie, and a useful one: it says the app is working. It stops short of the end and only lands on 100 when the splash is actually done.

halo

Defaults to false

boolean

A ring that pulses out of the logo. Costs nothing, reads as alive.

image
string

Background photograph, for variant="image".

minDuration

Defaults to 600

number

The shortest time it may be on screen once shown, in ms. What stops a 40ms boot from flashing a splash at somebody.

appearAfter

Defaults to 0

number

Wait this long before showing it at all, in ms. An app that becomes ready inside the delay never shows a splash — the best splash screen is the one nobody had to see.

duration

Defaults to 520

number

How long the exit takes, in ms.

autoHide

Defaults to true

boolean | number

When an uncontrolled splash takes itself off: true waits for the page to finish loading, a number is a plain timeout in ms, false leaves it up until something sets open.

lockScroll

Defaults to true

boolean

Stop the page behind it scrolling while it is up.

portalTo
PortalTarget

Where the overlay is moved to. null keeps it where it is.

inline

Defaults to false

boolean

Inside its container instead of over the viewport.

label
string

What a screen reader calls it.

onhide
() => void

The exit has started.

onhidden
() => void

The splash is off screen and out of the DOM.

logo
Snippet

The mark. An <img>, an inline <svg>, anything.

children
Snippet

Extra content between the words and the indicator.

actions
Snippet

Buttons — a "Retry" for a boot that failed, usually.

footer
Snippet

Pinned to the bottom edge: version, byline, legal line.

class
string
classes
SplashScreenClasses

Per-part Tailwind overrides. class still covers the root.