40
votes

Is it possible, in Sass, to manipulate a value a given element already inherits?

I am aiming for something like this:

body
  color: blue
  .warning
    color: red

strong
  color: darken(inherit,20)
4
That would be a great feature but sadly no, it cannot be done without using variablesAhmad Alfy
Did you try using variables to see what happens?cimmanon
I tried to do this today. Sadly, no.Mike Causer
Excellent question, been wondering about how to do this for a while myself. I'll chime in if I figure out something.mystrdat
One of my wishlist features for CSS is for this to be built-in. It's silly that if a box-shadow declaration, for example, has to wipe out all previous box shadows. It would be great if you could overwrite or append. If this is ever added to a Spec, someone let me know!JKillian

4 Answers

26
votes

Inheritance

No. Sass doesn't 'know' what selector to inherit the color from. It would have to know that strong is a descendant of body. That seems like a reasonable enough assumption for you and I since strong is not allowed outside of the body, but that sort of assumption cannot be made about most selectors. Sass would also have to know that there are no cascades happening from other ancestor elements.

ul {
    color: red;
}

ol {
    color: blue;
}

li {
    // which color do I inherit from ????
}

Well can I specify which selector I want to copy from?

Sass does not grant access to the values of any previously declared variables in any fashion, either. There is no way to specify "be darker than the body's color". CSS rules are not objects or mappings and are not accessible in any way. Your case may be simple, but consider a more complex case like this:

.foo {
    background: mix(white, blue); // fallback for non-rgba browsers
    background: rgba(blue, .5);

    .baz & {
        background: yellow;
    }

    @media (min-width 30em) {
        background: orange;
    }

    @supports (flex-wrap: wrap) {
        background: red;
    }
}

.bar {
    // access which background color from .foo ????
}

Well what can I do?

You'll either need to use variables or it has to be a feature of vanilla CSS to do what you want.

Old-Fashioned CSS

Some properties can give the illusion of being generated/inherited dynamically using stuff that's been supported by browsers for years:

ul.one {
  background: white;
}

ul.two {
  background: yellow;
}

ul {
  background: rgba(0, 120, 255, .2);
  padding: 1em;
}
<ul class="one">
  <li><ul>
    <li><ul>
      <li>Foo</li>
    </ul></li>
  </ul></li>
</ul>

<ul class="two">
  <li><ul>
    <li><ul>
      <li>Foo</li>
    </ul></li>
  </ul></li>
</ul>

CSS Variables

Generating CSS variables is about as close as you're going to get to being able to manipulate an inherited property. Browser support isn't quite there yet (check caniuse), but here's what that would look like:

Sass:

ul {
  --list-color: orange;
  --darker-color: darken(orange, 15%);
  color: var(--list-color);
}

ol {
  --list-color: green;
  --darker-color: darken(green, 10%);
  color: var(--list-color);
}

li {
  background: var(--darker-color);
}

Output:

ul {
  --list-color: orange;
  --darker-color: #b37400;
  color: var(--list-color);
}

ol {
  --list-color: green;
  --darker-color: #004d00;
  color: var(--list-color);
}

li {
  background: var(--darker-color);
}
<ul>
  <li>Foo</li>
</ul>

<ol>
  <li>Bar</li>
</ol>

If you're using a browser that supports CSS variables, the result should look like this:

enter image description here

9
votes

I was looking for the same thing, and came across this. Your question was answered, but it didn't solve the problem.

Here's the solution: http://codepen.io/monsto/pen/tiokl

If your HTML was this:

    <div class="main">
      <header class="header">
        <div class="warning">
          <p><strong>Danger,</strong> Will Robinson!</p>
        </div>
      </header>
    </div>

Then using SASS you could do this:

$bg: #f88;

@mixin colorize {
  $bg: darken($bg,15%) !global; // !global flag required for 3.4 or later, remove if using 3.3 or older
  background: $bg;
}

.warning {
  background: $bg;
  p {
    @include colorize;
    strong {
      @include colorize;
    }
  }
}

SASS seems to have no idea of the results of it's output. Therefore, inherit means nothing to it. You're basically asking it to know what the output is before it's output.

It does however know it's variables as, by default, they're tightly scoped. From the docs:

Variables are only available within the level of nested selectors where they’re defined. If they’re defined outside of any nested selectors, they’re available everywhere.

AND THEN variables in mixins:

The block of content passed to a mixin are evaluated in the scope where the block is defined, not in the scope of the mixin.

This allows the above mixin to take a known variable, defined in the parent level, and redefines it for the current level and available to it's children. It's like doing $x = $x + 1 inside a multi-nested loop

TBPH, this rather changes the way I think about SASS. It's clearly a lot more programmatic than I thought.

9
votes

Given that an element cannot have multiple of the same properties that combine and the fact that inherit can't know what the current rendered state is, your options are to

1) Keep track of the past transforms yourself using SASS variables: Demo

.parent {
  $firstTrans: translateX(50%);
  transform: $firstTrans;

  .child {
    /* Old followed by new */
    transform: $firstTrans rotate(10deg);
  }
}

2) Apply the transform to a parent (perhaps adding another container if needed): Demo

3) Use Javascript to combine the current transform with the one you want to add (this is the only way you can make sure to remove the transform applied to the parent if that's desired): Demo

Note: This answer is from a merged post because of this meta post.

0
votes

This answers addresses the darken function specifically: A possible alternative is using the CSS brightness() filter instead of SASS's (or LESS's) darken() function. You will basically need to wrap the color inside a span tag so the filter would not affect other elements.

Simple demo:

.red {color: red}
.blue {color: blue}
.green {color: green}

span {
  display: inline-block;
  padding: 1em;
}

.darken span {
  -webkit-filter: brightness(0.4);
  filter: brightness(0.4);
}
<span class="red">Red</span>
<span class="blue">Blue</span>
<span class="green">Green</span>

<div class="darken">
  <span class="red">Red</span>
  <span class="blue">Blue</span>
  <span class="green">Green</span>
</div>

jsFiddle: https://jsfiddle.net/azizn/hhorhz9s/

You need to keep in mind browser compatibility, it should work for IE Edge, latest Firefox and Chrome. See caniuse or MDN for more information.

In the case of a background darken, you could use a pseudo selector with opacity or add a semi-transparent black PNG background-image.