Introduction

Now that you’ve gotten the hang of defining UI patterns, let’s jump into some of the more advanced features of working with a pattern definition. In this section, we’re going to discuss property references, or the ability to reference another property value within the definition.

Getting to know this()

Let’s start off with the simplest case. We have a definition with a color property on it, and we want to reference this in another property (let’s say border-color).

This is as simple as using special this() method:

@include restyle-define(button, (
  color: blue,
  border-color: this(color)
));

At the time the definition is evaluated, the value for border-color will resolve to blue, by referencing the color property.

This is pretty useful, but it gets even more powerful when you apply it across modifiers.

Let’s take a look at a slightly more complex example:

@include restyle-define(button, (
  color: blue,
  border-color: this(color),
  restyle-modifier(secondary): (
    color: gray
  )
));

.btn {
  @include restyle(button);
  &.secondary {
    @include restyle(secondary button);
  }
}

Can you guess what this will output? If you guessed the following, you were right!

.btn {
  color: blue;
  border-color: blue;
}
.btn.secondary {
  color: gray;
  border-color: gray;
}

Even though the secondary modifier never changed the border-color value, it updates automatically because it’s a value is linked to color. If color changes, so does border-color.

this, root, and parent

this(...) can reference any property within the current context. We saw above that we can reference a property across modifiers, but what happens when you introduce a new selector (or state) context?

@include restyle-define(button, (
  color: blue,
  '.foo': (
    border-color: this(color)
  )
));

This will throw an error that it can’t resolve the value for this(color). So what’s going on here?

reSTYLE helpfully creates a new selector context here, so the properties of the parent do not leak into the child selector context.

Let’s expand on this example a bit:

@include restyle-define(button, (
  color: blue,
  '.foo': (
    color: this(color),
    border-color: this(color)
  )
));

Make more sense why the previous example would throw an exception? This will also throw an exception that this(color) cannot be resolved. In this case, we get into an infinite loop where color within .foo can never resolve because it is referencing itself.

This is where root() and parent() come in.

root

root will reference a property on the top most selector context.

@include restyle-define(button, (
  color: blue,
  '.foo': (
    color: root(color), // get the color value from the root color
    border-color: this(color)
  )
));

This works now! The color local to .foo now resolves to the root color (blue). border-color‘s reference to this(color) now resolves to the color local to .foo.

parent

parent is similiar, but it explicitly looks in the parent selector context. In this case, parent and root can be used interchangeably, because root is also the parent.

But if you had multiple parent contexts, you could target a specific property from one of them:

@include restyle-define(button, (
  color: blue,
  '.foo': (
    color: red,
    '.bar': (
      color: parent(color) // red
    )
  )
));

You can also chain parent() lookups:

@include restyle-define(button, (
  color: blue,
  '.foo': (
    color: red,
    '.bar': (
      color: parent(parent(color)) // blue
    )
  )
));

Up next - variables

Property references are pretty slick, but just wait until you check out variables.