Doing more with LESS!
by Siddique Hameed, Senior Software Architect
March 2013
Introduction
As the old saying goes "God made man and tailor made the gentleman...". Using the same metaphor, one can argue that if HTML and JavaScript made web applications live, then Cascading Style Sheets(CSS) made them look beautiful and aesthetically appealing!
Starting off as a simple style-based rules language, CSS slowly evolved into a highly sophisticated language producing eye popping animations and visually-immersive transitions. With the advent and progress in HTML5/CSS3 specifications and mobile/web development technology stack, the prevalence and power of CSS is growing.
Though CSS has been around for a long time and almost every web site and application uses it for styling, there are various shortcomings of the language itself that web developers find hard time to work with. It is probably because CSS is just a visual representation language that primarily works on DOM selector rules and cascading principles. Comparing it with compiled/interpreted languages like JavaScript, Ruby, Java etc. can be considered comparing apples with oranges. So, here comes LESS to the rescue, lessening some of the pains of web developers in working directly with CSS.
The pre requisites of this article is some basic level understanding of CSS. I will be going through basic concepts of LESS to help understand how LESS can be a valuable tool in web application development.
What is LESS?
In a nutshell, LESS enhances CSS by adding dynamic characteristics to the language semantics. It provides CSS with programming constructs like variables, mixins, operations and functions, which developers use in other programming languages. It helps streamline CSS writing in a way that makes it effectively managed and easy to deal with.
Why do we use LESS?
Well, there are variety of reasons. For starters, it helps web developers and designers write more reusable and maintainable CSS code. It borrows some basic best practices available in other programming languages and applies them indirectly to CSS. As web applications grow bigger and better, the challenge of maintaining and making updates to CSS gets harder. This is especially true when we are dealing with desktop and mobile based web application sharing common CSS code. LESS can save lot of pain and frustration in these situations.
Here is another example... Let's say for instance, we are running a web/mobile-based enterprise application with many CSS files and resources. And suddenly, the company is going on a re-branding initiative where the Look & Feel(L&F) of the application needs to be changed to the company's newer vision/goal. If the CSS files were written using LESS in a manageable structure, it will be easier and quicker to update.
How do we use LESS?
LESS can be used on the client or server-side. In most cases, it is used directly on the web server by compiling .less files to .css files. It comes with lessc script to compile .less files to .css files. It requires Node.js installed. Since LESS is written entirely in JavaScript it can also be invoked using a Rhino/Ant script. Please look at the reference section for pointers on how to get LESS compilation work with Rhino.
LESS on server side
If you are familiar with Node.js, you can install LESS using npm(node package manager) using the command below.
$ npm install -g less
Download LESS bundle
Here is the link to LESS's source bundle - https://github.com/cloudhead/less.js/archive/master.zip
After you download and extract the zip file there will be a lessc compilation script in bin folder, you can invoke lessc from command line as shown below.
$ lessc buttons.less buttons.css
LESS on client side
LESS can also be used on browser/client-side directly by including a provided JavaScript file. It will dynamically evaluate/transform LESS content into CSS content while rendering on the browser.
The example below shows the html file including .less file.
- <!-- less-client-side.html -->
- <html>
- <head>
- <link rel="stylesheet/less" type="text/css"
- href="less-client-side.less">
- <!-- less.js downloaded from LESS's website (http://lesscss.org) -->
- <script src="less.js" type="text/javascript"></script>
- <title>LESS in client side</title>
- </head>
- <body>
- <h1>H1 with yellow background!</h1>
- <h2>H2 with dark yellow background!</h2>
- </body>
- </html>
- <!-- less-client-side.less -->
- @light-yellow: desaturate(#fefec8, 10%);
- @dark-yellow: desaturate(darken(@light-yellow, 10%), 40%);
-
- h1 {
- background: @light-yellow;
- }
-
- h2 {
- background: @dark-yellow;
- }
The output to the above HTML file should look something like below in the browser.
The classic example of this approach is LESS's website itself - http://lesscss.org. If you view the page source of the website's index page in the browser, you will notice the included JavaScript (less.js) and .less files.
LESS code syntax, semantics and; constructs
Following sections will go over the LESS-based code semantics and constructs. Please note these are the currently available LESS (v1.3.3) features. They may vary in the future versions.
Variables
Variables are basic building blocks in almost every programming languages. Since CSS doesn't have the concept of variables by default, LESS adds that feature to CSS indirectly. They can be declared global or local to CSS classes.
Let's take this simple example.
- //buttons.css
- .fancy-button {
- -moz-border-radius: 12px;
- -webkit-border-radius: 12px;
- border-radius: 12px;
- background: #006dcc;
- border: 1px solid #0062c2;
- color: #e1e1e1;
- }
- .not-so-fancy-button {
- color: #e1e1e1;
- background: #006dcc;
- }
There are several duplicated values in buttons.css. The above code can be written using LESS as shown below. The second code block below shows the output generated from lessc
compilation. An important advantage is that changes to shared variables can be made in one place and wherever a variable is used will be automatically affected by that change.
- //variables.less
-
- //global variables that is shared among CSS classes
- @default-fg-color: #e1e1e1;
- @default-bg-color: #006dcc;
-
- .fancy-button {
- //local variable
- @radius: 12px;
-
- -moz-border-radius: @radius;
- -webkit-border-radius: @radius;
- border-radius: @radius;
- background: @default-bg-color;
- border: 1px solid #0062C2;
- color: @default-fg-color;
- }
-
- .not-so-fancy-button {
- color: @default-fg-color;
- background: @default-bg-color;
- }
- //lessc compilation output
- //variables.css
- .fancy-button {
- -moz-border-radius: 12px;
- -webkit-border-radius: 12px;
- border-radius: 12px;
- background: #006dcc;
- border: 1px solid #0062C2;
- color: #e1e1e1;
- }
- .not-so-fancy-button {
- color: #e1e1e1;
- background: #006dcc;
- }
Overriding Variables
In LESS, variables behave more like constants. It is mutable or can be overridden, but the last overridden value will take precedence while substituting values.
In the example below, we have two global variables, default-fg-color
and default-bg-color
. The default-bg-color
is overridden globally in line #18 and default-fg-color
is overridden in line #22 in local context (not-so-fancy-button
class).
In the CSS output oin the second code block, the change to default-fg-color
to #abcdef
is applied in both fancy-button
and not-so-fancy-button
classes though the overriding of that variable happens on line #18, which is after the declaration of fancy-button
class. Whereas, the default-bg-color
overridden value is only affected in the no-so-fancy-button
class.
- //override-variables.less
-
- @default-fg-color: #ffffff;
- @default-bg-color: #006dcc;
-
- .fancy-button {
- @radius: 12px;
- @border-color: #0062C2;
- -moz-border-radius: @radius;
- -webkit-border-radius: @radius;
- border-radius: @radius;
- background: @default-bg-color;
- border: 1px solid @border-color;
- color: @default-fg-color;
- }
-
- //overriding 'default-fg-color' in global context
- @default-fg-color: #abcdef;
-
- .not-so-fancy-button {
- //overriding 'default-bg-color' in local context
- @default-bg-color: yellow;
- color: @default-fg-color;
- background: @default-bg-color;
- }
- //lessc compilation output
- //override-variables.css
- .fancy-button {
- -moz-border-radius: 12px;
- -webkit-border-radius: 12px;
- border-radius: 12px;
- background: #006dcc;
- border: 1px solid #0062c2;
- color: #abcdef;
- }
- .not-so-fancy-button {
- color: #abcdef;
- background: #ffff00;
- }
Mixins
Mixins are like macros or methods in programming concepts. They support building reusable code blocks that can be embedded inside CSS classes.
From the previous example, let's say we want to design another button class that is the same as fancy-button
, but is purple in color. We don't want to duplicate everything defined in the fancy-button
class. Instead, we can embed fancy-button
class inside purple-fancy-button
and override only the properties we are interested in. The following example does exactly that. See line #20 in mixins.less file.
- //mixins.less
-
- //global variables that is shared among CSS classes
- @default-fg-color: #e1e1e1;
- @default-bg-color: #006dcc;
-
- .fancy-button {
- //local variable
- @radius: 12px;
-
- -moz-border-radius: @radius;
- -webkit-border-radius: @radius;
- border-radius: @radius;
- background: @default-bg-color;
- border: 1px solid #0062C2;
- color: @default-fg-color;
- }
-
- .purple-fancy-button {
- .fancy-button; //mixin call
- background: purple;
- //lessc compilation output
- //mixins.css
- .fancy-button {
- -moz-border-radius: 12px;
- -webkit-border-radius: 12px;
- border-radius: 12px;
- background: #006dcc;
- border: 1px solid #0062C2;
- color: #e1e1e1;
- }
- .purple-fancy-button {
- -moz-border-radius: 12px;
- -webkit-border-radius: 12px;
- border-radius: 12px;
- background: #006dcc;
- border: 1px solid #0062C2;
- color: #e1e1e1;
- background: purple;
- }
Parametric Mixins
Mixins can also take parameters/arguments with optional default values. They are very useful in building code blocks with variable style properties.
- //parametric-mixins.less
- .fancy-button-mixin (@width: 100px) { //Note the parentheses
- -moz-border-radius: 12px;
- -webkit-border-radius: 12px;
- border-radius: 12px;
- background: #006dcc;
- border: 1px solid #0062C2;
- width: @width;
- color: #e1e1e1;
- }
-
- .fancy-button {
- .fancy-button-mixin;
- }
-
- .larger-fancy-button {
- .fancy-button-mixin(200px);
- }
- //lessc compilation output
- //parametric-mixins.css
- .fancy-button {
- -moz-border-radius: 12px;
- -webkit-border-radius: 12px;
- border-radius: 12px;
- background: #006dcc;
- border: 1px solid #0062C2;
- width: 100px;
- color: #e1e1e1;
- }
- .larger-fancy-button {
- -moz-border-radius: 12px;
- -webkit-border-radius: 12px;
- border-radius: 12px;
- background: #006dcc;
- border: 1px solid #0062C2;
- width: 200px;
- color: #e1e1e1;
- }
Nesting Rules
Any strong CSS developer/designer might agree, inheritance and specificity are the key topics in understanding how CSS works under the cover(aka browser). It is generally considered a best practice to declare CSS classes specific to where it is applied when we want to isolate or not get affected by the selector/implicit inheritance rules defined in other CSS classes.
Nesting rules are an excellent way of representing inheritance between CSS selectors and maintain scope/specificity within CSS classes.
In the example below, 'h1'
styling declared within fancy-widget
class will be applied only to fancy-widget
class and it will not be affected by h1
styling in other CSS class definitions.
- //nesting-rules.less
- .border-radius-mixin(@radius: 5px) {
- -webkit-border-radius: @radius;
- -moz-border-radius: @radius;
- border-radius: @radius;
- }
-
- .fancy-widget {
- //nested/scoped within fancy-widget class,
- //so it will be not be affected for other 'h1' elements
- h1 {
- text-align: center;
- }
-
- &:hover { //Note the ampersand character. It will use the parent class name
- font-weight: bold;
- }
-
- a {
- color: purple;
- &:hover { //Note the ampersand character. It will use the parent class name
- text-decoration: none;
- }
- }
-
- .fancy-button {
- .border-radius-mixin;
- background: #006dcc;
- color: #ffffff;
- }
- }
- //lessc compilation output
- .fancy-widget h1 {
- text-align: center;
- }
- .fancy-widget:hover {
- font-weight: bold;
- }
- .fancy-widget a {
- color: purple;
- }
- .fancy-widget a:hover {
- text-decoration: none;
- }
- .fancy-widget .fancy-button {
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
- background: #006dcc;
- color: #ffffff;
- }
Operations
Operations are arithmetic operations(+, -, *, /, etc.), that can be performed on CSS style properties like size, color etc. They help define styling relationship between CSS classes and properties and hence building complex UI widgets with styling proportional or relative to each other.
Following example defines double-fancy-button
to be twice the size of fancy-button
and lighter-fancy-button
to have lighter background than fancy-button
.
- //operations.less
- @default-button-width: 100px;
- @default-border-radius: 12px;
- @default-background: #006dcc;
-
-
- .border-radius(@radius) {
- -webkit-border-radius: @radius;
- -moz-border-radius: @radius;
- border-radius: @radius;
- }
-
- .fancy-button-mixin (@width, @border-radius) {
- width: @width;
- .border-radius(@border-radius);
- background: @default-background;
- border: 1px solid #0062C2;
- color: #e1e1e1;
- }
-
- .fancy-button {
- .fancy-button-mixin(@default-button-width, @default-border-radius);
- }
-
- .double-fancy-button {
- //Note the (*) asterisk and (/) slashes within parentheses.
- //They represent operations and should be within parentheses
- .fancy-button-mixin(@default-button-width * 2, @default-border-radius / 2);
- }
-
- .lighter-fancy-button {
- .fancy-button-mixin(@default-button-width, @default-border-radius);
- background: (@default-background + #aaa);
- color: #ffffff;
- }
- //CSS output from lessc compilation
- .fancy-button {
- width: 100px;
- -webkit-border-radius: 12px;
- -moz-border-radius: 12px;
- border-radius: 12px;
- background: #006dcc;
- border: 1px solid #0062C2;
- color: #e1e1e1;
- }
- .double-fancy-button {
- width: 200px;
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
- background: #006dcc;
- border: 1px solid #0062C2;
- color: #e1e1e1;
- }
- .lighter-fancy-button {
- width: 100px;
- -webkit-border-radius: 12px;
- -moz-border-radius: 12px;
- border-radius: 12px;
- background: #006dcc;
- border: 1px solid #0062C2;
- color: #e1e1e1;
- background: #aaffff;
- color: #ffffff;
- }
Functions
LESS comes with some utility functions that are useful in defining style properties. They include string, numeric and color related functions.
The following example illustrates the use of the mix()
function. Refer to this page for all available functions - http://lesscss.org/#reference
- //functions.less
- @red-color: #FF0000;
- @blue-color: #0000ff;
-
-
- .fancy-purple-button{
- color: mix(@red-color, @blue-color);
- }
- //CSS output from lessc compilation
- .fancy-purple-button {
- color: #800080;
- }
Importing LESS files
As with most programming languages, LESS promotes modularization. We can declare commonly used color variables inside colors.less and import that into other .less files using @import
directive. This reduces redundancy and improve code reusability.
- //widgets.less
- @import "colors.less";
-
- .widget {
- ...
- }
Importing only once
The @import-once
directive will be useful for commonly imported .less files where the imported file should not be re-imported even if its declared to be imported multiple times.
- //widgets.less
- @import-once "colors.less";
- @import-once "shadows.less";
- @import-once "colors.less"; // will be ignored as its already imported in line #2
Watching .less files
The .less files need to be compiled to .css so they can be rendered by the browser. Either LESS is used on client-side or server-side, it needs to be compiled to CSS. If the application is in active development state, then it is best to automate the compilation process with the trigger of changes to source files (.less).
In the browser side, we can append #!watch
to the url, so LESS can automatically watch for changes to .less files. We can also call less.watch()
from the browser's developer console to instruct LESS to watch for source file changes.
There are various Node.js modules that do the same thing on the server side. One such node module is https://github.com/alexkwolfe/node-less-compiler
Where do we start?
One of the best parts of LESS is its 100% backward compatibility with CSS. Meaning, any valid CSS file is a valid .less file. If we have a web application and want to start using LESS, we can just copy the contents of .css files as is into the .less files and slowly work on converting them into a structure with the language semantics and features provided by LESS. This is a good transition strategy for any legacy applications for turning them into LESS based setup.
Learning by example
LESS is used in many web applications and frameworks. A very popular example is Twitter Bootstrap UI framework
Download and refer to its source files (.less files) to get a good understanding on how it uses LESS.
This wiki page lists the frameworks using LESS -https://github.com/cloudhead/less.js/wiki/Frameworks-that-use-LESS.js
Credit
Thanks to Mark Volkmann for reviewing this article and providing feedback!
References
- [1] LESS's website containing documentations and tutorials
http://lesscss.org/ - [2] LESS's Github repository
https://github.com/cloudhead/less.js - [3] Stackoverflow question about making LESS compilation work with Rhino and Ant
http://stackoverflow.com/questions/6909106/ant-script-to-compile-all-css-less-files-in-a-dir-and-subdirs-with-rhino - [4] Tips and tricks on using LESS
http://designshack.net/articles/css/10-less-css-examples-you-should-steal-for-your-projects/