Modern Web Development - Part 3


This is the third of ten parts of this blog post. The topics will be:

What’s Wrong with CSS?

If you’re going to do web development, you’ll need to learn how cascading style sheets (CSS) work. It’s a fine system for defining the look and feel of your designs but as a developer I find them more painful than necessary. Let’s discuss some of those pains.

So let’s assume I was writing a simple contact page form. So I’ll have a number of rules for the different parts of the form (I would probably not have rules specifically for the contact form, but have a common set of rules, but for this example it’s good enough):

contact_blank

It’s plain enough, so let’s make it pretty with some CSS:

/* home-contact.css */
h2
{
  font-style: italic;
  color: #222;
}

.big-form input[type=text]
{
  width: 300px;
  height: 25px;
  padding: 5px;
  border-radius: 5px;
  border: 1px black solid;
  background-color: #EEE;
  color: #222;
  margin: 2px 0;
  font-size: 18px;
}

.big-form label
{
  color: #222;
  font-size: 14px;
}

This works, but there are some common problems with this including repeating of values, repeating of selector syntax, no good way to handle shims or polyfills as well as no way to calculate values. In this way, CSS is purely content and presents maintenance issues.

A Better CSS?

To the rescue are dynamic stylesheet languages. Among these are:

  • Less: Client-side or server-side supported language for solving stylesheet problems.
  • Sass: Command-line or server-side supported language for solving stylesheet problems.

Both of these languages are similar in scope as they support features like:

  • Nested Styles
  • Variables
  • Mixins

They are both great languages the key importance here is that you should stop writing plain old CSS and use a dynamic stylesheet language. 

While Sass is a bit more powerful, I decided on Less as I found it easier to integrate with ASP.NET MVC. There are a number of ways to do this, but for me the least amount of friction came with a simple package I could install to my web project called dotless. This project can be installed via Nuget:

dotless

Dotless installs a http handler so that if you refer to .less files in your stylesheet declarations, it returns the generated CSS. Some might prefer to compile to .css at compile time, but since the .js can be changed at runtime too, I though this was the best mix of the two solutions. So let’s see how LESS can fix this for us.

Getting Less Working in ASP.NET MVC

One of the basic concepts around these dynamic stylesheet languages is that CSS is completely valid. So if I change my .css file to .less (and change the link to point to the .less file instead):

@* Contact.cshmtl *@
@model ModernWebDev.Models.SomeFormModel
@{
  ViewBag.Title = "Contact Us!";
}
@section Stylesheets
{
  <link rel="stylesheet" href="@Url.Content("~/Content/Home.Contact.less")" />
}
<h2>
  Contact</h2>
...

At this point the page still works because the new .less stylesheet is returned as the CSS it contains. Let’s refactor the CSS to use some of our new found power.

Refactoring the CSS with Less

The first change I want to make is to centralize the text color so that if I change it, I don’t have to search and replace. Less has the concept of variables. These variables start with the ‘@’ symbol like so:

@variable_name: value;

This means it can add a variable to hold our color at the top of our less file and use it everywhere:

/* home-contact.less */
@text-color: #222;

h2
{
  font-style: italic;
  color: @text-color;
}

.big-form input[type=text]
{
  width: 300px;
  height: 25px;
  padding: 5px;
  border-radius: 5px;
  border: 1px black solid;
  background-color: #EEE;
  color: @text-color;
  margin: 2px 0;
  font-size: 18px;
}

.big-form label
{
  color: @text-color;
  font-size: 14px;
}

While the variables tend to be constants, you can perform operations on them too. For example, I could use another variable to store the base size of my font then use operations to change the value as necessary:

/* home-contact.less */
@text-color: #222;
@base-font-size: 14px;

h2
{
  font-style: italic;
  color: @text-color;
}

.big-form input[type=text]
{
  width: 300px;
  height: 25px;
  padding: 5px;
  border-radius: 5px;
  border: 1px black solid;
  background-color: #EEE;
  color: @text-color;
  margin: 2px 0;
  font-size: @base-font-size + 4;
}

.big-form label
{
  color: @text-color;
  font-size: @base-font-size;
}

Line 21 is the magical line here. Notice that we’re saying that the size of the textbox text is going to be 4 larger than the base font, whatever that ends up being. Less can infer many types of operations and data types. For example, all of these work fine:

@aColor: Blue;
@aDarkerColor: @aColor + 80%;

@font-size: 14px;
@large-font-size: @font-size + 4;
@huge-font-size: @font-size + 8px;

Next thing is nested rules. Instead of repeating the name of the class (.big-form), less can nest it so that the nested styles will include the class name like so:

/* home-contact.less */
@text-color: #222;
@base-font-size: 14px;

h2
{
  font-style: italic;
  color: @text-color;
}

.big-form 
{
  input[type=text]
  {
    width: 300px;
    height: 25px;
    padding: 5px;
    border-radius: 5px;
    border: 1px black solid;
    background-color: #EEE;
    color: @text-color;
    margin: 2px 0;
    font-size: @base-font-size + 4;
  }

  label
  {
    color: @text-color;
    font-size: @base-font-size;
  }
}

While this is a whole lot more readable, it generates the right CSS to do the job that is implied. The CSS will include the duplication but this will be much easier to read and maintain.

We can also support mixins. The idea of mixins is to allow function-like syntax to add one or more properties. Mixins are defined with a ‘.’ to start then a name and parameter list. A simple mixin could be:

.solid-border()
{
  border: 1px black solid;
}

This allows us to use it in a rule and everything inside it is dumped in the rule:

.big-form 
{
  input[type=text]
  {
    .solid-border();
    //...
  }
  // ...
}

This results in the border line being added to the input. A great reason to use mixins is to handle cross-browser css like border-radius:

.rounded-corners-all(@size) {
  border-radius: @size;
  -webkit-border-radius: @size;
  -moz-border-radius: @size;
}

Notice that the @size variable is in the parameter list then used in the mixin. It can be used to leave size information in the rule like so:

.big-form 
{
  input[type=text]
  {
    .rounded-corners-all(5px);
    .solid-border();
    //...
  }
}

This will result in all three values being put in the rule with a size of 5px. But what’s interesting is that if the value passed in is multi-values, it can still accept it as a single value for example:

.big-form 
{
  input[type=text]
  {
    .rounded-corners-all(5px 10px 5px 15px);
    //...
  }
}

This works fine because the language knows that without any commas it should be treated as one value.

We’re getting close to a good solution, but we have all this information in one less file and it hurts its reuse. So we should refactor into several files moving the colors and mixins into their own files. Less has an interesting concept of import. If we move the colors and mixins to their own files, we can then use @import to pull in these other files:

/* home-contact.less */
@import "colors";   // will find .less if it exists
@import "mixins";   // otherwise will drop to css
@import "site.css"; // importing css is fine too

By specifying the import without the extension, it will try and find a .less file or fallback to a .css file. This way as you refactor into .less you don’t have to go fix up names as you need less features (pun intended).

This import will bring in complete .css files as well. The import here doesn’t just refer to the imported files but actually merges them into the file for you. So the resulting .less file contains all of the imported files. This means that you can build your CSS modularly without having to worry about the number of stylesheets that the browser can handle.

Debugging Less

Finally, sometimes you just need to see what is happening after processing. The easiest way to see this is to just browse to the .less file:

results

You can see this all working the working progress of the source code:

What do you think?




Application Name WilderBlog Environment Name Production
Application Ver 1.1.0.0 Runtime Framework .NETCoreApp,Version=v1.1
App Path D:\home\site\wwwroot Runtime Version .NET Core 4.6.25211.01
Operating System Microsoft Windows 6.2.9200 Runtime Arch X86