# Build your own FiveM loading screen from scratch

> A complete tutorial: create a FiveM loading screen resource with fxmanifest.lua, HTML, CSS and JavaScript — background, logo, progress bar driven by loading events, music with mute, handover greeting — then install and test it.

Source: https://fam-tools.com/guides/fivem-loading-screen-html-tutorial · FAM Tools · published 2026-09-11 · updated 2026-09-11 · about 15 min

**Short answer.** Create a resource folder with `fxmanifest.lua` (declaring `loadscreen 'index.html'`, `loadscreen_cursor 'yes'` and all files under `files`), an `index.html` with a background, logo, progress bar and audio element, a `style.css` using viewport units and `object-fit: cover`, and a `script.js` that listens for `loadProgress` and `onLogLine` messages. Add `ensure` for it, disable any other loading screen and restart.

## Key points

- Four files: `fxmanifest.lua`, `index.html`, `style.css`, `script.js` (plus images, fonts, music).
- Every file must be listed under `files` in the manifest.
- Only one resource may declare a `loadscreen` — disable the framework’s own.
- Drive the bar from `loadProgress` → `loadFraction`.
- Test in a desktop browser first; the game only adds the loading events.

You can build a good loading screen with nothing but a text editor: one manifest, one HTML file, one stylesheet and one script. This tutorial builds a complete one — responsive background, logo, a real progress bar, a status line, music with a mute button and a personal greeting — and explains each part so you can change it afterwards.

## The folder

*resources/[ui]/my_loadscreen/*

```text
my_loadscreen/
├── fxmanifest.lua
├── index.html
├── style.css
├── script.js
├── img/
│   ├── bg.webp
│   └── logo.png
└── music/
    └── theme.mp3
```

## Step 1: fxmanifest.lua

*fxmanifest.lua*

```lua
fx_version 'cerulean'
game 'gta5'

name 'my_loadscreen'
description 'Custom loading screen'

loadscreen 'index.html'
loadscreen_cursor 'yes'

files {
    'index.html',
    'style.css',
    'script.js',
    'img/*',
    'music/*',
}
```

Each directive is explained in [the loading screen manifest guide](https://fam-tools.com/guides/fxmanifest-loadscreen).

## Step 2: index.html

*index.html*

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <img class="bg" src="img/bg.webp" alt="">
  <div class="shade"></div>

  <main class="safe">
    <img class="logo" src="img/logo.png" alt="My City">
    <p class="welcome" id="welcome">Welcome to My City</p>

    <div class="bar"><div class="fill" id="fill"></div></div>
    <p class="status" id="status">Connecting…</p>
  </main>

  <button class="mute" id="mute" aria-label="Mute music">🔊</button>
  <audio id="music" src="music/theme.mp3" autoplay loop></audio>

  <script src="script.js"></script>
</body>
</html>
```

## Step 3: style.css

*style.css*

```css
html, body { margin: 0; height: 100%; overflow: hidden; background: #05070d; font-family: system-ui, sans-serif; color: #fff; }

.bg { position: fixed; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.shade { position: fixed; inset: 0; background: linear-gradient(180deg, rgba(5,7,13,.15), rgba(5,7,13,.8)); }

.safe {
  position: fixed; inset: 0; margin: auto;
  width: min(100vw, calc(100vh * 16 / 9)); aspect-ratio: 16 / 9;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  gap: 1.6vh; padding-bottom: 9vh; box-sizing: border-box;
}
.logo { width: clamp(180px, 20vw, 560px); height: auto; margin-bottom: auto; margin-top: 18vh; }
.welcome { font-size: clamp(16px, 1.3vw, 28px); opacity: .9; margin: 0; }
.bar { width: min(560px, 60vw); height: 6px; border-radius: 99px; background: rgba(255,255,255,.15); overflow: hidden; }
.fill { height: 100%; width: 0; background: #5b6af0; transition: width .4s ease; }
.status { font-size: clamp(12px, .85vw, 18px); opacity: .7; margin: 0; }

.mute { position: fixed; right: 2vw; bottom: 2vw; width: 44px; height: 44px; border-radius: 12px; border: 1px solid rgba(255,255,255,.2); background: rgba(0,0,0,.4); color: #fff; font-size: 18px; cursor: pointer; }
```

Viewport units and a 16:9 safe area keep this correct on every resolution — the reasoning is in [responsive loading screens](https://fam-tools.com/guides/fivem-loading-screen-resolution-responsive).

## Step 4: script.js

*script.js*

```javascript
const fill = document.getElementById('fill');
const status = document.getElementById('status');
const music = document.getElementById('music');
const mute = document.getElementById('mute');
let shown = 0;

// Greeting from the server, if it sends one (deferrals.handover)
const hand = window.nuiHandoverData || {};
if (hand.name) document.getElementById('welcome').textContent = `Welcome, ${hand.name}`;

// Loading events from FiveM
window.addEventListener('message', (e) => {
  const d = e.data || {};
  if (d.eventName === 'loadProgress') {
    shown = Math.max(shown, Math.min(1, d.loadFraction));
    fill.style.width = (shown * 100).toFixed(1) + '%';
  }
  if (d.eventName === 'onLogLine') status.textContent = d.message;
});

// Music: gentle volume, remembered mute
music.volume = 0.3;
music.muted = localStorage.getItem('muted') === '1';
mute.textContent = music.muted ? '🔇' : '🔊';
mute.addEventListener('click', () => {
  music.muted = !music.muted;
  localStorage.setItem('muted', music.muted ? '1' : '0');
  mute.textContent = music.muted ? '🔇' : '🔊';
});
```

## Step 5: install and test

1. Open `index.html` in Chrome or Edge and check the layout at a few window sizes.
2. Put the folder in `resources/` and add `ensure my_loadscreen` to server.cfg.
3. Disable any other loading screen resource (`qb-loadingscreen`, `esx_loadingscreen` or similar) — only one may declare `loadscreen`.
4. Restart the server and connect.

> **If you see the default screen** — Two loading screens are enabled, a file is missing from `files`, or the folder is nested one level too deep. Work through [loading screen not showing](https://fam-tools.com/guides/loading-screen-not-showing).

## Where to take it next

- A video background instead of an image — [video backgrounds](https://fam-tools.com/guides/fivem-loading-screen-video-background).
- Rules, staff and a tips carousel — [rules and info](https://fam-tools.com/guides/fivem-loading-screen-rules-and-info).
- Keep it up until your spawn is ready — [manual shutdown](https://fam-tools.com/guides/fivem-loadscreen-manual-shutdown).
- A component-based build with React — [React and Vite loading screens](https://fam-tools.com/guides/fivem-loading-screen-react-vite).

## Questions

### How do I make my own FiveM loading screen?

Create a resource with a manifest that declares `loadscreen 'index.html'` and lists every file under `files`, write the HTML, CSS and JavaScript, then `ensure` it and disable any other loading screen.

### Do I need to know JavaScript?

Only a little: the script in this tutorial is enough for a progress bar, status line and music. Everything else is HTML and CSS.

### Why does my loading screen show but images are missing?

The image files are not listed under `files` in the manifest, so the client never downloads them.

### Can I test the loading screen without FiveM?

Yes, open `index.html` in a desktop browser. Only the loading events need the game; you can fake them from the console.

## Related

- https://fam-tools.com/guides/fxmanifest-loadscreen
- https://fam-tools.com/guides/fivem-loading-screen-progress-bar
- https://fam-tools.com/guides/fivem-loading-screen-resolution-responsive
- https://fam-tools.com/guides/loading-screen-not-showing
