ACUL: blank white screen between screens (login-id → login-password) in Safari and Firefox, but not in Chrome/Edge

Hi everyone,

We’re building our login experience with Advanced Customizations for Universal Login (ACUL) using React (@auth0/auth0-acul-react), with the identifier-first flow (login-id → login-password). We’re seeing a visual issue that
only shows up in some browsers, and we’d like to know if there’s a recommended approach.

The problem

When a user submits their email on login-id and is redirected to login-password, Safari and Firefox show a blank white page for a moment before the new screen renders. In Chrome and Edge the transition is seamless.

What we think is happening

So the white screen is really the time between “new document committed” and “our JS has rendered something”. Chrome just hides it.

What we’ve already done on our side

We measured this locally with a production build served the same way as head_tags (CSS link + module scripts in , empty body, assets fully cached):

  • Before: ~360 ms between DOM ready and first contentful paint.
  • After: ~85 ms.

The biggest win came from our own code. Our screen components were behind React.lazy + Suspense, and React holds back the reveal after a fallback, which added roughly 300 ms of empty page. We now resolve the current screen’s
module before the first render, so the first render never suspends.

Part of what’s left is web font loading. We still see a short flash in Safari/Firefox, and we can’t remove it completely from the client side, because the document always starts empty.

safari-white-flash

Questions

  1. if we have any proper methods that we can solve the flash issue between login-id and login-password on safari and firefox, and is there a supported way to put initial content in the HTML of an ACUL screen? For example a static shell or skeleton in the , so the first paint isn’t blank while the JS boots.
  2. Inline styles in head_tags: the content field seems to be limited to 250 characters. Is there a way to include more critical CSS inline (e.g. a layout skeleton drawn with CSS), or is this limit fixed?
  3. Transitions between screens: is there any way to move between login-id and login-password without a full document navigation (e.g. client-side transitions in the ACUL SDK), or is a full reload always required by design?
  4. Is this a known issue? Is there any guidance or best practice from Auth0 for avoiding the white flash between ACUL screens in Safari/Firefox?
  5. Any plans to support things like cross-document View Transitions ( Cross-document view transitions for multi-page applications  |  View Transitions  |  Chrome for Developers ) or render-blocking hints (e.g. ) via
    head_tags, which could help in supporting browsers?

Environment

  • @auth0/auth0-acul-react v1.6.0, React 19, Vite build
  • Reproduced on Safari (macOS) and Firefox; not reproducible on Chrome or Edge

Happy to share a minimal reproduction if that helps. Thanks!

Hi @robert.huo

You are asking how to remove the white flash that appears in Safari and Firefox when a user moves from the login-id screen to the login-password screen in an Advanced Customizations for Universal Login (ACUL) flow built with @auth0/auth0-acul-react, and whether Auth0 supports inline initial content, longer inline CSS, client-side screen transitions, or newer rendering features to prevent it.

[Summary]

  • In standard ACUL, Auth0 serves an empty <body></body>.

  • Chrome and Chromium-based Edge use Paint Holding on same-origin navigations to retain the previous page’s rendered pixels until the new page’s first paint, masking the gap.

  • Safari and Firefox do not universally hold the previous paint during same-origin multi-page navigations when an empty document is committed, so they paint the blank <body> immediately while JavaScript executes.

Can initial content be placed in the HTML of an ACUL screen (e.g., a static shell or skeleton)?**

Yes, via Custom Page Templates with ACUL.

By default, ACUL serves an empty body. However, Auth0 supports combining Custom Page Templates with ACUL on a per-screen basis using the use_page_template setting.

How it works

In screen rendering settings:

  • Set "use_page_template": true via the Management API (PATCH /api/v2/prompts/{prompt}/screen/{screen}/rendering) or Auth0 CLI/Dashboard.

  • In a Page Template:

    • {%- auth0:head -%} is replaced by your ACUL head_tags and window.universal_login_context.

    • {%- auth0:widget -%} is replaced by an empty string (since ACUL handles the UI).

    • Crucially, any HTML markup placed in the <body> around {%- auth0:widget -%} is served directly in the initial document response.

Implementation pattern

You can define a Custom Page Template in your tenant containing your skeleton/shell:

<!DOCTYPE html>
<html lang="{{locale}}">
  <head>
    {%- auth0:head -%}
  </head>
  <body class="login-bg">
    <!-- Static shell rendered immediately before JavaScript executes -->
    <div id="root">
      <div class="auth-card-skeleton">
        <div class="logo-placeholder"></div>
        <div class="skeleton-input"></div>
        <div class="skeleton-button"></div>
      </div>
    </div>
    {%- auth0:widget -%}
  </body>
</html>

When React mounts into <div id="root">, it replaces or hydrates over the skeleton. In Safari and Firefox, the browser paints this shell on the initial paint, eliminating the stark white blank screen.


Inline styles in head_tags & the 250-character limit

  • The 250-character limit on head_tags[].content is fixed in the API schema (maxLength: 250). Exceeding it returns an invalid_body / 400 Bad Request error. It is designed only for small snippets, page titles, or context variables.

  • Workaround with Custom Page Templates:
    When using use_page_template: true, <style> tags placed directly in the Custom Page Template’s <head> or inline style="..." attributes on the skeleton elements are not subject to the 250-character head_tags limit.

  • Workaround with external stylesheets:
    A <link rel="stylesheet" href="..."> in head_tags pointing to your critical CSS bundle is treated as render-blocking by browsers. If the stylesheet is cached with immutable headers (or served over HTTP/2 / HTTP/3 from the same CDN), it will apply synchronously prior to first paint.


Transitions between screens: can a full document reload be avoided?

  • No. Full document navigation is required by design.

  • Auth0 Universal Login is fundamentally a server-orchestrated state machine:

    • Submitting the email on login-id performs an HTTP POST (application/x-www-form-urlencoded) back to Auth0.

    • The Auth0 backend processes the identifier, verifies security policies (e.g., Attack Protection, CAPTCHA, Home Realm Discovery), generates updated state cookies/tokens, and responds with a 302 redirect or a new document for login-password.

    • Each screen receives its own specific, server-hydrated window.universal_login_context.

  • The ACUL SDK does not support Single-Page App (SPA) client-side routing across prompt steps because the session state transitions must remain anchored to the server.


Is this a known issue, and what are the best practices?**

Yes, this is a known challenge with client-side rendered (CSR) multi-page applications on rendering engines that lack Chromium’s Paint Holding.

Auth0-recommended practices to mitigate this include:

  1. Enable Custom Page Templates (use_page_template: true) to deliver a static skeleton or branded background container directly in the initial HTML payload.

  2. Aggressive asset caching & prefetching:

    • Ensure your bundled CSS and JS entry points use immutable cache headers (Cache-Control: public, max-age=31536000, immutable).

    • On the login-id screen, preload the assets required by login-password:

<!-- In your login-id head_tags or dynamically inserted by your app -->
<link rel="modulepreload" href="https://your-cdn.com/assets/login-password.[hash].js">

  1. Prevent font-swapping flash (FOIT / FOUT):

    • Use <link rel="preload" as="font" type="font/woff2" crossorigin href="..."> in head_tags.

    • Apply font-display: optional or font-display: swap in your CSS so text rendering isn’t blocked on Safari/Firefox.

  2. Pre-render screen components:

    • Avoiding runtime React.lazy waterfalls on the initial render (as you have already done) is critical.

Support for cross-document View Transitions and render-blocking hints

  • Cross-Document View Transitions (@view-transition):

    • You do not need Auth0-specific platform support to use CSS-based MPA view transitions.

    • You can include the following in your CSS (loaded via head_tags or in your Page Template):

@view-transition {
navigation: auto;
}

  • In browsers supporting cross-document view transitions (Chrome/Edge currently, with Safari and Firefox progressing on support), the browser automatically captures a snapshot of the outgoing page and cross-fades into the incoming page.

  • Render-blocking hints (<link rel="expect" blocking="render">):

    • head_tags allows up to 10 arbitrary attributes per tag. You can specify attributes such as rel="expect", href="#root", and blocking="render". Note that support for rel="expect" is primarily Chromium-based and does not yet solve the behavior in Firefox/Safari.
  • Auth0 Roadmap:
    Because Universal Login remains an MPA architecture, Auth0’s recommended mechanism for controlling initial paint remains the Page Template integration (use_page_template).

Kind Regards,
Nik