The problem: Elements with x-cloak still flash visible for a split second on page load before Alpine hides them — dropdown menus, modals, and tabs briefly show their "wrong" state before snapping to the correct one.
Environment: Laravel 11, Alpine.js 3.x (Livewire-bundled), Vite 5, Tailwind CSS 3, Blade layouts.
The Error
// No console error — this is a pure visual/CSS timing bug.
// What you actually see: a mobile nav menu, modal, or
// x-show="false" block rendering open for ~100-300ms
// on first paint, then disappearing once Alpine boots.
Why It Happens
x-cloak doesn't do anything by itself — it's just a plain HTML attribute. Alpine only removes it once Alpine finishes initializing and evaluates the element's directives. The actual "hide it until Alpine is ready" behavior has to come from a CSS rule you define yourself:
[x-cloak] { display: none !important; }
If that CSS rule is missing, or if it's defined in a stylesheet that loads after the browser has already painted the page, the element renders normally first — x-cloak attribute or not — and only gets hidden retroactively once your CSS finally applies. In Laravel + Vite setups this usually happens because Tailwind's compiled CSS is loaded via @vite in the <head> but something (a large blocking script, or Livewire's own asset injection) delays first paint just enough for the flash to become visible, or because the [x-cloak] rule was never added to app.css at all — Tailwind doesn't ship it by default.
The Fix
First, confirm the base rule actually exists in your compiled CSS. Add it directly to your main stylesheet, not inside a component file that might get purged:
/* resources/css/app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
[x-cloak] {
display: none !important;
}
Second, make sure this stylesheet loads in <head> before any content renders — via @vite, not appended later in the body:
<head>
<meta charset="utf-8">
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
</head>
Third, if you still see a flash specifically on Livewire-rendered components (not on static Blade), it's because Livewire re-inserts markup during its initial page load hydration. Pair x-cloak with an explicit initial state in x-data so there's nothing ambiguous for Alpine to resolve:
<div x-data="{ open: false }" x-cloak>
<div x-show="open">...</div>
</div>
Avoid inline <style> tags scattered per-component for the [x-cloak] rule — if it's not in the main stylesheet loaded first, you'll get the flash on whichever page happened to load that component's styles late.
Step-by-Step
- Open
resources/css/app.cssand confirm[x-cloak] { display: none !important; }exists near the top, after the@tailwinddirectives. - If it's missing, add it and rebuild with
npm run build(or restartnpm run dev). - Check your main Blade layout — confirm
@vite([...])for your CSS is in<head>, not deferred to the bottom of<body>. - Hard-refresh with cache disabled and watch the affected element on load.
- If the flash persists only on Livewire components, add an explicit boolean default in
x-data(e.g.{ open: false }) rather than leaving it undefined. - Throttle your network in DevTools (Slow 3G) to make any remaining flash easier to catch and confirm it's gone.
x-cloak inside a component loaded via wire:navigate (Livewire's SPA-style navigation), the flash can reappear on subsequent page transitions even after fixing the initial load, because wire:navigate swaps body content without a full page reload. Confirm your [x-cloak] stylesheet is loaded globally (not per-page) so it persists across navigations.Still seeing a flash after all this? Reach out on the Contact page with a screen recording and I'll take a look.