# A Bulletproof Function to Validate Length Values in Sass

By Kitty Giraudel
Blogs
Share:

Once you start getting comfortable with Sass, playing with some functions and writing your own mixins, soon enough you will want to validate developer input. This means adding some extra checks to your functions and mixins to make sure the given arguments are correct in terms of how they are going to be used within the function or mixin.

After having seen a dozen different functions supposedly aimed at validating a length, I thought I would give it a try and write something on it. So here we are.

Let’s first recall what a length is according to official CSS specifications:

Lengths refer to distance measurements […] A length is a dimension. However, for zero lengths the unit identifier is optional (i.e. can be syntactically represented as the ‘0’). A dimension is a number immediately followed by a unit identifier.

If we sum up what we just read, a length is a number followed by a unit, unless it’s zero. It’s a bit simplistic but we’ll start with that.

## Setting Up the Function

If a function returns a Boolean, it is usually good practice to name the function with a leading `is-`. By the very nature of the question is [this] [that]?, we imply that the answer is either yes (`true`) or no (`false`).

In our case, we want to make sure the given value is a length, so I suggest `is-length`, in all its simplicity.

``````/**
* Check whether `\$value` is a valid length.
* @param {*} \$value - Value to validate.
* @return {Bool}
*/
@function is-length(\$value) {
// Do some magic and return a Boolean.
}``````

## Returning a Valid Value

Thanks to our refresher we know that a length is a number with a unit. Let’s start with that.

``````@function is-length(\$value) {
@return type-of(\$value) == "number" and not unitless(\$value);
}``````

Before going any further, let me remind you of a couple of things:

• We return a Boolean, so there is no need to pollute the function’s core with unnecessary `@if`, `@else if` and `@else` directives.
• We quote strings (using double quotes) because it’s more elegant and because an unquoted string is strictly equal to the same quoted string anyway.

Okay, that’s a good start. Unfortunately, most Sass frameworks that do unit validation will stop there. We cannot stop after such a good start! Also, we failed to implement correctly the CSS specification that if the value is zero, the unit is optional. Our function will return `false` if `\$value` is `0` because it is unitless. Let’s fix it!

``````@function is-length(\$value) {
@return \$value == 0 or type-of(\$value) == "number" and not unitless(\$value);
}``````

The `or` (`||`) operator is implemented like this in most languages: Keep parsing the condition until a token evaluates to `true`. That means if `\$value` is `0`, the function stops parsing and directly returns `true`, otherwise it keeps going.

Note that we could have written our condition like this as well (where parentheses are included for readability):

``````@function is-length(\$value) {
@return type-of(\$value) == "number" and (not unitless(\$value) or \$value == 0);
}``````

Our first implementation reads as if `\$value` is `0` or `\$value` is a number with a unit. While this implementation reads as if `\$value` is a number and either has a unit or is `0`. You can pick the one you feel the most comfortable with.

## Allowing Common Values

You are probably not without knowing that there are a couple of values that can apply to all CSS properties: `initial` and `inherit`. We can also add `auto` to the list because most length-based properties do accept `auto` as a valid value. So we have three values that we want to allow, four if we count `0` that we already added previously. Let’s start with the ugly way:

``````@function is-length(\$value) {
@return \$value == 0
or \$value == "auto"
or \$value == "initial"
or \$value == "inherit"
or type-of(\$value) == "number"
and not unitless(\$value);
}``````

Wow… Not really elegant, is it? Thankfully, in a previous article here at SitePoint I showed you how we can optimise such a statement using the `index` function.

The `index` function returns the index of the second parameter (our value) in the first parameter (a list). If the value isn’t found in the list, it returns `null` in Sass 3.4 or `false` in Sass 3.3.

``````@function is-length(\$value) {
@return index(0 "auto" "inherit" "initial", \$value)
or type-of(\$value) == "number"
and not unitless(\$value);
}``````

The thing is, our `is-length` value can now return an integer. For instance, if `\$value` is `auto`, then the function returns `2`, because `auto` is the second item in our list of allowed values. While it shouldn’t be a big deal if you use `@if is-length(\$value)`, there might be cases where you want to have a Boolean at all times.

In order to make sure the function returns a `bool` type, you can use the not-not trick (seen in the same article as above) to cast the value as a Boolean. While this is not really elegant, it works fine:

``````@function is-length(\$value) {
@return not not index(0 "auto" "inherit" "initial", \$value)
or type-of(\$value) == "number"
and not unitless(\$value);
}``````

If you want to make this more explicit, you can build a little `contains` function that hides this:

``````/**
* Check whether `\$list` contains `\$value`.
* @param  {List} \$list  - List of values.
* @param  {*}    \$value - Value to check in the list.
* @return {Bool}
*/
@function contains(\$list, \$value) {
@return not not index(\$list, \$value);
}``````

…then:

``````@function is-length(\$value) {
@return contains(0 "auto" "inherit" "initial", \$value)
or type-of(\$value) == "number"
and not unitless(\$value);
}``````

Both clear and eloquent, but needs an extra function. I’ll let you decide which is best for your needs.

Note: remember that `auto` is an invalid value for `padding`. Quite an edge case, but you should keep that in mind.

## Allowing `calc()`

Our function is starting to get somewhere, isn’t it? However, we still need to allow `calc()`, which is often forgotten or omitted from length checkers. The thing with checking for `calc()` is that it requires Sass 3.3, because we need the `str-slice` method.

The idea is quite simple. We slice the first four characters from the value. If the result is “calc”, then it (likely) means the value is a `calc()` function, which is valid:

``````@function is-length(\$value) {
@return not not index(0 "auto" "initial" "inherit", \$value)
or type-of(\$value) == "number" and not unitless(\$value)
or str-slice(\$value + "", 1, 4) == "calc";
}``````

Two things to note here:

1. We use `\$value + ""` to cast the value as a string in case it’s a number (e.g. `42px`) to avoid an error with the `str-slice` function expecting a string.
2. We put this check at the very end to perform it only when needed since it is likely to be both less frequent and more costly than the other two.

The function looks pretty good so far!

## Extending to Sizing

At this point we have quite a robust function to validate lengths. We could use it in a sizing mixin like this:

``````/**
* Set `width` and `height` in a single statement.
* @param {Number} \$width - Width.
* @param {Number} \$height (\$width) - Height.
* @output `width` and `height`.
* @requires {function} is-length
*/
@mixin size(\$width, \$height: \$width) {
@if is-length(\$width) {
width: \$width;
}

@else {
@warn "Invalid length `#{\$width}` for `\$width` parameter in `size` mixin.";
}

@if is-length(\$height) {
height: \$height;
}

@else {
@warn "Invalid length `#{\$height}` for `\$height` parameter in `size` mixin.";
}
}``````

If you try it, you’ll see it works pretty well. I should note that we might have a problem if we try to use intrinsic sizing. In case you are not familiar with that concept, intrinsic sizing involves new valid values for both `width` and `height` properties in order to give more control to developers over the size of their elements.

The thing is, we cannot update our function to include intrinsic values since they are invalid for most properties using lengths (`padding`, `margin`, `background-size`, and so on). What we could do, however, is make another function using the one we already built. What about `is-size`?

``````/**
* Check whether `\$value` is a valid size.
* @param {*} \$value - Value to validate.
* @return {Bool}
* @requires {function} is-length
*/
@function is-size(\$value) {
@return is-length(\$value)
or not not index("fill" "fit-content" "min-content" "max-content", \$value);
}``````

…or if you use the `contains` function as well:

``````@function is-size(\$value) {
@return is-length(\$value)
or contains("fill" "fit-content" "min-content" "max-content", \$value);
}``````

Now we can safely use intrinsic sizing values with our brand new `is-size` function without fearing a falsy return! Pretty neat, right?

## Final Thoughts

As I’ve shown in this article, to build a bulletproof function to validate lengths, you have to make sure you cover the most frequent edge cases (`calc()`, intrinsic sizing, etc).

We could still improve the function by filtering angles (`deg`, `rad`, `grad`, `turn`) and durations (`s`, `ms`) because they are currently considered as lengths by our function since they are basically a number with a unit. But since lengths, angles, and durations are used in very different contexts, there’s very little chance that the function will get passed an angle when it’s expecting a length.

Covering edge cases is nice, but covering everything including the most unlikely scenario is sometimes excessive.

You can find the full code to play with on this Sassmeister. ### Kitty Giraudel

Non-binary accessibility & diversity advocate, frontend developer, author. Real life cat. They/them. 