Empty Ghost
A copy-paste empty states component in pure HTML & CSS. Zero dependencies, framework-agnostic, MIT-licensed.
Empty StatesHTMLCSSany framework
Nothing here
Copy into your project
HTML
<!-- Empty Ghost -->
<div class="nuda-empty-ghost" role="status" aria-label="Nothing here">
<div class="nuda-empty-ghost__scene">
<div class="nuda-empty-ghost__shadow"></div>
<div class="nuda-empty-ghost__body">
<span class="nuda-empty-ghost__eye nuda-empty-ghost__eye--l"></span>
<span class="nuda-empty-ghost__eye nuda-empty-ghost__eye--r"></span>
<span class="nuda-empty-ghost__mouth"></span>
</div>
</div>
<p class="nuda-empty-ghost__text">Nothing here</p>
</div>CSS
/* Empty Ghost
Friendly ghost floating with a soft shadow that breathes.
Customize: --empty-ghost-color, --empty-ghost-eye, --empty-ghost-text */
.nuda-empty-ghost {
--empty-ghost-color: #e4ff54;
--empty-ghost-eye: #09090b;
--empty-ghost-text: rgba(255, 255, 255, 0.55);
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 12px;
color: var(--empty-ghost-color);
}
.nuda-empty-ghost__scene {
position: relative;
width: 60px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
}
.nuda-empty-ghost__shadow {
position: absolute;
bottom: 2px;
left: 50%;
width: 42px;
height: 6px;
background: radial-gradient(ellipse at center, rgba(228, 255, 84, 0.25), transparent 70%);
transform: translateX(-50%);
animation: nuda-empty-ghost-shadow 3s ease-in-out infinite;
filter: blur(1px);
}
.nuda-empty-ghost__body {
position: relative;
width: 48px;
height: 60px;
background: currentColor;
border-radius: 24px 24px 6px 6px;
animation: nuda-empty-ghost-float 3s ease-in-out infinite;
box-shadow:
0 0 16px rgba(228, 255, 84, 0.25),
inset 0 -6px 0 rgba(0, 0, 0, 0.1);
}
.nuda-empty-ghost__body::before,
.nuda-empty-ghost__body::after {
content: "";
position: absolute;
bottom: -4px;
width: 14px;
height: 8px;
background: currentColor;
border-radius: 0 0 50% 50%;
}
.nuda-empty-ghost__body::before { left: 2px; }
.nuda-empty-ghost__body::after { right: 2px; }
.nuda-empty-ghost__eye {
position: absolute;
top: 22px;
width: 5px;
height: 7px;
background: var(--empty-ghost-eye);
border-radius: 50%;
}
.nuda-empty-ghost__eye--l { left: 14px; }
.nuda-empty-ghost__eye--r { right: 14px; }
.nuda-empty-ghost__mouth {
position: absolute;
top: 34px;
left: 50%;
transform: translateX(-50%);
width: 8px;
height: 8px;
border: 1.5px solid var(--empty-ghost-eye);
border-top: none;
border-radius: 0 0 50% 50%;
}
.nuda-empty-ghost__text {
font: 500 12px ui-sans-serif, system-ui, sans-serif;
color: var(--empty-ghost-text);
letter-spacing: 0.02em;
margin: 0;
}
@keyframes nuda-empty-ghost-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
@keyframes nuda-empty-ghost-shadow {
0%, 100% { transform: translateX(-50%) scaleX(1); opacity: 0.7; }
50% { transform: translateX(-50%) scaleX(0.7); opacity: 0.4; }
}
@media (prefers-reduced-motion: reduce) {
.nuda-empty-ghost__body,
.nuda-empty-ghost__shadow {
animation: none;
}
}How to use Empty Ghost
Paste the HTML where you need it and the CSS into a global stylesheet (or a <style> tag). Every class is prefixed nuda- so it never collides with Tailwind or your own styles. Tweak the CSS custom properties to match your design system.
Works in React, Vue, Svelte, Astro, Next.js, Nuxt, Laravel Blade, Django, Rails — or a single .html file. No npm install, no build step.