Tutorial

Part 3 of 4

Build an Interactive Character Builder for Email

Part 3: CSS: making it work

This is part 3 of a 4-part tutorial. New here? Start with part 1.

4. CSS: Let's make it work!

Okay dokey, now the fun part. All the magic happens in the CSS in the head of the email. Without the CSS we wouldn't be able to control the interactive elements, position everything correctly, or add any styles. We also wouldn't be able to only show the interactive elements when supported. There's a lot to cover here!

First, we need to wrap the whole CSS in a media query to target only email clients that support WebKit. I also like to keep my CSS in modular pieces of code, so we'll keep this in its own style declaration.

<style type="text/css">
 @media screen and (-webkit-min-device-pixel-ratio: 0) {
   <!-- ALL YOUR CSS WILL GO HERE -->
 }
</style>

We'll also add the CSS which hide the fallback and shows the interactive code. There is also a fix for Samsung to prevent it from showing the interactive elements and we have added this also. this is the '#MessageViewBody' piece of code.

<style type="text/css">
 @media screen and (-webkit-min-device-pixel-ratio: 0) {
  /* Samsung Mobile Fix */
  #MessageViewBody .fallback, body.MsgBody .fallback { display: block !important; visibility: visible !important; }
  #MessageViewBody .slider, body.MsgBody .slider { display: none !important; visibility: hidden !important; }
  /* Hide fallback and show interactive */
  .fallback { display: none !important; visibility: hidden !important; }
  .slider { display: block !important; visibility: visible !important; }

 }
</style>

Next, let's add everything we need to style the interactive elements. Here I am adding a lot at once. This will be very much up to your own example and the design of your email but the key points I've added for my example are:

  • Defined the main container 'sliders' div width and height. This is a whole interactive section of code.
  • Defined the 'slides-cont' div's sizing a position. I have set the max-width to 960px and the overflow to hidden. This is our 'window' which only shows the centre three slides at any time.
  • Defined the 'slides' container divs sizing a position. Here, I am setting the width to be 166.66% because I want the individual slides to be 320px wide, 320px X 5 slides = The slides container needs to be 1600px. 1600px is 166.66% of 960px, which is the size of our view window.
  • I also set the slides to be position: absolute. This allows me to use z-index to positing the divs on top of one another and set the top 0px to keep them all aligned.
  • Next, the individual slides need to be positioned within their containers. This one is simple as we have 5 slides so each one is positioned using the CSS left property and increasing by increments of 20%.
  • Now we need some controls. This is similar to positioning the slides containers. I've set each to be positioned absolute and added a relevant z-index. Although this time the CSS property top is used to position the three sets of arrows at different heights from the top. One important thing to note here is that you want your controls to have the highest z-index to ensure they are above the content slides.
  • Lastly, our controls divs need some buttons to click. I'm adding some background images for each and set position absolute.
<style type="text/css">
 @media screen and (-webkit-min-device-pixel-ratio: 0) {
 /* Samsung Mobile Fix */
 #MessageViewBody .fallback, body.MsgBody .fallback { display: block !important; visibility: visible !important; }
 #MessageViewBody .slider, body.MsgBody .slider { display: none !important; visibility: hidden !important; }
 /* Hide fallback and show interactive */
 .fallback { display: none !important; visibility: hidden !important; }
 .slider { display: block !important; visibility: visible !important; }

 /* Slider */
 .slider { width: 100%; height: 480px }

 /* Slider content sizing and position */
 .slides-cont { max-width: 960px; height: 230px; margin: 0 auto 0 auto; position: relative; overflow: hidden; }

 /* Slider sizing and positons */
 .slides { width: 166.66%; height: 230px; padding: 0 90px 0 90px; margin: 0 auto auto; position: absolute; left: -33.33% }
 .slides1 { z-index: 998; position: absolute; top: 0; }
 .slides2 { z-index: 999; position: absolute; top: 0; }
 .slides3 { z-index: 997; position: absolute; top: -1px; }

 /* Individual slides initial positions */
 .slide { filter: brightness(025%); position: absolute; }
 .slide1 { top: 0; left: 0%; }
 .slide2 { top: 0; left: 20%; }
 .slide3 { top: 0; left: 40%; }
 .slide4 { top: 0; left: 60%; }
 .slide5 { top: 0; left: 80%; }

 /* Intitial character selected */
 .slide-start { filter: brightness(100%); }

 /* Controls sizing and positons */
 .controls { max-width: 1080px; margin: 0 auto 0 auto; width: 100%; top: 10px; position: relative; z-index: 1000; }
 .controls1 { width: 100%; z-index: 1000; position: absolute; top: 0; }
 .controls2 { width: 100%; z-index: 1000; position: absolute; top: 80px; }
 .controls3 { width: 100%; z-index: 1000; position: absolute; top: 160px; }

 /* Controls buttons sizing, styling sizing and positons */
 .slider-nav { width: 50px; height: 50px; background-size: 50px 50px; background-repeat: no-repeat; display: block; position: absolute; top: 100%; }
 .slider-nav-lft { background-image: url('http://ofishialdigital.com/examples/minifigures/img/icon-nav-lft-50x50-2x.png'); left: 10px; }
 .slider-nav-rgt { background-image: url('http://ofishialdigital.com/examples/minifigures/img/icon-nav-rgt-50x50-2x.png'); right: 10px; }

 }
</style>

Phew... that was a lot of work! But we still have to make it actually interactive! At this point, you should have something that looks like below. You can view all the code we have covered so far, on codepen here.

Character builder layout with the stacked character images in place

So that looks nice, but it's kinda useless. let's add all the controls! This is where we are going to be using the CSS Tilde selector. Basically any time someone marks one of our inputs as 'checked' we want to find a proceeding element in the code and do something to it. In my example, when someone clicks one of the arrows I want to change the CSS left property to move the slide to a different location. We also want to 'switch-out' the controls that are visible with the next set so that when the user clicks again it is a different control being clicked. To summarise:

  • We show the current slides navigation when the current slide is visible.
  • We hide all the other slides navigations when the current slide is visible.
  • We re-position the slides within the slides container. Including moving the slides which are outside of the window to achieve a continuous looping slider.
<style type="text/css">
 @media screen and (-webkit-min-device-pixel-ratio: 0) {
 /* Samsung Mobile Fix */
 #MessageViewBody .fallback, body.MsgBody .fallback { display: block !important; visibility: visible !important; }
 #MessageViewBody .slider, body.MsgBody .slider { display: none !important; visibility: hidden !important; }
 /* Hide fallback and show interactive */
 .fallback { display: none !important; visibility: hidden !important; }
 .slider { display: block !important; visibility: visible !important; }

 /* Slider */
 .slider { width: 100%; height: 480px }

 /* Slider content sizing and position */
 .slides-cont { max-width: 960px; height: 230px; margin: 0 auto 0 auto; position: relative; overflow: hidden; }

 /* Slider sizing and positons */
 .slides { width: 166.66%; height: 230px; padding: 0 90px 0 90px; margin: 0 auto auto; position: absolute; left: -33.33% }
 .slides1 { z-index: 998; position: absolute; top: 0; }
 .slides2 { z-index: 999; position: absolute; top: 0; }
 .slides3 { z-index: 997; position: absolute; top: -1px; }

 /* Individual slides initial positions */
 .slide { filter: brightness(025%); position: absolute; }
 .slide1 { top: 0; left: 0%; }
 .slide2 { top: 0; left: 20%; }
 .slide3 { top: 0; left: 40%; }
 .slide4 { top: 0; left: 60%; }
 .slide5 { top: 0; left: 80%; }

 /* Intitial character selected */
 .slide-start { filter: brightness(100%); }

 /* Controls sizing and positons */
 .controls { max-width: 1080px; margin: 0 auto 0 auto; width: 100%; top: 10px; position: relative; z-index: 1000; }
 .controls1 { width: 100%; z-index: 1000; position: absolute; top: 0; }
 .controls2 { width: 100%; z-index: 1000; position: absolute; top: 80px; }
 .controls3 { width: 100%; z-index: 1000; position: absolute; top: 160px; }

 /* Controls buttons sizing, styling sizing and positons */
 .slider-nav { width: 50px; height: 50px; background-size: 50px 50px; background-repeat: no-repeat; display: block; position: absolute; top: 100%; }
 .slider-nav-lft { background-image: url('http://ofishialdigital.com/examples/minifigures/img/icon-nav-lft-50x50-2x.png'); left: 10px; }
 .slider-nav-rgt { background-image: url('http://ofishialdigital.com/examples/minifigures/img/icon-nav-rgt-50x50-2x.png'); right: 10px; }

 /* Show current slide nav when current slide visibile */
 .slider1-rad1:checked ~ .controls .slider1-nav1, .slider1-rad2:checked ~ .controls .slider1-nav2, .slider1-rad3:checked ~ .controls .slider1-nav3, .slider1-rad4:checked ~ .controls .slider1-nav4, .slider1-rad5:checked ~ .controls .slider1-nav5,
 .slider2-rad1:checked ~ .controls .slider2-nav1, .slider2-rad2:checked ~ .controls .slider2-nav2, .slider2-rad3:checked ~ .controls .slider2-nav3, .slider2-rad4:checked ~ .controls .slider2-nav4, .slider2-rad5:checked ~ .controls .slider2-nav5,
 .slider3-rad1:checked ~ .controls .slider3-nav1, .slider3-rad2:checked ~ .controls .slider3-nav2, .slider3-rad3:checked ~ .controls .slider3-nav3, .slider3-rad4:checked ~ .controls .slider3-nav4, .slider3-rad5:checked ~ .controls .slider3-nav5
 { display: block !important; visibility: visible !important; }

 /* Hide alternate slides nav when current slide visibile */
 .slider1-rad1:checked ~ .controls .slider1-nav2, .slider1-rad1:checked ~ .controls .slider1-nav3, .slider1-rad1:checked ~ .controls .slider1-nav4, .slider1-rad1:checked ~ .controls .slider1-nav5,
 .slider1-rad2:checked ~ .controls .slider1-nav1, .slider1-rad2:checked ~ .controls .slider1-nav3, .slider1-rad2:checked ~ .controls .slider1-nav4, .slider1-rad2:checked ~ .controls .slider1-nav5,
 .slider1-rad3:checked ~ .controls .slider1-nav1, .slider1-rad3:checked ~ .controls .slider1-nav2, .slider1-rad3:checked ~ .controls .slider1-nav4, .slider1-rad3:checked ~ .controls .slider1-nav5,
 .slider1-rad4:checked ~ .controls .slider1-nav1, .slider1-rad4:checked ~ .controls .slider1-nav2, .slider1-rad4:checked ~ .controls .slider1-nav3, .slider1-rad4:checked ~ .controls .slider1-nav5,
 .slider1-rad5:checked ~ .controls .slider1-nav1, .slider1-rad5:checked ~ .controls .slider1-nav2, .slider1-rad5:checked ~ .controls .slider1-nav3, .slider1-rad5:checked ~ .controls .slider1-nav4,
 .slider2-rad1:checked ~ .controls .slider2-nav2, .slider2-rad1:checked ~ .controls .slider2-nav3, .slider2-rad1:checked ~ .controls .slider2-nav4, .slider2-rad1:checked ~ .controls .slider2-nav5,
 .slider2-rad2:checked ~ .controls .slider2-nav1, .slider2-rad2:checked ~ .controls .slider2-nav3, .slider2-rad2:checked ~ .controls .slider2-nav4, .slider2-rad2:checked ~ .controls .slider2-nav5,
 .slider2-rad3:checked ~ .controls .slider2-nav1, .slider2-rad3:checked ~ .controls .slider2-nav2, .slider2-rad3:checked ~ .controls .slider2-nav4, .slider2-rad3:checked ~ .controls .slider2-nav5,
 .slider2-rad4:checked ~ .controls .slider2-nav1, .slider2-rad4:checked ~ .controls .slider2-nav2, .slider2-rad4:checked ~ .controls .slider2-nav3, .slider2-rad4:checked ~ .controls .slider2-nav5,
 .slider2-rad5:checked ~ .controls .slider2-nav1, .slider2-rad5:checked ~ .controls .slider2-nav2, .slider2-rad5:checked ~ .controls .slider2-nav3, .slider2-rad5:checked ~ .controls .slider2-nav4,
 .slider3-rad1:checked ~ .controls .slider3-nav2, .slider3-rad1:checked ~ .controls .slider3-nav3, .slider3-rad1:checked ~ .controls .slider3-nav4, .slider3-rad1:checked ~ .controls .slider3-nav5,
 .slider3-rad2:checked ~ .controls .slider3-nav1, .slider3-rad2:checked ~ .controls .slider3-nav3, .slider3-rad2:checked ~ .controls .slider3-nav4, .slider3-rad2:checked ~ .controls .slider3-nav5,
 .slider3-rad3:checked ~ .controls .slider3-nav1, .slider3-rad3:checked ~ .controls .slider3-nav2, .slider3-rad3:checked ~ .controls .slider3-nav4, .slider3-rad3:checked ~ .controls .slider3-nav5,
 .slider3-rad4:checked ~ .controls .slider3-nav1, .slider3-rad4:checked ~ .controls .slider3-nav2, .slider3-rad4:checked ~ .controls .slider3-nav3, .slider3-rad4:checked ~ .controls .slider3-nav5,
 .slider3-rad5:checked ~ .controls .slider3-nav1, .slider3-rad5:checked ~ .controls .slider3-nav2, .slider3-rad5:checked ~ .controls .slider3-nav3, .slider3-rad5:checked ~ .controls .slider3-nav4 
 { display: none !important; visibility: hidden !important; }

 /* Re-position slides when the current slide is visible */
 /* Slides in position 1 */
 .slider1-rad1:checked ~ .slides-cont .slides1 .slide4, .slider2-rad1:checked ~ .slides-cont .slides2 .slide4, .slider3-rad1:checked ~ .slides-cont .slides3 .slide4,
 .slider1-rad2:checked ~ .slides-cont .slides1 .slide5, .slider2-rad2:checked ~ .slides-cont .slides2 .slide5, .slider3-rad2:checked ~ .slides-cont .slides3 .slide5,
 .slider1-rad3:checked ~ .slides-cont .slides1 .slide1, .slider2-rad3:checked ~ .slides-cont .slides2 .slide1, .slider3-rad3:checked ~ .slides-cont .slides3 .slide1,
 .slider1-rad4:checked ~ .slides-cont .slides1 .slide2, .slider2-rad4:checked ~ .slides-cont .slides2 .slide2, .slider3-rad4:checked ~ .slides-cont .slides3 .slide2,
 .slider1-rad5:checked ~ .slides-cont .slides1 .slide3, .slider2-rad5:checked ~ .slides-cont .slides2 .slide3, .slider3-rad5:checked ~ .slides-cont .slides3 .slide3
 { left: 00%; filter: brightness(025%); transition: all 0.2s ease-in-out; }
 /* Slides in position 2 */
 .slider1-rad1:checked ~ .slides-cont .slides1 .slide5, .slider2-rad1:checked ~ .slides-cont .slides2 .slide5, .slider3-rad1:checked ~ .slides-cont .slides3 .slide5,
 .slider1-rad2:checked ~ .slides-cont .slides1 .slide1, .slider2-rad2:checked ~ .slides-cont .slides2 .slide1, .slider3-rad2:checked ~ .slides-cont .slides3 .slide1, 
 .slider1-rad3:checked ~ .slides-cont .slides1 .slide2, .slider2-rad3:checked ~ .slides-cont .slides2 .slide2, .slider3-rad3:checked ~ .slides-cont .slides3 .slide2,
 .slider1-rad4:checked ~ .slides-cont .slides1 .slide3, .slider2-rad4:checked ~ .slides-cont .slides2 .slide3, .slider3-rad4:checked ~ .slides-cont .slides3 .slide3,
 .slider1-rad5:checked ~ .slides-cont .slides1 .slide4, .slider2-rad5:checked ~ .slides-cont .slides2 .slide4, .slider3-rad5:checked ~ .slides-cont .slides3 .slide4
 { left: 20%; filter: brightness(025%); transition: all 0.2s ease-in-out; }
 /* Slides in position 3 */
 .slider1-rad1:checked ~ .slides-cont .slides1 .slide1, .slider2-rad1:checked ~ .slides-cont .slides2 .slide1, .slider3-rad1:checked ~ .slides-cont .slides3 .slide1,
 .slider1-rad2:checked ~ .slides-cont .slides1 .slide2, .slider2-rad2:checked ~ .slides-cont .slides2 .slide2, .slider3-rad2:checked ~ .slides-cont .slides3 .slide2,
 .slider1-rad3:checked ~ .slides-cont .slides1 .slide3, .slider2-rad3:checked ~ .slides-cont .slides2 .slide3, .slider3-rad3:checked ~ .slides-cont .slides3 .slide3,
 .slider1-rad4:checked ~ .slides-cont .slides1 .slide4, .slider2-rad4:checked ~ .slides-cont .slides2 .slide4, .slider3-rad4:checked ~ .slides-cont .slides3 .slide4,
 .slider1-rad5:checked ~ .slides-cont .slides1 .slide5, .slider2-rad5:checked ~ .slides-cont .slides2 .slide5, .slider3-rad5:checked ~ .slides-cont .slides3 .slide5 
 { left: 40%; filter: brightness(100%); transition: all 0.2s ease-in-out; }
 /* Slides in position 4 */
 .slider1-rad1:checked ~ .slides-cont .slides1 .slide2, .slider2-rad1:checked ~ .slides-cont .slides2 .slide2, .slider3-rad1:checked ~ .slides-cont .slides3 .slide2,
 .slider1-rad2:checked ~ .slides-cont .slides1 .slide3, .slider2-rad2:checked ~ .slides-cont .slides2 .slide3, .slider3-rad2:checked ~ .slides-cont .slides3 .slide3,
 .slider1-rad3:checked ~ .slides-cont .slides1 .slide4, .slider2-rad3:checked ~ .slides-cont .slides2 .slide4, .slider3-rad3:checked ~ .slides-cont .slides3 .slide4,
 .slider1-rad4:checked ~ .slides-cont .slides1 .slide5, .slider2-rad4:checked ~ .slides-cont .slides2 .slide5, .slider3-rad4:checked ~ .slides-cont .slides3 .slide5,
 .slider1-rad5:checked ~ .slides-cont .slides1 .slide1, .slider2-rad5:checked ~ .slides-cont .slides2 .slide1, .slider3-rad5:checked ~ .slides-cont .slides3 .slide1
 { left: 60%; filter: brightness(025%); transition: all 0.2s ease-in-out; }
 /* Slides in position 5 */
 .slider1-rad1:checked ~ .slides-cont .slides1 .slide3, .slider2-rad1:checked ~ .slides-cont .slides2 .slide3, .slider3-rad1:checked ~ .slides-cont .slides3 .slide3,
 .slider1-rad2:checked ~ .slides-cont .slides1 .slide4, .slider2-rad2:checked ~ .slides-cont .slides2 .slide4, .slider3-rad2:checked ~ .slides-cont .slides3 .slide4,
 .slider1-rad3:checked ~ .slides-cont .slides1 .slide5, .slider2-rad3:checked ~ .slides-cont .slides2 .slide5, .slider3-rad3:checked ~ .slides-cont .slides3 .slide5,
 .slider1-rad4:checked ~ .slides-cont .slides1 .slide1, .slider2-rad4:checked ~ .slides-cont .slides2 .slide1, .slider3-rad4:checked ~ .slides-cont .slides3 .slide1,
 .slider1-rad5:checked ~ .slides-cont .slides1 .slide2, .slider2-rad5:checked ~ .slides-cont .slides2 .slide2, .slider3-rad5:checked ~ .slides-cont .slides3 .slide2
 { left: 80%; filter: brightness(025%); transition: all 0.2s ease-in-out; }

}
</style>

Okay, that's it! You should no have a working example similar to this. But, let's take it one step further...

Work with us

Want interactive emails like this for your brand?

We design and build interactive, modular emails that work across every major email client. Tell us a little about what you need and we'll be in touch within one working day.

  • Interactive email design and development
  • Fallbacks that work in every inbox
  • Reusable interactive modules for your platform

Prefer email? hello@ofishialdigital.com

We'll only use your details to reply to your enquiry. See our privacy policy.