Let's build the planets in our solar system — complete with their relative rotation speed, tilt, and other photorealistic goodies — using only CSS!

As part of this week's Planet Challenge (#cpc-planets #codepenchallenge), I'll be dissecting part of my Solar CSSystem demo I posted in April 2019:

As the great science communicator Carl Sagan once said:

"If you wish to make an apple pie from scratch, you must first invent the universe."

Welp... there's a lot going on in that apple pie, so let's unpack the recipe one step at a time.

All of the data used in this demo was obtained from NASA's Planetary Fact Sheet.


To keep things simple, let's focus on just one planet for now. I'll be use Mars 🔴 in this demo (since my name is Rob Di Marz o after all 🙃), but feel free to use any planet you're vibing with.

  • Find an image of the entire planet's surface. Make sure the left and right sides of the map can connect seamlessly.
  • Set CSS variables for the image and day length (in hours) of the planet. (Note: These values can also be applied directly in CSS or stored as Sass variables, but I am using CSS variables because they're useful when looping through multiple planets later.)

    :root {
      --mars-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/332937/mars.jpg);
      --mars-day: 24.6;
  • Apply the CSS variables to the planet__surface div. The biggest thing to point out here is the animation duration: calc(var(--mars-day)*.1s). This code calculates 10% of the planet's day length and measures the value in seconds instead of hours. In this example, 24.6 becomes 2.46s, which becomes the duration of one full rotation.

    .planet__surface {
      background-size: cover;     
      background-image: var(--mars-image);     
      animation: planetRotate calc(var(--mars-day)*.1s) linear infinite;
  • Animate the planet rotation by moving the background-position's x-value from 0 to -200%. This will move the image so that the next rotation flawlessly begins where the last one's ends.

    @keyframes planetRotate {
      0% {    background-position: 0% center; }
      100% {  background-position: -200% center; }

So far so good! Now let's add some...


  • Add a border-radius to make the planet circular, because it's clearly very unplanet-like so far.
  • Set another CSS variable for the planet's tilt (known in our data source as Obliquity to Orbit).

      --mars-tilt: rotate(25.2deg);
  • Apply the tilt variable to rotate the planet__surface div by the tilt amount (25.2 degrees). I'll also add an additional scale transformation to enlarge the image and overcome size distortion issues related to flat map projection.

      transform: var(--mars-tilt)scale(1.15);
  • Add an axis line to exaggerate and bring more focus to the tilt measurement.

      .card__planet {
        &::before {
          content: '';
          position: absolute;
          height: 200px;
          z-index: -2;
          left: 50%;
          top: 0%;
          border-left: 1px dashed rgba(255,255,255,.5);
          transform: var(--mars-tilt)scale(1.2);

Pretty cool! But it still feels... blah. So let's add some...


  • Set another CSS variable for the planet's primary color. To obtain this value, I used the eyedropper tool in Sketch (or whichever image editting app you choose) to select a color that is most represented in the planet.

        --mars-color: #c07158;
  • Add box-shadows and radial-gradients to the planet__atmosphere div to create the illusion of a sphere and texture of a planet. I am layering multiple properties here to create depth:

    • The Inner Crest (inner left): inset 10px 0px 12px -2px rgba(255,255,255,.2)
    • The Dark Side (inner right): inset -70px 0px 50px 0px black
    • The Atmosphere (outer left): -5px 0px 10px -4px var(--mars-color)
    • Spherical Spotlight (inner middle): radial-gradient(circle at 30% 50%, rgba(255,255,255,.2) 0%, rgba(255,255,255,0);

       box-shadow: inset 10px 0px 12px -2px rgba(255,255,255,.2), 
                   inset -70px 0px 50px 0px black,
                   -5px 0px 10px -4px var(--mars-color);
        background: radial-gradient(circle at 30% 50%, 
                                    rgba(255,255,255,.2) 0%, 
                                    rgba(255,255,255,0) 65%);
  • Color the axis-line with the planet's primary color, because we can.

The Beyond...

The teachings in this blog post can be applied to any of our celestial neighbors. Give it a try with your favorite planet 🤓!

For the sake of brevity in this post, I won't go into the deeper aspects of my Solar CSSystem demo, like:

  • Extending this pattern to all the planets in our solar system
  • Using a Sass mixin to optimize the planet styling process
  • Using Pug to iterate a dynamic HTML structure and data for each planet

Is that something you'd be interested in learning in another blog post? Let me know in the comments below!