Empty-state panel
A centered empty-state block with an icon, a short explanation and primary and secondary actions for blank lists.
Tailwind CSSFeedback and empty statesempty-statepanelonboardingux
Live preview
Code
<section class="mx-auto max-w-xl rounded-2xl border-2 border-dashed border-slate-300 px-6 py-16 text-center">
<span class="mx-auto flex size-14 items-center justify-center rounded-full bg-indigo-50 text-indigo-600">
<svg class="size-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Z"/>
</svg>
</span>
<h2 class="mt-5 text-balance text-lg font-semibold text-slate-900">No projects yet</h2>
<p class="mx-auto mt-2 max-w-sm text-sm text-slate-600">
Projects group your files, members and reports in one place. Create the first one to get going.
</p>
<div class="mt-6 flex flex-col items-center justify-center gap-3 sm:flex-row">
<a href="/projects/new"
class="inline-flex items-center rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
Create project
</a>
<a href="/docs/projects" class="text-sm font-medium text-slate-600 underline-offset-4 hover:underline">
Read the guide
</a>
</div>
</section>
How it works
What it solves
A blank table or list looks broken unless the page explains why it is empty and what to do next. An empty-state panel turns that dead end into a first step, and it should be reusable across lists with different wording.
How it works
The panel is a dashed-border container so it reads as a placeholder rather than content. An icon tile at the top gives the eye an anchor, followed by a heading, one supporting sentence limited with max-w-sm, and two actions: a filled primary button and a quieter text link for documentation. The wrapper uses role="status" only when the state appears dynamically after a search or filter, not on initial render. Spacing comes from space-y-* and py-16, which keeps the block tall enough to feel intentional inside a card but compact inside a small column. Colour tokens stay neutral so the primary action is the only strong colour.
Adapting it
Create variants by changing copy: "No results" with a clear-filters button, "Nothing here yet" with a create button, or "No access" with a request link. Replace the icon path to match the object type. Use text-balance on the heading for long translations.
Pitfalls
- Do not show the panel while data is still loading; render a skeleton first.
- Make the primary action do the thing the empty list is missing.
- Avoid apologetic copy; say what the area is for.
How to use it
- Paste
empty-state.htmlwhere your list or table would render when it has no rows. - Show it only when the count is zero and loading has finished.
- Edit the heading, sentence and the two links.
- Add
role="status"if you toggle it in after a filter change.
Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.