Skip to content
CoDdleCoDoodle home
CSS CharactersBeginnerHTML + CSS

Cozy Ghost

A sleepy little sheet ghost floats above its soft navy shadow with blushy cheeks and the tiniest smile. It brought one mint prop and a sunshine sparkle, because even haunting can be cozy.

← you, in 25 minutes.

what you'll build

Here's the plan

A round-top cream ghost drawn with plain divs and CSS only: chunky navy outline, scalloped bottom bumps, sleepy half-lidded eyes, coral blush, a tiny smile, and rounded side arms. The finished doodle floats gently above a breathing shadow while a sunshine sparkle twinkles near its head and a small mint prop keeps the scene feeling fresh.

You’ll learn

  • Building a CSS character from nested plain divs instead of vector paths
  • Using border-radius and overlapping circles to make a simple ghost silhouette
  • Drawing a tiny face with rounded rectangles, blush ovals, and a bottom-only smile arc
  • Animating centered elements without dropping their translateX(-50%) alignment
Skill level
Beginner
Time
~25 min
Tools
Just a browser
Code type
HTML + CSS
Lines
~185

the steps

Build it, one change at a time

  1. Shape the floating sheet

    Start with the wrapper, a soft oval shadow, and one cream ghost body with a chunky navy outline. The body keeps a simple rounded top now, so the character reads clearly before we add the scalloped hem.

    <div class="ghost-wrap">
      <div class="shadow"></div>
      <div class="ghost"></div>
    </div>
    
    .ghost-wrap {
      position: relative;
      width: 200px;
      height: 180px;
    }
    .shadow {
      position: absolute;
      left: 50%;
      bottom: 8px;
      transform: translateX(-50%);
      width: 104px;
      height: 16px;
      background: #2B3A55;
      border-radius: 50%;
      opacity: 0.13;
    }
    .ghost {
      position: absolute;
      left: 50%;
      top: 16px;
      transform: translateX(-50%);
      width: 128px;
      height: 132px;
      background: #FFFDF7;
      border: 6px solid #2B3A55;
      border-radius: 64px 64px 18px 18px;
    }
    

    a very polite blank ghost sheet has entered the room.

  2. Give it a sleepy face

    Add two half-lidded eyes, tiny blush ovals, and the classic CoDoodle smile arc. The smile is just a little box with the top border removed, which is a handy trick for friendly faces.

        <div class="eye eye-left"></div>
        <div class="eye eye-right"></div>
        <div class="blush blush-left"></div>
        <div class="blush blush-right"></div>
        <div class="smile"></div>
    
    .eye {
      position: absolute;
      top: 50px;
      width: 24px;
      height: 10px;
      background: #2B3A55;
      border-radius: 999px;
      transform-origin: center;
    }
    .eye-left { left: 30px; }
    .eye-right { right: 30px; }
    .blush {
      position: absolute;
      top: 74px;
      width: 18px;
      height: 10px;
      background: #FFB3AD;
      border-radius: 50%;
      opacity: 0.85;
    }
    .blush-left { left: 24px; }
    .blush-right { right: 24px; }
    .smile {
      position: absolute;
      top: 72px;
      left: 50%;
      transform: translateX(-50%);
      width: 22px;
      height: 12px;
      border: 3px solid #2B3A55;
      border-top: none;
      border-radius: 0 0 22px 22px;
    }
    

    sleepy, blushy, and already emotionally unavailable.

  3. Add arms, scallops, and props

    Now the ghost gets rounded arms and four overlapping hem bumps so the bottom turns wavy. The mint prop and sunshine sparkle sit outside the body, which keeps the character simple while the scene gets a little personality.

        <div class="arm arm-left"></div>
        <div class="arm arm-right"></div>
        <div class="hem-bump hem-bump-1"></div>
        <div class="hem-bump hem-bump-2"></div>
        <div class="hem-bump hem-bump-3"></div>
        <div class="hem-bump hem-bump-4"></div>
      </div>
      <div class="mint-prop"></div>
      <div class="sparkle"></div>
    
    .arm {
      position: absolute;
      top: 80px;
      width: 18px;
      height: 28px;
      background: #FFFDF7;
      border: 5px solid #2B3A55;
      border-radius: 50%;
      z-index: -1;
    }
    .arm-left {
      left: -13px;
      transform: rotate(16deg);
    }
    .arm-right {
      right: -13px;
      transform: rotate(-16deg);
    }
    .hem-bump {
      position: absolute;
      bottom: -12px;
      width: 23px;
      height: 12px;
      background: #FFFDF7;
      border: 6px solid #2B3A55;
      border-top: none;
      border-radius: 0 0 50% 50%;
    }
    .hem-bump-1 { left: -6px; }
    .hem-bump-2 { left: 29px; }
    .hem-bump-3 { left: 64px; }
    .hem-bump-4 { right: -6px; }
    .mint-prop {
      position: absolute;
      left: 8px;
      bottom: 10px;
      width: 22px;
      height: 18px;
      background: #5ECBB4;
      border: 4px solid #2B3A55;
      border-radius: 50% 50% 7px 7px;
      transform: rotate(-10deg);
    }
    .sparkle {
      position: absolute;
      top: 30px;
      right: 16px;
      width: 16px;
      height: 16px;
    }
    .sparkle::before,
    .sparkle::after {
      content: "";
      position: absolute;
      background: #FFD84D;
      border-radius: 2px;
    }
    .sparkle::before {
      left: 50%;
      top: 0;
      transform: translateX(-50%);
      width: 4px;
      height: 16px;
    }
    .sparkle::after {
      top: 50%;
      left: 0;
      transform: translateY(-50%);
      width: 16px;
      height: 4px;
    }
    

    now with scallops, sparkle, and one minty little side quest.

  4. Let it float softly

    The final pass adds a slow bob, an inverse shadow scale, a sleepy blink, and a twinkle. Notice how the ghost and shadow keyframes keep translateX(-50%) in every step, so the centered layout never jumps while it moves.

    @media (prefers-reduced-motion: no-preference) {
      .ghost {
        animation: ghost-float 3.8s ease-in-out infinite;
      }
      .shadow {
        animation: ghost-shadow-breathe 3.8s ease-in-out infinite;
      }
      .eye {
        animation: ghost-blink 4.6s ease-in-out infinite;
      }
      .sparkle {
        transform-origin: center;
        animation: ghost-twinkle 2.8s ease-in-out infinite;
      }
    }
    @keyframes ghost-float {
      0%, 100% { transform: translateX(-50%) translateY(0); }
      50% { transform: translateX(-50%) translateY(-8px); }
    }
    @keyframes ghost-shadow-breathe {
      0%, 100% { transform: translateX(-50%) scaleX(1); opacity: 0.13; }
      50% { transform: translateX(-50%) scaleX(0.82); opacity: 0.08; }
    }
    @keyframes ghost-blink {
      0%, 88%, 100% { transform: scaleY(1); }
      92% { transform: scaleY(0.18); }
    }
    @keyframes ghost-twinkle {
      0%, 100% { opacity: 0.45; transform: scale(0.82) rotate(0deg); }
      45% { opacity: 1; transform: scale(1.12) rotate(8deg); }
    }
    

    a sleepy float loop with excellent tiny-haunt manners.

the complete code

Everything, in one place

Skipped straight to the end? Welcome. Copy the whole thing, download it, or open it in the Playground to start remixing.

<div class="ghost-wrap">
  <div class="shadow"></div>
  <div class="ghost">
    <div class="eye eye-left"></div>
    <div class="eye eye-right"></div>
    <div class="blush blush-left"></div>
    <div class="blush blush-right"></div>
    <div class="smile"></div>
    <div class="arm arm-left"></div>
    <div class="arm arm-right"></div>
    <div class="hem-bump hem-bump-1"></div>
    <div class="hem-bump hem-bump-2"></div>
    <div class="hem-bump hem-bump-3"></div>
    <div class="hem-bump hem-bump-4"></div>
  </div>
  <div class="mint-prop"></div>
  <div class="sparkle"></div>
</div>
Open in Playground

tips & gotchas

Mistakes we actually made

I build the ghost body before the hem, because the big silhouette needs to read before the cute details arrive.

I keep the hem bumps the same cream and navy as the body so they merge into one simple sheet shape.

I use short, flat eyes for a sleepy mood; tall round eyes would make the ghost feel much more awake.

I always repeat translateX(-50%) inside centered keyframes, because transforms replace each other instead of stacking automatically.

make it yours

Remix it

  • Change the moodeasy

    Swap the sleepy eyes for round dots, surprised ovals, or tiny happy arcs.

  • Dress the ghosteasy

    Add a small bow, cap, or scarf using one extra div and the same navy outline.

  • More scallopsmedium

    Try five narrower hem bumps and adjust their overlap until the bottom feels wavy but not crowded.

  • Night shiftmedium

    Add a dark rounded backdrop with a moon dot, then lighten the shadow so the cream ghost still pops.

challenge extension

Turn this into a tiny ghost parade: duplicate the body at two smaller sizes, delay each float animation, and give every ghost a different face while keeping the same cream, navy, coral, mint, and sunshine palette.