Abstracting your markup with LESS rulesets

Nesting, putting CSS rules inside of other CSS rules, is arguably the most useful feature in LESS, and certainly the first one that LESS (and CSS preprocessors in general) converts get excited about.

But, like all good things, nesting needs to be done in moderation. Otherwise, LESS files start to become an unreadable mess. There are optimization concerns, too: selector efficiency becomes harder and harder to grok as nesting goes deeper. While the general rule of “More nesting is worse” keeps selector inefficiency from being too much of a problem, it’s still hard to see at a glance what’s going on with chunks of code like this:

.nav-container {
	@padding:10px;

	padding:@padding;

	ul {
		float:right;
		list-style-type:none;

		li {
			float:right;
			margin-left:@padding;

			a {
				color:blue;

				&:hover {
					color:red;
				}
			}
		}
	}
}

This is a pretty typical markup structure for navigation elements, but even with the basics in place, nesting has gone five levels deep. Child selectors could help make this a bit more efficient, but then the code looks like this:

.nav-container {
	@padding:10px;

	padding:@padding;

	> ul {
		float:right;
		list-style-type:none;

		> li {
			float:right;
			margin-left:@padding;

			> a {
				color:blue;

				&:hover {
					color:red;
				}
			}
		}
	}
}

The > child selector makes the code more efficient, but not necessarily more readable. There’s got to be a better way to do it, for readability’s sake. Luckily, a recently released LESS feature provides a way to solve this issue of nesting, complexity and readability: Rulesets.

Rulesets allow passing of sets of rules to a mixin, which means entire components (or subcomponents) can be abstracted into a mixin.

Instead of a huge nested mess like above, Rulesets allow us to ask, “What needs to be styled?” and answer that with a mixin.

Let’s take the link, the most deeply nested element, and abstract it out. First, we take the rules and assign them to a variable:

@nav-link-rules: {
	color:blue;

	&:hover {
		color:red;
	}
}

Now the variable @nav-link-rules has all of our rules for the nav-link. We still need to express the markup, though, which is where a mixin comes in handy:

.nav-link() {
	> ul > li > a {
		@nav-link-rules();
	}
}

Now we can easily see, on one line, the markup structure of a nav link. One of the biggest advantages of rulesets is that we can pass them into mixins, allowing us to compact the whole thing and lose @nav-link-rules:

.nav-link(@rules) {
	> ul > li > a {
		@rules();
	}
}

Once we have the mixin written, using it looks like this:

.nav-link(@rules) {
	> ul > li > a {
		@rules();
	}
}

.nav-link({
	color:blue;

	&:hover {
		color:red;
	}
});

Note that what used to be @nav-link-rules is now passed directly into the mixin.

The whole example above, rewritten, looks like this:

.nav-container {
	@padding:10px;

	padding:@padding;

	// markup

	.nav-list(@rules) {
		& > ul { @rules();}
	}

	.nav-link-container {
		& > ul > li { @rules();}
	}

	.nav-link {
		& > ul > li > a { @rules();}
	}

	// styling

	.nav-list({
		float:right;
		list-style-type:none;
	});

	.nav-link-container({
		float:right;
		margin-left:@padding;
	});

	.nav-link({
		color:blue;

		&:hover {
			color:red;
		}
	});
}

There are drawbacks here: This method doesn’t necessarily save lines, especially on smaller/simpler components like this one, and inheritance rules aren’t as easy to grok.

But the end result is more readable and more portable. It matches much more closely to the CSS that’s eventually coughed out. It’s easier to optimize. And it’s way, way easier to adopt new markup structures.