apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
SplashScreen
Variant↳ other

Feedback

SplashScreen

Imperative full-screen splash. Call

SplashScreen

The canonical first-paint surface — but you don't render it as JSX. Instead, mount the singleton <SplashProvider /> once at your app root (next to <ThemeProvider />) and call the imperative splash.show({ ... }) facade from anywhere — a click handler, a fetch interceptor, a service worker, an async action. This mirrors the Toaster + toast() pattern used elsewhere in the design system: the provider owns the portal and the lifecycle; consumers reach for a stateless function.

tsx
// app/layout.tsx — mount once
import { ThemeProvider, SplashProvider } from '@apx-ui/ds';

export default function RootLayout({ children }) {
  return (
    <ThemeProvider>
      <SplashProvider /> {/* portal-owned, picks up theme via the surrounding provider */}
      {children}
    </ThemeProvider>
  );
}
// app/layout.tsx — mount once
import { ThemeProvider, SplashProvider } from '@apx-ui/ds';

export default function RootLayout({ children }) {
  return (
    <ThemeProvider>
      <SplashProvider /> {/* portal-owned, picks up theme via the surrounding provider */}
      {children}
    </ThemeProvider>
  );
}
tsx
// anywhere else — just call `splash`
import { splash } from '@apx-ui/ds';

splash.show({
  variant: 'pulse',
  logo: <MyLogo />,
  title: 'Syncing…',
  showSpinner: true,
  timeout: 3000,
});

// or the variant shortcuts
splash.gradient({ logo: <MyLogo />, title: 'Welcome' });
splash.pulse({ title: 'Connecting' });
splash.particles({ color: 'info' });

// driving a progress bar from async work
const id = splash.show({ showProgress: true, progress: 0 });
for await (const c of upload()) splash.update(id, { progress: c.percent });
splash.hide(id);
// anywhere else — just call `splash`
import { splash } from '@apx-ui/ds';

splash.show({
  variant: 'pulse',
  logo: <MyLogo />,
  title: 'Syncing…',
  showSpinner: true,
  timeout: 3000,
});

// or the variant shortcuts
splash.gradient({ logo: <MyLogo />, title: 'Welcome' });
splash.pulse({ title: 'Connecting' });
splash.particles({ color: 'info' });

// driving a progress bar from async work
const id = splash.show({ showProgress: true, progress: 0 });
for await (const c of upload()) splash.update(id, { progress: c.percent });
splash.hide(id);

Five variants — one click each

Loading preview…
Overview.tsx

Why imperative

The splash is a page-level overlay, conceptually outside the component tree that triggered it. A button in a settings page that wants to show a "Saving…" splash shouldn't have to hoist state to the app shell, render a <SplashScreen /> somewhere, and thread open through props or context. The same is true for non-React code paths (fetch middlewares, error handlers, route loaders).

Instead, the provider lives at the root, owns the portal + lifecycle once, and any caller gets a one-liner:

ts
splash.show({ /* … */ });   // open
splash.update(id, patch);   // patch in place (drive a progress bar)
splash.hide(id);            // close
splash.show({ /* … */ });   // open
splash.update(id, patch);   // patch in place (drive a progress bar)
splash.hide(id);            // close

If you really need a declarative form (inline embeds, Storybook examples), the underlying <SplashScreen /> primitive is still exported — see Declarative form.

The imperative API

CallPurpose
splash(options)Show a splash. Equivalent to splash.show(options).
splash.show(options)Show / replace the active splash. Returns the resolved id.
splash.fade(options)Variant shortcut for { variant: 'fade', ... }.
splash.pulse(options)Variant shortcut for { variant: 'pulse', ... }.
splash.gradient(options)Variant shortcut for { variant: 'gradient', ... }.
splash.particles(options)Variant shortcut for { variant: 'particles', ... }.
splash.wave(options)Variant shortcut for { variant: 'wave', ... }.
splash.update(id, patch)Patch the active splash in place. No-op if the id isn't active.
splash.hide(id?)Dismiss the splash. Pass an id to scope; omit to dismiss whatever's up.
splash.isActive(id?)true if any (or a specific) splash is currently visible.

Calling splash.show({ id }) with the same id updates the existing record in place — the splash stays mounted and its entrance animation does not restart. That's how you drive a progress bar without flicker.

Four-step async workflow with splash.update()

Loading preview…
ImperativeApi.tsx

Variants

VariantLook & feelWhen to reach for it
fadeClean solid backdrop, logo scales + fades inDefault. Minimal first-paint for utility apps.
pulseThree concentric "radar" rings expanding from logoConnection / sync flows, IoT apps, anything "live".
gradientFlowing animated multi-stop gradient backdropMarketing-grade brand-immersive splashes.
particlesLogo with eight orbiting + breathing particlesPlayful / consumer apps, onboarding intros.
waveTwo parallaxing wave bands at the bottomTravel / hospitality / nature-leaning brands.

Every variant uses pure CSS animations registered in the DS Tailwind preset (splash-fade-in, splash-ring-pulse, splash-gradient-shift, splash-orbit, splash-particle-breathe, splash-wave, splash-wave-back).

fade — clean entrance

Loading preview…
FadeVariant.tsx

pulse — concentric radar rings

Loading preview…
PulseVariant.tsx

gradient — flowing brand surface

Loading preview…
GradientVariant.tsx

particles — orbiting accents

Loading preview…
ParticlesVariant.tsx

wave — bottom parallax bands

Loading preview…
WaveVariant.tsx

Custom gradient colors

By default splash.gradient({ color: 'primary' }) paints a theme-derived gradient — the engine's buildPaletteGradient(role) composes a linear-gradient(...) from --sds-palette-{role}-{slot} CSS variables, so the gradient automatically re-tints when the theme switches mode (light / dark), variant (Katana / Tetsu / Origami / runtime override), or per-tenant palette swap.

If you need brand colors outside the theme — a marketing launch, a seasonal hero, a customer-specific subdomain — pass the gradient option to override the default. Five input shapes, pick whichever matches how you carry the colors:

ts
// 1. Stop array → equally-spaced 135° linear gradient.
splash.gradient({ gradient: ['#ff5722', '#ffeb3b', '#4caf50'] });

// 2. Structured object → explicit from / via / to with optional angle.
splash.gradient({
  gradient: { from: '#0ea5e9', via: '#8b5cf6', to: '#ec4899', angle: 120 },
});

// 3. Full CSS string → escape hatch for radial / conic / multi-stop fades.
splash.gradient({ gradient: 'radial-gradient(circle, #fb923c, #1e293b 70%)' });

// 4. Theme-token references → re-tint automatically with light / dark mode.
splash.gradient({
  gradient: {
    from: 'var(--sds-palette-primary-main)',
    to:   'var(--sds-palette-info-main)',
  },
});

// 5. Reshape the default — `BuildPaletteGradientOptions` from `@apx-ui/engine`.
//    Keeps the engine doing the theme derivation; you only override its shape.
splash.gradient({
  color: 'warning',
  gradient: { kind: 'radial', angle: 90, stops: ['active', 'hover', 'active'] },
});
// 1. Stop array → equally-spaced 135° linear gradient.
splash.gradient({ gradient: ['#ff5722', '#ffeb3b', '#4caf50'] });

// 2. Structured object → explicit from / via / to with optional angle.
splash.gradient({
  gradient: { from: '#0ea5e9', via: '#8b5cf6', to: '#ec4899', angle: 120 },
});

// 3. Full CSS string → escape hatch for radial / conic / multi-stop fades.
splash.gradient({ gradient: 'radial-gradient(circle, #fb923c, #1e293b 70%)' });

// 4. Theme-token references → re-tint automatically with light / dark mode.
splash.gradient({
  gradient: {
    from: 'var(--sds-palette-primary-main)',
    to:   'var(--sds-palette-info-main)',
  },
});

// 5. Reshape the default — `BuildPaletteGradientOptions` from `@apx-ui/engine`.
//    Keeps the engine doing the theme derivation; you only override its shape.
splash.gradient({
  color: 'warning',
  gradient: { kind: 'radial', angle: 90, stops: ['active', 'hover', 'active'] },
});

The animated splash-gradient-shift keyframe still applies to whatever the result is, so any custom gradient drifts across the viewport like the default. Animation halts under prefers-reduced-motion.

Power user. The engine generator is exported as buildPaletteGradient(role, options?) from @apx-ui/engine and reused by any DS component that wants a theme-derived gradient surface. The pre-built per-role map is exported as PALETTE_GRADIENTS.

Theme-derived default, custom colors, reshape options

Loading preview…
CustomGradient.tsx

Loading indicator

Pair any variant with either a Spinner or a Progress bar via showSpinner / showProgress shortcuts (or the explicit indicator option). progress (0–100) drives the bar determinately; omit it for the indeterminate sweep.

Spinner — indeterminate, brand-tinted

Loading preview…
WithSpinner.tsx

Progress — live splash.update() at 180 ms intervals

Loading preview…
WithProgress.tsx

Timeout & lifecycle

Pass timeout: ms and the splash auto-dismisses after the delay. onTimeout fires once when the timer elapses; onHide fires on every dismiss path (timeout, click, Escape, manual splash.hide(), replaced by another splash.show()) — that's the place for cleanup that must run regardless of how the splash closed.

By default the imperative API enables:

  • closeOnEscape: true — pressing Escape dismisses (integrates with the engine's escape stack so nested overlays unwind in the right order).
  • closeOnClick: false — set it to true for "tap to continue" intros.

Adjustable timeout slider + lifecycle callbacks

Loading preview…
WithTimeout.tsx

Real fullscreen + Escape / click-to-dismiss

Loading preview…
Fullscreen.tsx

Backdrops & colors

Backdrop and color are independent axes:

  • backdrop chooses the wash behind the content (solid / paper / tinted / transparent).
  • color chooses the role used for the accent (rings, particles, waves, gradient stops, spinner / progress).

solid / paper / tinted / transparent

Loading preview…
Backdrops.tsx

Every palette role on the pulse variant

Loading preview…
Colors.tsx

splash.show(options) reference

OptionTypeDefaultNotes
idstringgeneratedStable identifier. Re-calling with the same id patches in place.
variant'fade' | 'pulse' | 'gradient' | 'particles' | 'wave''fade'Visual style.
color'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral''primary'Semantic palette role driving accents.
backdrop'solid' | 'paper' | 'tinted' | 'transparent''solid'Backdrop treatment. tinted picks up color.
gradientstring | string[] | { from, via?, to, angle? }from presetCustom gradient stops for variant='gradient'. Overrides the per-role preset.
logoReactNode—Brand mark. Sized to a 96×96 slot.
showLogobooleantrue if logo is setHide the logo wrapper entirely.
titleReactNode—Renders below the logo.
subtitleReactNode—Muted subheading below the title.
footerReactNode—Tiny copy below the indicator (version / legal).
indicator'none' | 'spinner' | 'progress''none'Loading indicator style.
showSpinnerboolean—Shortcut for indicator='spinner'.
showProgressboolean—Shortcut for indicator='progress'.
progressnumber (0..100)—Determinate value. Omit for an indeterminate sweep.
loadingLabelstring'Loading'Accessible label for the spinner / progress bar.
timeoutnumber (ms)—Auto-dismiss delay. Omit / set to 0 to disable.
onTimeout(id) => void—Fires once when timeout elapses.
onHide(id) => void—Fires on every dismiss path.
closeOnClickbooleanfalseDismiss on click anywhere on the surface.
closeOnEscapebooleantrueDismiss on Escape (uses the engine's escape stack).
classNamestring—Merged via tailwind-merge. Last-wins.
sxSx—Theme-aware inline style.

<SplashProvider /> reference

PropTypeDefaultNotes
defaultOptionsSplashShowOptions—Shallow-merged onto every splash.show() call. Pin brand color / variant once.
portalContainerHTMLElement | nulldocument.bodyMount target. Scope to an Electron window root, etc.

Declarative form

For inline embeds and rare advanced use cases, <SplashScreen /> is still available as a declarative React primitive:

tsx
import { SplashScreen } from '@apx-ui/ds';

<SplashScreen
  placement="inline"     // skips the fullscreen positioning
  variant="pulse"
  logo={<MyLogo />}
  title="Syncing"
/>
import { SplashScreen } from '@apx-ui/ds';

<SplashScreen
  placement="inline"     // skips the fullscreen positioning
  variant="pulse"
  logo={<MyLogo />}
  title="Syncing"
/>

Prefer this only when you genuinely need to embed the visual surface inside another container (e.g. a documentation tile). For fullscreen splashes always use the imperative splash.show(...) API.

Accessibility

  • Status landmark. The root carries role="status" + aria-busy="true" + aria-live="polite". Screen readers announce the loading state when the splash mounts.
  • Title labels the landmark. When title is set the title's id becomes the splash's aria-labelledby. When only subtitle is set it becomes the aria-describedby. When neither is set the splash falls back to aria-label="Loading" (or your loadingLabel).
  • Decorations are hidden. Gradient layer, wave bands, pulse rings, and particle orbits are all aria-hidden.
  • prefers-reduced-motion. Every animation halts under reduced motion via Tailwind's motion-reduce: variant.
  • Escape stack integration. Provider-driven splashes register with the engine's escape stack when closeOnEscape !== false, so nested overlays (a modal opened from inside the splash) unwind in the right order.

Theming

ts
defineTheme({
  components: {
    SplashScreen: {
      defaultProps: { variant: 'gradient', color: 'primary', timeout: 1500 },
      styleOverrides: {
        title: 'tracking-tight font-display',
        subtitle: 'text-balance',
        stack: 'gap-6',
      },
    },
  },
});
defineTheme({
  components: {
    SplashScreen: {
      defaultProps: { variant: 'gradient', color: 'primary', timeout: 1500 },
      styleOverrides: {
        title: 'tracking-tight font-display',
        subtitle: 'text-balance',
        stack: 'gap-6',
      },
    },
  },
});

Every slot (root, gradient, wave, stack, logoWrap, pulseRing, orbit, particle, title, subtitle, indicator, footer) is themable independently.

Recipes & power-user exports

  • splashScreenRecipes — { root, gradient, wave, stack, logoWrap, pulseRing, orbit, particle, title, subtitle, indicator, footer }
  • SplashStore — the underlying singleton (getState, subscribe, show, update, hide, isActive, __reset). Reach for it to build custom hosts / test harnesses.
  • useSplashState() — React hook that subscribes to the store. The state is { current: SplashScreenItem | null }.
  • <SplashSurface /> — the stateless rendering primitive used by both the host and the declarative <SplashScreen />. Useful for SSR snapshots or alternate host strategies.
  • SPLASH_GRADIENT_BY_COLOR / SPLASH_WAVE_COLOR_CLASS / SPLASH_PULSE_RING_DELAYS_MS / SPLASH_PARTICLE_INNER_ANGLES / SPLASH_PARTICLE_OUTER_ANGLES / SPLASH_PARTICLE_BREATHE_DELAYS_MS — animation / layout constants.

Props

PropTypeDefaultDescription
backdropResponsiveValue<SplashScreenBackdrop>'solid'Backdrop treatment.
classNamestring—Additional class names merged onto the root via `tailwind-merge`.
closeOnClickbooleanfalseWhen `true`, clicking anywhere on the splash dismisses it. Useful for "tap to continue" intros — pairs naturally with `timeout` being omitted / very long.
closeOnEscapebooleandepends on call siteWhen `true`, pressing Escape dismisses the splash. The imperative facade defaults this to `true` (splashes triggered by code are usually dismissible) while the declarative `<SplashScreen>` defaults to `false` (forced first paint).
colorResponsiveValue<SplashScreenColor>'primary'Semantic palette role driving accents (rings / particles / waves / spinner).
defaultOpenbooleantrueUncontrolled initial visibility.
footerReactNode—Slot for arbitrary extra content (legal copy, version string, etc.) rendered below the indicator. Use sparingly — the splash should stay scan-in-300ms minimal.
gradientSplashGradient—Custom gradient for `variant='gradient'`. Overrides the default per-role gradient picked from `SPLASH_GRADIENT_BY_COLOR[color]`. Accepts a full CSS gradient string, an array of stops (135° linear), or a `{ from, via?, to, angle? }` object — see `SplashGradient` for the full shape with examples. Ignored when `variant !== 'gradient'`.
indicatorenum'none'Loading indicator style.
loadingLabelstring—Accessible label for the spinner / progress indicator. Defaults to `'Loading'`. Override to localize or to disambiguate (`'Initializing workspace'`, `'Syncing data'`).
logoReactNode—Brand mark. Accepts any ReactNode — an `<img>`, an inline SVG, a `<MyLogo />` component. The splash renders a `flex` container around it sized by the variant (typically 96–128px across) and applies the variant's entrance animation to the wrapper, not to your logo element, so internal logo animations (e.g. a Lottie file) play independently.
onOpenChange((open: boolean) => void)—Fires whenever the splash's open state changes — manual close, timeout, controlled flip.
onTimeout(() => void)—Fires once when `timeout` elapses (does not fire on manual / controlled close).
openboolean—Controlled visibility. When omitted the splash uses `defaultOpen` (`true`) so the canonical "show on first render, hide after timeout" pattern needs zero state wiring.
placementenum'fullscreen'Render position.
portalbooleantrueMount the (fullscreen) splash inside a Portal so it overlays the entire viewport regardless of where it sits in the React tree. Ignored when `placement='inline'`.
portalContainerHTMLElement | null—Mount target for the portal. Defaults to `document.body`.
progressnumber—Determinate progress value (0–100). Only meaningful when `indicator='progress'` (or `showProgress` is `true`). Omit to render the indeterminate sweep.
showLogobooleantrue when `logo` is passedHide the logo wrapper entirely.
showProgressboolean—Shorthand for `indicator='progress'`. Wins over `indicator` when both are set.
showSpinnerboolean—Shorthand for `indicator='spinner'`. Wins over `indicator` when both are set.
styleCSSProperties—Plain inline style. Merged last so it wins over the recipe-derived style.
subtitleReactNode—Subtitle / tagline. Renders below the title in muted color.
sxSx—Theme-aware inline style object (resolves palette / spacing / radius tokens to CSS vars).
timeoutnumber—Auto-dismiss delay in milliseconds. Set to `0` (or omit when paired with explicit `open` control) to disable the timer. When the timer fires the splash transitions to `open=false` and `onTimeout` / `onOpenChange` fire in that order.
titleReactNode—Title text or node. Renders below the logo in the variant's title slot.
variantResponsiveValue<SplashScreenVariant>'fade'Visual style.