5 Pure CSS2 with JSPublished 2026-10-05Free · no sign-up
Seven empty states that help instead of shrugging: a first-run screen with one clear action, a search with no results and ways forward, an inbox that celebrates being empty, a failed load with Try again, a permission screen with Request access, an empty table body and a dashboard checklist for new accounts.
The screen before anything exists: a simple illustration, a heading that says what will appear here, one sentence on why it matters and one clear action. An empty state is the first thing new users see, so it should teach and invite, not just say Nothing here.
View code
<section class="es1" aria-labelledby="es1h"><div class="art" aria-hidden="true"><span></span><span></span><span></span></div><h3 id="es1h">Your projects will live here</h3><p>Create a project to start collecting designs, notes and feedback in one place.</p><a href="#new">Create your first project</a></section>
When a search finds nothing, repeat what was searched, suggest what to try, and give one-click ways out: clear filters, or popular searches. A search that ends in a blank page loses the visitor; one that offers a next step keeps them.
View code
<section class="es2" aria-live="polite"><h3>No results for “lineen shirt”</h3><p>Check the spelling, or try fewer filters.</p><button>Clear all filters</button><p class="pop">Popular: <a href="#linen">linen shirt</a> <a href="#tote">tote bag</a> <a href="#mug">mug</a></p></section>
An empty inbox is good news, so this empty state celebrates it with a tick, a friendly heading and a quiet secondary link. Positive empty states reward finishing a task instead of making the screen feel broken.
View code
<section class="es3"><span class="ok" aria-hidden="true">✓</span><h3>You are all caught up</h3><p>No new messages. Enjoy the quiet.</p><a href="#archive">View archived messages</a></section>
When content fails to load, explain it in plain words, reassure that nothing was lost and offer Try again. The message is an alert so it is announced, and the retry button shows a busy state while it works.
View code
<section class="es4" role="alert"><span class="ic" aria-hidden="true">!</span><h3>We could not load your orders</h3><p>Your data is safe. Check your connection and try again.</p><button>Try again</button></section>
var b = document.querySelector('.es4 button');
b.onclick = function () { b.setAttribute('aria-busy', 'true'); b.textContent = 'Trying…'; setTimeout(function () { b.removeAttribute('aria-busy'); b.textContent = 'Try again'; }, 1500); };
05. No Access Yet
Empty statePermissions
When someone lacks permission, say who can grant it and give a button to ask, rather than a dead end. The page names the owner and what will happen when the request is sent, and confirms with a polite status message.
View code
<section class="es5"><span class="ic" aria-hidden="true">🔒</span><h3>You need access to this folder</h3><p>Ask <b>Ana Ribeiro</b>, the folder owner, to add you.</p><button>Request access</button><p class="st" role="status"></p></section>
var b = document.querySelector('.es5 button'), st = document.querySelector('.es5 .st');
b.onclick = function () { b.disabled = true; b.textContent = 'Request sent'; st.textContent = 'Ana will get an email. You will be notified when she replies.'; };
06. Empty Table Body
Empty stateTable
A table with no rows keeps its headers, so people see what will appear, and shows the empty message in a single cell spanning all columns. Keeping the table structure avoids a jarring layout change when the first row arrives.
View code
<div class="es6"><table><caption>Invoices</caption><thead><tr><th scope="col">Number</th><th scope="col">Client</th><th scope="col">Amount</th></tr></thead><tbody><tr><td colspan="3"><b>No invoices yet</b><span>Invoices you send will appear here.</span><a href="#new-invoice">Create an invoice</a></td></tr></tbody></table></div>
A new dashboard with no data shows a short getting-started checklist instead of empty charts, with progress in words (1 of 3 done). Each unfinished step is a link to where it is done, and finished steps are marked done in text, not only with a tick.
View code
<section class="es7" aria-labelledby="es7h"><h3 id="es7h">Get set up</h3><p class="pr">1 of 3 done</p><ol><li class="d">Create your shop <span class="sr">(done)</span></li><li><a href="#products">Add your first product</a></li><li><a href="#payments">Connect payments</a></li></ol></section>
An empty state should explain what this space is for and how to fill it: Your projects will live here, then one clear button. Nothing to show is not enough.
One primary action
Offer the single next step (create, import, clear filters) as the main button. Secondary links can follow, but should not compete.
Different empties, different tones
First use invites, no results suggests, all done celebrates and errors reassure. Write each one in the voice that fits the moment.
Keep the structure
Inside a table or list, keep the headers and show the empty message in place, so the layout does not jump when the first item arrives.
Frequently asked questions
Do empty states need illustrations?
No. A small icon or none is fine; the words and the action matter most.
Should errors be announced?
Yes. Use role="alert" for load failures so screen reader users know something went wrong.
What about loading versus empty?
Show a loading state until you know the list is empty, so the empty message never flashes up first.