Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Dave Ferguson and Simon Free and Tim Cunningham and Jason Dean
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Dave Ferguson Simon Free Tim Cunningham Jason Dean

Thinking About Strings, Quotes, Tokens, And Tildes In Less CSS

By
Published in Comments (1)

When you've programmed in more traditional programming languages, the use of String values in the Less CSS preprocessor can be somewhat confusing. Less does have Strings (and an isstring() validation function); but, they don't play a major role, like they do in other languages. In Less CSS, you have to think in terms of "tokens" and "inputs;" and, using quotes as a means to group tokens that may or may not be wrapped in quotes.

When I first started digging into Less CSS, the biggest hurdle for me was changing the way that I felt about values like:

"hello"

The last 15 years of my life compel me to look at that and think, "That's a string that contains the world, hello." And while this may be slightly accurate, thinking about it that way is detrimental to your overall mental model in Less CSS.

What you really need to think is, "That's a token that contains the value, "hello", complete with quotes." There's nothing special or "stringy" about it. This starts to become more clear when you realize that quotes are rendered directly in the CSS output.

body {
	@quoted: "hello world" ;
	@unquoted: hello world ;

	quoted: @quoted ;
	unquoted: @unquoted ;
}

... gets compiled down to:

body {
	quoted: "hello world";
	unquoted: hello world;
}

There's two behaviors to notice here in this demo. First, I didn't need quotes to define the @unquoted input value, even though it contains multiple tokens. And, second, the quote characters, in the quoted value, were rendered in the CSS. Less didn't look at the quoted string and "unwrap" it during compilation; it simply rendered it as a token that contained a quoted value.

Pause here for a moment and really think about that. It might seem like an insignificant point; but, if you don't stop and truly embrace that idea, working with Less CSS is going to seem a lot more mysterious and unpredictable than it actually is.

In Less CSS, you don't you use quotes to create strings - you use quotes for two reasons:

  1. You want actual quote characters showing up in your compiled CSS output.
  2. You need to group multiple tokens into a single value.

The first point, we've already seen above; but, the second point requires a bit more exploration. Most of the time, Less CSS will automatically group successive tokens into a single input / value, as we saw with the unquoted value in the previous demo. But, sometimes, the context is too ambiguous. In such cases, you can use quotes to tell Less CSS which tokens to group together.

This can be seen in mixins that accept CSS values that contain natural list delimiters. Take "transition" for example. It can use commas to definite per-property timing and easing:

.transition( @properties ) {
	transition: @properties ;
}

body {
	.transition( top 1s ease, left 1s ease ) ;
}

If you look at the mixin definition, you can see that it's expecting a single value. And, if you look at our mixin invocation, you may think that we're passing a single CSS value; but, we're not. And, when we go to compile this, Less throws an error:

No matching definition was found for `.transition(top 1s ease, left 1s ease)`

The problem here is that Less looks at the "," in the CSS value we're passing and treats it as parameter-delineation. In other words, it parses our code as invoking .transition() with two arguments:

  • top 1s ease
  • left 1s ease

And, since there is no definition of .transition() that accepts two arguments, Less doesn't know what to do.

In this case, we need to help Less CSS understand that all those tokens are actually part of one value. One way to do that is to add a superflous delimiter with a higher precedence. In this case, the semi-colon:

.transition( @properties ) {
	transition: @properties ;
}

body {
	.transition( top 1s ease, left 1s ease ; ) ;
}

Notice that there's a dangling ";" at the end of our arguments list. When it comes to mixin invocation, you can delimit values with either commas or semi-colons. But, if you use any semi-colons, Less CSS will stop treating commas as delimiters. As such, in this case, it tells Less to parse all of our invocation tokens into a single value.

But, that approach is a little bit off-topic. Getting back to our original exploration, we could also explicitly group the tokens with quotes:

.transition( @properties ) {
	transition: @properties ;
}

body {
	.transition( "top 1s ease, left 1s ease" ) ;
}

This parses and generates CSS output; but, it doesn't quite do what we want. Remember, you can't think of that quoted value as a "string" - you have to start thinking about it as a single value that contains quotes. Because, that's how it's rendered:

body {
	transition: "top 1s ease, left 1s ease";
}

Notice that our CSS property value is quoted, which we don't want.

This is where the tilde comes into play. If you precede the quoted value with a tilde, the tokens will still be grouped into a single value, but the quotes will be stripped off.

That is all that the tilde does - strip quotes. It does nothing else. If you use tildes currently, you probably don't actually need them in half the cases. If you're anything like me, you [incorrectly] copy-pasted them from online demos without actually knowing what it was doing.

In our case, we can use the tilde-quote combination to get our CSS working:

.transition( @properties ) {
	transition: @properties ;
}

body {
	.transition( ~"top 1s ease, left 1s ease" ) ;
}

... which compiles down into the CSS:

body {
	transition: top 1s ease, left 1s ease;
}

... which is exactly what we want.

As a side note, I will say that Less CSS is smart enough to not double-up on quotes during variable interpolation. Meaning, if you have a quoted value being interpolated into another quoted value:

body {
	@foo: "hello" ;
	@bar: "@{foo}" ;
	@baz: "@{bar}" ;

	content: @baz ;
}

... Less won't continue to add quotes to the outliers of the value; instead, the surrounding quotes will be collapsed down into a single set:

body {
	content: "hello";
}

Letting go of your more traditional understanding of strings, in Less CSS, can be quite a hurdle; it still trips me up from time to time. But once you embrace the way Less CSS treats tokens, inputs, and quotes, it becomes a lot easier to understand. And the tilde doesn't seem like some magical beast that you use simply because everyone else does.

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

Good Stuff!

I'm searching for an answer to a problem and maybe you can help....

.size(@w, @h, @f:none){
.condition(@vars){
.do(@vars){
@wmm: extract(@w, 1);
@wv: extract(@w, 2);
WHY: ~"@{wmm}-width:@{wv}";
}
}
}

.my{ .size(min 100px, 100px, left); }

-----------------------------------------------------

Why must I define a property for the string value ~"@{wmm}-width:@{wv}" to work? Is there a way around this so that LESS understands this is the property and value.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel