Designing Next Generation Web Projects with CSS3

4 (2 reviews total)
By Sandro Paganotti
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Free Chapter
    No Sign Up? No Party!

About this book

CSS3 unveils new possibilities for frontend web developers: things that would require JavaScript, such as animation and form validation, or even third party plugins, such as 3D transformations, are now accessible using this technology.

"Designing Next Generation Web Projects with CSS3" contains ten web projects fully developed using cutting edge CSS3 techniques. It also covers time saving implementation tips and tricks as well as fallback, polyfills, and graceful degradation approaches.

This book draws a path through CSS3; it starts with projects using well supported features across web browsers and then it moves to more sophisticated techniques such as multi polyfill implementation and creating a zooming user interface with SVG and CSS.

React to HTML5 form validation, target CSS rules to specific devices, trigger animations and behavior in response to user interaction, gain confidence with helpful tools like SASS, learn how to deal with old browsers and more.

"Designing Next Generation Web Projects with CSS3" is a helpful collection of techniques and good practices designed to help the implementation of CSS3 properties and features.

Publication date:
January 2013
Publisher
Packt
Pages
288
ISBN
9781849693264

 

Chapter 1. No Sign Up? No Party!

CSS3 has been a big leap forward for forms. Not only are new style possibilities available, but new and powerful pseudo-selectors can also now be used to modify the appearance of our page, depending on the state of the form or of its fields. In this chapter, we will use a party registration form as a test case to show how this component can be enhanced by the new CSS specifications. We will also pay attention to how we can retain the right behavior for older browsers. We're going to cover the following topics:

  • HTML structure

  • The form

  • Basic styling

  • Marking required fields

  • The checked radio buttons trick

  • Counting invalid fields

  • Balloon styling

 

HTML structure


Let's start with some HTML5 code to shape the structure of our project's web page. To do so, create a file, named index.html, in a new folder, named no_signup_no_party, containing the following markup:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
  <title>No signup? No party!</title>
  <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.7.3/build/cssreset/cssreset-
min.css">
  <link rel='stylesheet' type='text/css' 
href='http://fonts.googleapis.com/css?family=Port+Lligat+Sans'>
  <link rel='stylesheet' type='text/css' 
href='css/application.css'>
  <script 
src="http://html5shiv.googlecode.com/svn/trunk/html5.js">
</script>
</head>
<body>
  <article>
    <header>
      <h1>No signup? No party!</h1>
      <p>
        Would you like to join the most amazing party of the 
planet? Fill out this form with your info but.. hurry up! only a 
few tickets are still available!
      </p>
    </header>
    <form name="subscription">
      <!-- FORM FIELDS -->
      <input type="submit" value="Yep! Count me in!">
    </form>
    <footer>
      Party will be held at Nottingham Arena next sunday, for info 
call 555-192-132 or drop us a line at [email protected]
    </footer>
  </article>
</body>
</html>

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased through your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

As you can see from the markup, we are taking advantage of the new structure offered by HTML5. Tags such as <article>, <header>, and <footer> enrich the page by adding semantic meaning to the content. These tags are rendered exactly as <div> but are, semantically speaking, better because they explain something about their content.

Note

For more information, I suggest you look at the following article: http://html5doctor.com/lets-talk-about-semantics

Flavor text aside, the only section that needs detailed explanation is the <head> section. Within this tag, we ask the browser to include some external assets that will help us along the way.

Reset stylesheet and custom fonts

First, there is a Reset stylesheet, which is particularly useful for ensuring that all the CSS properties that browsers apply by default to HTML elements get removed. In this project, we use the one offered freely by Yahoo!, which basically sets all the properties to none or something equivalent.

Next, we ask for another stylesheet. This one is from a Google service called Google Web Fonts (www.google.com/webfonts), which distributes fonts that can be embedded and used within a web page. Custom web fonts are defined with a special @font-face property that contains the link to the font file the browser has to implement.

@font-face{
  font-family: YourFontName;
  src: url('yourfonturl.eot');
}

Unfortunately, to reach the maximum possible compatibility between browsers, more font file formats are required, and so a more complex statement is necessary. The following statements help achieve such compatibility:

@font-face{
  font-family: YourFontName;
  src: url('yourfonturl.eot');
  src: 
    url('yourfonturl.woff') format('woff'), 
    url('yourfonturl.ttf') format('truetype'), 
    url('yourfonturl.svg') format('svg');
  font-weight: normal;
  font-style: normal;
}

Google Web Fonts provides us with a stylesheet containing these statements for the fonts we choose, saving us all the trouble related to font conversion.

Next, let's create an empty file for our stylesheet under a css folder within the project.

Last but not least, we need to ensure that even older Internet Explorer browsers will be able to handle the new HTML5 tags correctly. html5shiv (html5shiv.googlecode.com) is a small JavaScript file that accomplishes exactly this task.

 

Creating the form


Now let's write the HTML code for the form by adding the following code below the <!--FORM FIELDS--> mark:

<fieldset>
  <legend> 
    Some info about you:
  </legend>
  <input type="text" name="name" id="name" placeholder="e.g. 
Sandro" title="Your name, required" required>
  <label class="label" for="name"> Name: </label>
  <input type="text" name="surname" id="surname" placeholder="e.g. 
Paganotti" title="Your surname, required" required>
  <label class="label" for="surname"> Surname: </label>
  <input type="email" name="email" id="email" placeholder="e.g. 
[email protected]" title="Your email address, a valid 
email is required" required>
  <label class="label" for="email"> E-mail: </label>
  <input type="text" name="twitter" id="twitter" placeholder="e.g. 
@sandropaganotti" title="Your twitter username, starting with @" 
pattern="@[a-zA-Z0-9]+">
  <label class="label" for="twitter"> Twitter:</label>
  <footer></footer>
</fieldset>

HTML5 offers some new attributes that we will explore briefly, as follows:

  • placeholder: This is used to specify some help text that is displayed within the field when empty.

  • required: This is used to mark the field as required. It's a Boolean attribute that tells the browser to ensure that the field is not empty before submitting the form. This attribute is part of the new form validation features, which basically offer a way to specify some input constraints on the client side. Unfortunately, each browser handles the display of the error messages contained in the title attribute in a different way, but we'll check this later in the chapter.

  • pattern: This is a powerful and sometimes complex way of specifying a validation pattern. It needs a regular expression as a value. This expression is then checked against the data inserted by the user. In case of failure, the message contained in the title attribute is displayed.

    In the given example, the pattern value is @[a-zA-Z0-9]+, which means "one or more occurrences (the + sign) of glyphs from the ranges a-z (all lowercase letters), A-Z (all uppercase letters), and 0-9 (all digits)".

    Note

    More ready-to-use patterns can be found at http://html5pattern.com/.

Like most of the features introduced by HTML5, even new form attributes such as the ones we saw in the code earlier suffer in terms of complete browser compatibility.

Note

To get a glimpse of the current browser support for these attributes and many other HTML5 and CSS3 features, I suggest going to http://caniuse.com/.

Misplaced labels

There's another oddity in this code: labels are placed after the fields they're linked to. This markup, although uncommon, is still valid and gives us some new interesting options to intercept user interaction with the form elements. This may sound mysterious, but we're going to analyze the technique in detail in a few pages from now.

Let's add another fieldset element below the one we just wrote:

<fieldset class="preferences">
  <legend> Your party preferences: </legend>
  <input type="radio" name="beers" id="4_beers" value="4">
  <label class="beers" for="4_beers">4 beers</label>
  <input type="radio" name="beers" id="3_beers" value="3">
  <label class="beers" for="3_beers">3 beers</label>
  <input type="radio" name="beers" id="2_beers" value="2">
  <label class="beers" for="2_beers">2 beers</label>
  <input type="radio" name="beers" id="1_beers" value="1">
  <label class="beers" for="1_beers">1 beers</label>
  <input type="radio" name="beers" id="0_beers" value="0" 
required>
  <label class="beers" for="0_beers">0 beers</label>
  <span  class="label"> How many beers?: </span>
  <input type="radio" name="chips" id="4_chips" value="4">
  <label class="chips" for="4_chips">4 chips</label>
  <input type="radio" name="chips" id="3_chips" value="3">
  <label class="chips" for="3_chips">3 chips</label>
  <input type="radio" name="chips" id="2_chips" value="2">
  <label class="chips" for="2_chips">2 chips</label>
  <input type="radio" name="chips" id="1_chips" value="1">
  <label class="chips" for="1_chips">1 chips</label>
  <input type="radio" name="chips" id="0_chips" value="0" 
required>
  <label class="chips" for="0_chips">0 chips</label>
  <span class="label"> How many chips?: </span>
  <footer></footer>
</fieldset>

Nothing to highlight here; we've just added two radio button groups. Now, if we try to run what we've done up to now in a browser, we are going to face some disappointment because the default browser's styles have been removed by the Reset stylesheet.

Time to add some basic styling!

 

Basic styling


What we need to do is center the form, give the right size to the texts, choose a background, and adjust the displacement of labels and fields.

Let's start with the background. What we want to achieve is to place an image as big as possible to fit the page while keeping its proportions. This simple task in the "CSS2 era" would involve some use of JavaScript, such as the well-known Redux jQuery plugin (http://bavotasan.com/2011/full-sizebackground-image-jquery-plugin/). With CSS3 it's just a matter of a few statements:

html{
  height: 100%;
  background: black;
  background-image: url('../img/background.jpg');
  background-repeat: no-repeat;
  background-size: cover;
  background-position: top left;
  font-family: sans-serif;
  color: #051a00;
}

What does the trick here is the background-size property, which accepts the following values:

  • length: Using this value, we can express the size of the background using any units of measurement, for example background-size: 10px 10px;.

  • percentage: Using this value, we can specify a background size that varies with the size of the element, for example background-size: 10% 10%;.

  • cover: This value scales the image (without stretching it) to cover the whole area of the element. This means that part of the image may not be visible because it could get bigger than the container.

  • contain: This value scales the image (without stretching it) to the maximum size available while keeping the whole image within the container. This, obviously, could leave some area of the element uncovered.

So, by using cover, we ensure that the whole page will be covered by our image, but we can do more! If we run all that we've done up to here in a browser, we will see that the pixels of our background image become visible if we enlarge the window too much. To avoid this, what we can do is to use another background image on top of this one. We can use small black dots to hide the pixels of the underlying image and achieve a better result.

The good news is that we can do this without using another element, as CSS3 allows more than one background on the same element. We can use commas (,) to separate the backgrounds, keeping in mind that what we declare first will lay over the others. So, let's change the preceding code a bit:

html{
  height: 100%;
  background: black;
  background-image: 
    url('../img/dots.png'), 
    url('../img/background.jpg');
  background-repeat: repeat, no-repeat;
  background-size: auto, cover;
  background-position: center center, top left;
  font-family: sans-serif;
  color: #051a00;
}

Also, all the other background-related properties act in the same way. If we omit one of the values, the previous one is used, so writing background-repeat: repeat is the same as writing background-repeat: repeat, repeat if two background images are declared.

Defining properties

Let's move on and define the rest of the required properties to complete the first phase of the project:

/* the main container */
article{
  width: 600px;
  margin: 0 auto;
  background: #6cbf00;
  border: 10px solid white;
  margin-top: 80px;
  position: relative;
  padding: 30px;
  border-radius: 20px;
}
/* move the title over the main container */
article h1{
  width: 600px;
  text-align: center;
  position: absolute;
  top: -62px;
/* using the custom font family provided by google */
  font-family: 'Port Lligat Sans', cursive;
  color: white;
  font-size: 60px;
  text-transform: uppercase;
}

/* the small text paragraphs */
article p, 
article > footer{
  padding-bottom: 1em;
  line-height: 1.4em;
}

/* the fieldsets' legends */
article legend{
  font-family: 'Port Lligat Sans', cursive;
  display: block;
  color: white;
  font-size: 25px;
  padding-bottom: 10px;
}

.label{
  display: block;
  float: left;
  clear: left;
}

/* positioning the submit button */
input[type=submit]{
  display:block;
  width: 200px;
  margin: 20px auto;
}

/* align texts input on the right */
input[type=text], input[type=email]{
  float: right;
  clear: right;
  width: 350px;
  border: none;
  padding-left: 5px;
}
input[type=text], 
input[type=email], 
.label{
  margin: 2px 0px 2px 20px;
  line-height: 30px;
  height: 30px;
}

span + input[type=radio], legend + input[type=radio]{
  clear: right
}

/* size of the small labels linked to each radio */
.preferences label.chips,
.preferences label.beers{
  width: 60px;
  background-image: none;
}

input[type="radio"]{
  padding-right: 4px;
}

input[type="radio"], 
.preferences label{
  float: right;
  line-height: 30px;
  height: 30px;
}

There are just a few things to underline here. First of all, by using some floats, we've moved all the fields to the right and the labels to the left. Next, we've defined some distance between the elements. Maybe the most cryptic statement is the following one:

span + input[type=radio], legend + input[type=radio]{
  clear: right
}

Due to the floating that we just talked about, the first element of each group of radio buttons became the rightmost. So, we identify this element by using the selector1 + selector2 selector, which indicates that the specified elements must be siblings. This is called an adjacent sibling selector, and selects all the elements matching the selector2 selector that directly follows an element matching the selector1 selector. Finally, using clear:right we simply state that there must be no other floating elements to the right of these radio buttons.

Let's reload the project in the browser to appreciate the result of our work:

 

Marking required fields


Let's look at an easy trick to automatically display an asterisk (*) near the labels of required fields. The HTML5 form validation model introduces some new and very interesting pseudo-selectors:

  • :valid: It matches only fields that are in a valid state.

  • :invalid: It works in the opposite way, matching only fields with errors. This includes empty fields with the required attribute set to true.

  • :required: It matches only fields with the required flag, whether they're filled or not.

  • :optional: It works with all fields the without the required flag.

In our case, we need to match all the labels that follow a field that has the required attribute. Now the HTML5 structure we implemented earlier comes in handy because we can take advantage of the + selector to accomplish this.

input:required + .label:after, input:required + * + .label:after{
  content: '*';
}

We added a small variation (input:required + * + .label:after) in order to intercept the structure of the radio buttons as well.

Let's analyze the sentence a bit before moving on. We used the :after pseudo-selector to get access to the location just after the element with a label class. Then, with the content property, we injected the asterisk within that location.

If we reload the page we can verify that, now, all the labels that belong to fields with a required flag end with an asterisk. Someone may point out that screen readers do not recognize this technique. To find a way around this, we can take advantage of the aria-required property, part of the WAI-ARIA specification (http://www.w3.org/TR/WCAG20-TECHS/ARIA2).

 

The checked radio buttons trick


Now we can concentrate on the radio buttons, but how can we render them in a better way? There is a cool technique for this; it takes advantage of the fact that you can check a radio button even by clicking on its linked label. What we can do is hide the input elements and style the corresponding labels, maybe using icons that represent chips and beers.

Let's begin by removing the text from the radio button labels and changing the cursor appearance when it's hovering over them:

.preferences label{
  float: right;
  text-indent: -100px;
  width: 40px !important;
  line-height: normal;
  height: 30px;
  overflow: hidden;
   cursor: pointer;
}

Well done! Now we have to hide the radio buttons. We can achieve this by placing a patch with the same color as the background over the radio button. Let's do that:

input[type=radio]{
  position: absolute;
  right: 30px;
  margin-top: 10px;
}

input[type=radio][name=chips]{
  margin-top: 35px;
}

span + input[type=radio] + label, 
legend + input[type=radio] + label{
  clear: right;
  margin-right: 80px;
  counter-reset: checkbox;
}

.preferences input[type="radio"]:required + label:after{
  content: '';
  position: absolute;
  right: 25px;
  min-height: 10px;
  margin-top: -22px;
  text-align: right;
  background: #6cbf00;
  padding: 10px 10px;
  display: block;
}

If we now try to submit the form either using WebKit-based browsers or Firefox, we can appreciate that the validation balloons related to radio buttons are displayed correctly on both of them.

Displaying icons within radio button labels

Let's move on and work on the radio button labels that, at the moment, are completely empty because we moved the text away using the text-indent property. What we are going to do now is to put a tiny placeholder image within each label, and by taking advantage of the CSS3 ~ selector, create a pseudo-star rating system with a nice mouse-over effect.

Due to the fact that we have to work with different images (for beers and chips), we have to duplicate some statements. Let's start with the .beers labels:

.preferences label.beers{
  background: transparent url('../img/beer_not_selected.png') 
no-repeat center center;
}

.preferences label.beers:hover ~ label.beers, 
.preferences label.beers:hover, 
.preferences input[type=radio][name=beers]:checked ~ label.beers{
  background-image: url('../img/beer.png');
  counter-increment: checkbox;
}

The elem1 ~ elem2 selector applies to all the elem2 labels that are siblings of the elem1 label and that follow it (the elem2 labels don't have to be adjacent, though). This way, we can target all the labels that follow a label that is in the hover state (when the mouse is over the element) with the selector .preferences label.beers:hover ~ label.beers.

Using the CSS3 :checked pseudo-class selector, we can identify the radio button that has been checked, and by applying the same trick that we just discussed, we can target all the labels that follow a checked radio button by using .preferences input[type=radio][name=beers]:checked ~ label.beers. By putting together these two selectors and a classic .preferences label.beers:hover selector, we are now able to change the placeholder image reflecting the user interaction with the radio buttons. Now let's add a final cool feature. We have used the counter-increment property to keep track of the number of selected labels, so we can take advantage of this counter and display it.

.preferences input[type=radio][name=beers]:required + 
label.beers:after{
  content: counter(checkbox) " beers!";
}

Let's try the result in a browser:

Now, we have to duplicate the same statements for the .chips labels too:

.preferences label.chips{
  background: transparent 
url('../img/frenchfries_not_selected.png') 
no-repeat center center;
}

.preferences label.chips:hover ~ label.chips, 
.preferences label.chips:hover, 
.preferences input[type=radio][name=chips]:checked ~ label.chips {
 background-image: url('../img/frenchfries.png');
  counter-increment: checkbox;
}

.preferences input[type=radio][name=chips]:required + 
label.chips:after {
  content: counter(checkbox) " chips!";
}

All of the styling we did in this chapter has one big problem; if the browser doesn't support CSS3, it successfully hides both radio buttons and text labels but fails to add their image replacements, making everything unusable. There are a few ways to prevent this. The one introduced here is to use media queries.

Media queries, which will be covered in detail in a later project, basically consist of a statement that describes some conditions required to apply some styles. Let's consider the following example:

@media all and (max-width: 1000px){
  body{
    background: red;
  }
}

In this example, the body background is turned into red only if the size of the browser window doesn't exceed 1000px. Media queries are really useful to apply specific styles only to target devices (smartphones, tablets, and so on), but they have another interesting property; if a browser supports them, it also supports the CSS3 rules we used, so we can place all of the CSS written in this and in the previous sections within a media query statement:

@media all and (min-device-width: 1024px){

/* --- all of this and previous sections' statements --- */

}

With this trick, we solved another subtle problem. Trying the project on an iPad without this media query statement would have resulted in some problems with clicking on the radio buttons. This is because labels do not respond to clicks on iOS. By implementing this media query, we force iOS devices to fall back to regular radio buttons.

 

Counting and displaying invalid fields


In the previous section, we used some properties without explaining them; they are counter-reset and counter-increment. Plus, we used a function-like command called counter(). In this section, we'll explain these properties by creating a mechanism to display the number of invalid fields. A counter is basically a variable we can name and whose value can be incremented using counter-increment. Next, this counter can be displayed by using the counter(variable name) declaration within a content property.

Let's see a small example:

<ul>
  <li>element</li>
  <li>element</li>
  <li>element</li>
</ul>
<p></p>

<style>

ul{
  counter-reset: elements;
}

li{
  counter-increment: elements;
}

p:after{
  content: counter(elements) ' elements';
}

</style>

Trying this small bit of code results in a p element containing the sentence 3 elements:

We can combine these powerful properties with the new form pseudo-selector to obtain a way to display valid and invalid fields.

Implementing the counters

Let's start by creating two counters, invalid and fields, and resetting them at each fieldset element because we want to display the invalid fields for each fieldset element. Then, we increment both counters when we find an invalid field and only the fields counter when we find a valid field.

fieldset{
  counter-reset: invalid fields;
}

input:not([type=submit]):not([type=radio]):invalid, 
input[type=radio]:required:invalid{
  counter-increment: invalid fields;
  border-left: 5px solid #ff4900;
}

input:not([type=submit]):not([type=radio]):valid, 
input[type=radio]:required{
  counter-increment: fields;
  border-left: 5px solid #116300;
}

The :not pseudo-selector is pretty straightforward. It subtracts the elements matching the selector within the parentheses from the elements matching the leftmost selector. If this seems a bit confusing, let's try to read the last selector: match all the input elements, whose type value is not submit and not radio, that respond to the :valid pseudo-selector.

Almost there! Now that we have the counters, let's display them using the footer element we have:

fieldset footer{
  clear: both;
  position: relative;
}

fieldset:not([fake]) footer:after{
  content: 'yay, section completed, move on!';
  text-align: right;
  display: block;
  font-size: 13px;
  padding-top: 10px;
}

/* the value of the content property must be on one single line */ 
fieldset > input:invalid ~ footer:after{
  content: counter(invalid) '/' counter(fields) " fields with 
problems; move the mouse over the fields with red marks to see 
details.\a Fields with * are required.";
  white-space: pre;
}

The :not([fake]) selector is used like the media query shown earlier. We just want to ensure that only the browsers that support the :valid and :invalid pseudo-selectors can interpret this selector.

This last addition has some drawbacks, though; mixing presentation with content is generally something to avoid.

 

Balloon styling


Each browser actually displays form errors in its own way, and we can't do very much to affect this visualization. The only exceptions are WebKit-based browsers, which let us change the appearance of such messages. The following code shows how an error balloon is constructed in these browsers:

<div>::-webkit-validation-bubble
  <div>::-webkit-validation-bubble-arrow-clipper
    <div>::-webkit-validation-bubble-arrow
    </div>
  </div>::-webkit-validation-bubble-message
  <div>
    <b>Browser validation message</b>
    element's title attribute
  </div>
</div>

We can access all the elements that compose an error message by using the special pseudo-classes listed in the preceding code. So, let's begin!

::-webkit-validation-bubble{
  margin-left: 380px;
  margin-top: -50px;
  width: 200px;
}

input[type=radio]::-webkit-validation-bubble{
  margin-left: 50px;
  margin-top: -50px;
}

::-webkit-validation-bubble-arrow-clipper{
  -webkit-transform: rotate(270deg) translateY(-104px) 
translateX(40px);
}

::-webkit-validation-bubble-arrow{
  background: #000;
  border: none;
  box-shadow: 0px 0px 10px rgba(33,33,33,0.8);
}

::-webkit-validation-bubble-message{
  border: 5px solid black;
  background-image: none;
  box-shadow: 0px 0px 10px rgba(33,33,33,0.8);
}

With -webkit-transform, we're applying some transformation to the matched elements. In this case, we're moving the arrow, which usually lies on the bottom of the balloon, to the left side of it.

The following is a glimpse of how our completed project looks:

 

Graceful degradation


As we might expect, this project is not fully supported on all browsers because it implements HTML5 and CSS3 features that, of course, aren't included in old browsers. Many techniques exist to find a way around this issue; the one we'll look at now is called graceful degradation. It basically focuses on making the core functionalities of the project as widely supported as possible while accepting that everything else might be unsupported and thus not displayed.

Our project is a good example of graceful degradation: when a browser does not support a specific property, its effects are simply ignored without affecting the basic functionality of the form.

To prove this, let's try the project on IE8, which basically has no CSS3 support:

To achieve the best possible browser support, we may also want to hide footer elements and radio buttons on IE9 because, otherwise, they'll be displayed but they won't behave as expected. To do so, we need to add a conditional comment in our index.html file, just before the end of the head section. We'll see in the later chapters how conditional comments work, but for now let's say that they allow us to specify some markup that needs to be interpreted only by chosen browsers.

<!--[if IE 9]>
  <style>
    footer, input[name=beers], input[name=chips]{
      display: none;
    }
  </style>
<![endif]-->
 

Summary


In this first project, we've explored how CSS3 can enhance our forms with useful information derived from the markup and the status of the fields. In the next chapter, we'll focus our attention on buttons and how we can mimic real-world shapes and behavior without using images by taking full advantage of gradients and other CSS3 properties.

About the Author

  • Sandro Paganotti

    Sandro Paganotti is a web developer, a Google Developers Expert in HTML5, a technical writer, and an HTML5/CSS3 teacher with a passion for Ruby and cutting-edge frontend technologies. He enjoys finding clever and pragmatic solutions to ambitious projects at Comparto Web, the company he co-founded in 2012. He's also a funding member of WEBdeBs, a local community of web enthusiasts, and a speaker at local and national conferences.

    Browse publications by this author

Latest Reviews

(2 reviews total)
The book is pretty good, starting small and then going deeper. Core subjects are the newest features of CSS3, but it's always present an eye open to retro-compatibility and mobile devices
Designing Next Generation Web Projects with CSS3
Unlock this book and the full library FREE for 7 days
Start now