Skip to content

Operators

Operator Precedence

Below is the operator precedence table, highest to lowest:

.
[]
! ~ + -
is defined
** * / %
+ -
... ..
<= >= < >
in
== is != is not isnt
is a
&& and || or
?:
= := ?= += -= *= /= %=
not
if unless
.
[]
! ~ + -
is defined
** * / %
+ -
... ..
<= >= < >
in
== is != is not isnt
is a
&& and || or
?:
= := ?= += -= *= /= %=
not
if unless

Unary Operators

The following unary operators are available, !, not, -, +, and ~.

!0
// => true

!!0
// => false

!1
// => false

!!5px
// => true

-5px
// => -5px

--5px
// => 5px

not true
// => false

not not true
// => true
!0
// => true

!!0
// => false

!1
// => false

!!5px
// => true

-5px
// => -5px

--5px
// => 5px

not true
// => false

not not true
// => true

The logical not operator has low precedence, therefore the following example could be replaced with

a = 0
b = 1

!a and !b
// => false
// parsed as: (!a) and (!b)
a = 0
b = 1

!a and !b
// => false
// parsed as: (!a) and (!b)

With:

not a or b
// => false
// parsed as: not (a or b)
not a or b
// => false
// parsed as: not (a or b)

Binary Operators

Subscript []

The subscript operator allows us to grab a value inside an expression via index (zero-based). Negative index values starts with the last element in the expression.

list = 1 2 3
list[0]
// => 1

list[-1]
// => 3
list = 1 2 3
list[0]
// => 1

list[-1]
// => 3

Parenthesized expressions may act as tuples (e.g. (15px 5px), (1 2 3)).

Below is an example that uses tuples for error handling (and showcasing the versatility of this construct):

add(a, b)
  if a is a 'unit' and b is a 'unit'
    a + b
  else
    (error 'a and b must be units!')

body
  padding add(1,'5')
  // => padding: error "a and b must be units";
  
  padding add(1,'5')[0]
  // => padding: error;
  
  padding add(1,'5')[0] == error
  // => padding: true;

  padding add(1,'5')[1]
  // => padding: "a and b must be units";
add(a, b)
  if a is a 'unit' and b is a 'unit'
    a + b
  else
    (error 'a and b must be units!')

body
  padding add(1,'5')
  // => padding: error "a and b must be units";
  
  padding add(1,'5')[0]
  // => padding: error;
  
  padding add(1,'5')[0] == error
  // => padding: true;

  padding add(1,'5')[1]
  // => padding: "a and b must be units";

Here's a more complex example. Now we're invoking the built-in error() function with the return error message, whenever the ident (the first value) equals error.

if (val = add(1,'5'))[0] == error
  error(val[1])
if (val = add(1,'5'))[0] == error
  error(val[1])

Range .. ...

Both the inclusive (..) and exclusive (...) range operators are provided, expanding to expressions:

1..5
// => 1 2 3 4 5

1...5
// => 1 2 3 4

5..1
// => 5 4 3 2 1
1..5
// => 1 2 3 4 5

1...5
// => 1 2 3 4

5..1
// => 5 4 3 2 1

Additive: + -

Multiplicative and additive binary operators work as expected. Type conversion is applied within unit type classes, or default to the literal value. For example 5s - 2px results in 3s.

15px - 5px
// => 10px

5 - 2
// => 3

5in - 50mm
// => 3.031in

5s - 1000ms
// => 4s

20mm + 4in
// => 121.6mm

"foo " + "bar"
// => "foo bar"

"num " + 15
// => "num 15"
15px - 5px
// => 10px

5 - 2
// => 3

5in - 50mm
// => 3.031in

5s - 1000ms
// => 4s

20mm + 4in
// => 121.6mm

"foo " + "bar"
// => "foo bar"

"num " + 15
// => "num 15"

Multiplicative: / * %

2000ms + (1s * 2)
// => 4000ms

5s / 2
// => 2.5s

4 % 2
// => 0
2000ms + (1s * 2)
// => 4000ms

5s / 2
// => 2.5s

4 % 2
// => 0

When using / within a property value, you must wrap with parens. Otherwise the / is taken literally (to support CSS line-height):

font: 14px/1.5;
font: 14px/1.5;

But the following is evaluated as 14px ÷ 1.5:

font: (14px/1.5);
font: (14px/1.5);

This is only required for the / operator.

Shorthand operators: += -= *= /= %=

Shorthand operators works like other common language. With list variable, the first value will be use to execute the operators and overwrite the list to turn it to a single-value variable. With string, node values only += works as an appending function. With number type value, all operators work exactly like a normal math. Similar for color value.

n = 12
n += 8
// => n = 20

int-list = 12 23 0 32
int-list %= 2
// => 12 % 2 = 0 (mod operator)
// => int-list = 0

mixed-list = node 23 'str'
mixed-list %= 2
// => error

mixed-list = node 23 'str' #2e7
mixed-list += 2
// => mixed-list = node2

s = 'str'
s += 2
// => s = 'str2'

c = #0e0
c -= #0e0
// => c = #000    
n = 12
n += 8
// => n = 20

int-list = 12 23 0 32
int-list %= 2
// => 12 % 2 = 0 (mod operator)
// => int-list = 0

mixed-list = node 23 'str'
mixed-list %= 2
// => error

mixed-list = node 23 'str' #2e7
mixed-list += 2
// => mixed-list = node2

s = 'str'
s += 2
// => s = 'str2'

c = #0e0
c -= #0e0
// => c = #000    

Exponent: **

The Exponent operator:

2 ** 8
// => 256
2 ** 8
// => 256

Equality & Relational: == != >= <= > <

Equality operators can be used to equate units, colors, strings, and even identifiers. This is a powerful concept, as even arbitrary identifiers (such as as wahoo) can be utilized as atoms. A function could return yes or no instead of true or false (although not advised).

5 == 5
// => true

10 > 5
// => true

#fff == #fff
// => true

true == false
// => false

wahoo == yay
// => false

wahoo == wahoo
// => true

"test" == "test"
// => true

true is true
// => true

'hey' is not 'bye'
// => true

'hey' isnt 'bye'
// => true

(foo bar) == (foo bar)
// => true

(1 2 3) == (1 2 3)
// => true

(1 2 3) == (1 1 3)
// => false
5 == 5
// => true

10 > 5
// => true

#fff == #fff
// => true

true == false
// => false

wahoo == yay
// => false

wahoo == wahoo
// => true

"test" == "test"
// => true

true is true
// => true

'hey' is not 'bye'
// => true

'hey' isnt 'bye'
// => true

(foo bar) == (foo bar)
// => true

(1 2 3) == (1 2 3)
// => true

(1 2 3) == (1 1 3)
// => false

Only exact values match. For example, 0 == false and null == false are both false.

Aliases:

==    is
!=    is not
!=    isnt
==    is
!=    is not
!=    isnt

Truthfulness

Nearly everything within Stylus resolves to true, including units with a suffix. Even 0%, 0px, etc. will resolve to true (because it's common in Stylus for mixins or functions to accept units as valid).

However, 0 itself is false in terms of arithmetic.

Expressions (or "lists") with a length greater than 1 are considered truthy.

true examples:

      0% 
      0px
      1px 
      -1
      -1px
      hey
      'hey'
      (0 0 0)
      ('' '')
      0% 
      0px
      1px 
      -1
      -1px
      hey
      'hey'
      (0 0 0)
      ('' '')

false examples:

0 
null
false
''
0 
null
false
''

Logical Operators: && || and or

Logical operators && and || are aliased and / or which apply the same precedence.

5 && 3
// => 3

0 || 5
// => 5

0 && 5
// => 0

#fff is a 'rgba' and 15 is a 'unit'
// => true
5 && 3
// => 3

0 || 5
// => 5

0 && 5
// => 0

#fff is a 'rgba' and 15 is a 'unit'
// => true

Existence Operator: in

Checks for the existence of the left-hand operand within the right-hand expression.

Simple examples:

nums = 1 2 3
1 in nums
// => true

5 in nums
// => false
nums = 1 2 3
1 in nums
// => true

5 in nums
// => false

Some undefined identifiers:

words = foo bar baz
bar in words
// => true

HEY in words
// => false
words = foo bar baz
bar in words
// => true

HEY in words
// => false

Works with tuples too:

vals = (error 'one') (error 'two')
error in vals
// => false

(error 'one') in vals
// => true

(error 'two') in vals
// => true

(error 'something') in vals
// => false
vals = (error 'one') (error 'two')
error in vals
// => false

(error 'one') in vals
// => true

(error 'two') in vals
// => true

(error 'something') in vals
// => false

Example usage in mixin:

pad(types = padding, n = 5px)
  if padding in types
    padding n
  if margin in types
    margin n

body
  pad()

body
  pad(margin)

body
  pad(padding margin, 10px)
pad(types = padding, n = 5px)
  if padding in types
    padding n
  if margin in types
    margin n

body
  pad()

body
  pad(margin)

body
  pad(padding margin, 10px)

Yielding:

body {
  padding: 5px;
}
body {
  margin: 5px;
}
body {
  padding: 10px;
  margin: 10px;
}
body {
  padding: 5px;
}
body {
  margin: 5px;
}
body {
  padding: 10px;
  margin: 10px;
}

Conditional Assignment: ?= :=

The conditional assignment operator ?= (aliased as :=) lets us define variables without clobbering old values (if present). This operator expands to an is defined binary operation within a ternary.

For example, the following are equivalent:

color := white
color ?= white
color = color is defined ? color : white
color := white
color ?= white
color = color is defined ? color : white

When using plain =, we simply reassign:

color = white
color = black

color
// => black
color = white
color = black

color
// => black

But when using ?=, our second attempt fails (since the variable is already defined):

color = white
color ?= black

color
// => white
color = white
color ?= black

color
// => white

Instance Check: is a

Stylus provides a binary operator named is a used to type check.

15 is a 'unit'
// => true

#fff is a 'rgba'
// => true

15 is a 'rgba'
// => false
15 is a 'unit'
// => true

#fff is a 'rgba'
// => true

15 is a 'rgba'
// => false

Alternatively, we could use the type() BIF:

type(#fff) == 'rgba'
// => true                                                                            
type(#fff) == 'rgba'
// => true                                                                            

Note: color is the only special-case, evaluating to true when the left-hand operand is an RGBA or HSLA node.

Variable Definition: is defined

This pseudo binary operator does not accept a right-hand operator, and does not evaluate the left. This allows us to check if a variable has a value assigned to it.

foo is defined
// => false

foo = 15px
foo is defined
// => true

#fff is defined
// => 'invalid "is defined" check on non-variable #fff'
foo is defined
// => false

foo = 15px
foo is defined
// => true

#fff is defined
// => 'invalid "is defined" check on non-variable #fff'

Alternatively, one can use the lookup(name) built-in function to do this—or to perform dynamic lookups:

name = 'blue'
lookup('light-' + name)
// => null

light-blue = #80e2e9
lookup('light-' + name)
// => #80e2e9
name = 'blue'
lookup('light-' + name)
// => null

light-blue = #80e2e9
lookup('light-' + name)
// => #80e2e9

This operator is essential, as an undefined identifier is still a truthy value. For example:

body
  if ohnoes
    padding 5px
body
  if ohnoes
    padding 5px

Will yield the following CSS when undefined:

body {
  padding: 5px;
}
body {
  padding: 5px;
}

However this will be safe:

body
  if ohnoes is defined
    padding 5px
body
  if ohnoes is defined
    padding 5px

Ternary

The ternary operator works as we would expect in most languages. It's the only operator with three operands (the condition expression, the truth expression, and the false expression).

num = 15
num ? unit(num, 'px') : 20px
// => 15px
num = 15
num ? unit(num, 'px') : 20px
// => 15px

Casting

As a terse alternative to the unit() built-in function, the syntax (expr) unit may be used to force the suffix.

body
  n = 5
  foo: (n)em
  foo: (n)%
  foo: (n + 5)%
  foo: (n * 5)px
  foo: unit(n + 5, '%')
  foo: unit(5 + 180 / 2, deg)
body
  n = 5
  foo: (n)em
  foo: (n)%
  foo: (n + 5)%
  foo: (n * 5)px
  foo: unit(n + 5, '%')
  foo: unit(5 + 180 / 2, deg)

Color Operations

Operations on colors provide a terse, expressive way to alter components. For example, we can operate on each RGB:

#0e0 + #0e0
// => #0f0
#0e0 + #0e0
// => #0f0

Another example is adjust the lightness value by adding or subtracting a percentage. To lighten a color, add; to darken, subtract.

#888 + 50%
// => #c3c3c3

#888 - 50%
// => #444
#888 + 50%
// => #c3c3c3

#888 - 50%
// => #444

Adjust the hue is also possible by adding or subtracting with degrees. For example, adding 50deg to this red value results in a yellow:

#f00 + 50deg
// => #ffd500
#f00 + 50deg
// => #ffd500

Values clamp appropriately. For example, we can "spin" the hue 180 degrees, and if the current value is 320deg, it will resolve to 140deg.

We may also tweak several values at once (including the alpha) by using rgb(), rgba(), hsl(), or hsla():

#f00 - rgba(100,0,0,0.5)
// => rgba(155,0,0,0.5)
#f00 - rgba(100,0,0,0.5)
// => rgba(155,0,0,0.5)

Sprintf

The string sprintf-like operator % can be used to generate a literal value, internally passing arguments through the s() built-in:

'X::Microsoft::Crap(%s)' % #fc0
// => X::Microsoft::Crap(#fc0)
'X::Microsoft::Crap(%s)' % #fc0
// => X::Microsoft::Crap(#fc0)

Multiple values should be parenthesized:

'-webkit-gradient(%s, %s, %s)' % (linear (0 0) (0 100%))
// => -webkit-gradient(linear, 0 0, 0 100%)
'-webkit-gradient(%s, %s, %s)' % (linear (0 0) (0 100%))
// => -webkit-gradient(linear, 0 0, 0 100%)