20 CSS Empty States
20 hand-coded empty states — the screens most teams ship last and worst. An empty state is what your app shows when there is nothing to show, and it is the moment a user decides whether the product is broken or just new. Each one here names what happened and gives you somewhere to go next. Scoped under .est-NN, prefers-reduced-motion guarded, framework-agnostic.
Related30 CSS Notifications22 CSS Skeleton Loaders20 CSS Form Validation20 CSS Banners & Alerts20 Tailwind CSS Empty States
Premium icon pickPartner: Nucleo
Affiliate linkNeed icons for the components you just copied?
You just browsed 20 empty states. Nucleo is a library of 44,000+ SVG icons in five families (Core, UI, Sharp, Pixel and Micro Bold), with web, Mac and Windows apps to manage them. The All Icons Bundle is $149 with lifetime updates.
Nucleo. We may earn a commission, at no extra cost to you.
02. First-Time Onboarding State (opens in a new tab)
03. Empty Shopping Cart State (opens in a new tab)
04. Inbox Zero / All Caught Up State (opens in a new tab)
05. Empty Data Table / No Rows State (opens in a new tab)
colspan cell carries the message, and ghost rows fade in behind it so the grid still reads as a table rather than a hole. New invoice adds a genuine draft row and updates the footer count, so the demo covers the transition out of empty as well as the state itself.06. Filtered List No Matches State (opens in a new tab)
:has() on the form root.07. Empty Dashboard / No Analytics Data (opens in a new tab)
08. 500 Server Error Page Layout (opens in a new tab)
09. Empty Wishlist / Favorites State (opens in a new tab)
:has() switch the panels, ghost product frames sit behind the first card so the grid still reads as a grid, and a row of recently viewed pieces gives the page somewhere to go.10. Empty Notification Center Flyout (opens in a new tab)
:has() lifts it to the panel, the bell, and the page behind it, which blurs and dims as the flyout takes focus. Three radio-driven tabs each carry their own empty state.11. Failed to Load / Connection Error State (opens in a new tab)
12. Offline / No Internet Connection State (opens in a new tab)
13. Permission Denied / Restricted Access State (opens in a new tab)
14. 404 Page Not Found Layout (opens in a new tab)
15. Deleted or Archived Items State (opens in a new tab)
16. Skeleton Loading to Empty State Transition (opens in a new tab)
min-block-size on the slot — so the swap costs zero layout shift, and the shimmer is a single moving background-position rather than an animated box.17. Accessible Empty State (ARIA Live) (opens in a new tab)
role="status" region announces the outcome, focus moves to the results container so a screen reader lands on the message rather than hunting for it, and the recovery suggestions are real buttons that re-run the search. Nothing about the accessibility work is visible — which is the point.18. Pure CSS Illustrated Empty State (opens in a new tab)
border-radius and clip-path. It sits in a real cookbook app — header, collections rail, saved tab .19. Dark Mode Compatible Empty State (opens in a new tab)
:has(), that repaints an entire settings surface by swapping nine tokens. System defers to prefers-color-scheme, so the same markup follows the OS until the user overrides it.20. Inline / Micro Empty State Component (opens in a new tab)
Frequently asked questions
What makes a good empty state?
How do I make an empty state accessible?
role="status", or aria-live="polite" — and write the new text into it when the state changes. Two details make the difference between working and not: the live region must already exist in the DOM before you write to it, because injecting the region and its content together announces nothing; and it must be polite rather than assertive for a routine result count, since assertive interrupts whatever the user is currently hearing. Keep the announcement short and factual — "No results for merino socks" — rather than reading out the whole empty state including the illustration alt text and the button label.How do I show an empty state inside a data table?
<tbody> with one cell spanning every column, using colspan set to the column count. That keeps the table structurally valid, which matters more than it looks: a <div> dropped inside a <tbody> is invalid HTML, and browsers will hoist it out of the table entirely, so your carefully centred empty state ends up rendering above or below the table rather than inside it. Centring the content is then a matter of giving that one cell some vertical padding and a text-align. Two things worth getting right: keep the table header visible so the user can still see what columns exist and what they were filtering on, and if the emptiness is caused by a filter rather than by genuinely having no data, say so and offer to clear it — those are different situations and deserve different copy.Should an empty state use an illustration?
aria-hidden="true" so screen readers skip straight to the text — otherwise assistive technology announces a description of a picture before it reaches the sentence that actually explains what happened.How do I transition from a loading skeleton to an empty state without layout shift?
min-height sized to the taller of the two states, so the box is stable and only its contents change. From there the swap can be a cross-fade rather than a hard cut, which reads as calmer: @starting-style with transition-behavior: allow-discrete animates an element that is entering or leaving display: none, which plain transitions cannot do. Both are progressive enhancement — without them the states swap instantly, which is still correct, just less smooth.What is the difference between an empty state, an error state, and a loading state?
How do I handle offline and connection-loss states?
online and offline events on window and reflect the current value of navigator.onLine in your UI. The important caveat is that navigator.onLine only tells you whether the device has a network interface up — it says nothing about whether your server is reachable, so a captive portal, a DNS failure or a downed API all report as online. Treat it as a hint rather than a truth: use it to show a banner immediately when connectivity drops, but rely on actual request failures to drive the per-component error states. Two things make the pattern feel considered rather than alarming: keep any content you already have on screen rather than replacing it with an offline screen, and queue the user's pending actions so they are not lost, telling them plainly that the actions will send when the connection returns.How do these port to React, Vue, Svelte and Tailwind?
.est-NN selector and custom property is framework-agnostic, and six of the twenty demos need no JavaScript at all. Where a demo does use script, it drives the trigger rather than the styling: filtering a list to zero, failing a fetch, clearing a cart. In a framework that logic is already yours, so what you take from these demos is the markup and the CSS. The one pattern worth copying carefully is the live region: in React, render the region unconditionally and change only its text content, because conditionally mounting the whole region on empty means the announcement fires as the node is created and screen readers frequently miss it. Vue and Svelte have the same trap. For Tailwind the states map to conditional classes as usual, and the min-height that prevents layout shift between the skeleton and the empty state is worth keeping as an explicit utility rather than leaving to chance.Related collections
30 CSS Badges
30 hand-coded CSS badges for status indicators, notifications, membership tiers, live-data displays, and SEO / DevOps / financial dashboards — upload progress, typing indicator, transit line status, Core Web Vitals, ECG heartbeat, CI/CD build pipeline, countdown ring, live price ticker, keycap shortcut, wax seal, conference lanyard, and holographic collectibles. Copy-paste HTML and CSS, WCAG 2.2 accessible, MIT licensed.
20 CSS Banners & Alerts
20 hand-coded CSS banner and alert bars for e-commerce storefronts, SaaS dashboards, marketing sites, and compliance-bound products. Covers GDPR cookie consent with a real preferences panel, promo bars with countdown timers, free-shipping threshold progress, app-install smart banners, maintenance and incident status bars, newsletter signup bars, age verification gates, limited-stock urgency banners, live event announcements, browser upgrade notices, geo and currency switchers, sticky top announcement bars, bottom cookie bars, inline form validation alerts, icon-aligned alert banners, left-border accent alerts, diagonal stripe promos, full-width hero banners, animated gradient border alerts, and a text-wrapping laboratory that shows five strategies for the unbreakable string that breaks more banners than anything else. Every bar is scoped under a .ba-NN prefix for no-collision pasting, guards prefers-reduced-motion, carries the correct aria-live and role semantics, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.
25 CSS Blockquotes
25 CSS blockquotes for article pull quotes, testimonial cards, docs callout admonitions, statement banners, and social-post embeds. Covers giant decorative quotation marks, minimalist hairline and classic left borders, animated gradient and glowing neon borders, glassmorphism and aurora backgrounds, brutalist cards, speech bubbles, thick accent rails, highlighted mark text, newspaper multi-column layouts, an expandable read-more disclosure, a scroll-snap testimonial slider, and one-click copy.



















