3

I want to combine a class selector like .smart with a [attr^=val] selector, whose attr is a class, like [class^='test-']. I have tested each one of the selectors individually and they seem to work just fine. However, when combined, they fail to produce the desired result.

Example

You can also view the Codepen.

/* Works */

[class^='test-'] {
  background: blue;
}
/* Works */

.smart {
  background: yellow;
}
/* Doesn't work */

[class^='test-'].smart {
  background: red;
}
<div class="test-me">
  <p>Should be blue.</p>
</div>
<div class="smart">
  <p>Should be yellow.</p>
</div>
<div class="smart test-me">
  <p>Should have been red, but isn't.</p>
</div>

Can anyone explain why the CSS selector [class^='test-'].smart does not work and, if possible, how to fix the problem?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Angelos Chalaris
  • 6,611
  • 8
  • 49
  • 75

3 Answers3

7

why the CSS selector [class^='test-'].smart does not work

Because your class attribute doesn't start with test-. It starts with smart. If it did start with test-, like so:

<div class="test-me smart">
  <p>Should be red.</p>
</div>

then it'd match.

how to fix the problem

You need an additional selector for when the attribute doesn't start with test-. As described here:

[class^='test-'].smart, [class*=' test-'].smart {
  background: red;
}
<div class="smart test-me">
  <p>Should be red.</p>
</div>

<div class="test-me smart">
  <p>Should also be red.</p>
</div>
Community
  • 1
  • 1
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • Ah you answered faster than me ^^ – J'hack le lezard Jan 06 '17 at 17:00
  • 1
    @Harry: Honestly, I might as well just mark the question as a duplicate. Shouldn't have to jump through too many hoops to go from type selector in my original answer to class selector in this question. – BoltClock Jan 06 '17 at 17:01
  • 1
    This is exactly the solution I was looking for. I feel a bit dumb for not realizing that **starts with** actually means *starts with* instead of *contains substring that starts with*, but I can see how this is quite obvious now. Thanks for the help! – Angelos Chalaris Jan 06 '17 at 17:02
  • But for the sake of completeness you should point out that this entire design approach is flawed. It is not a good idea to build internal structure into class names that you then try to pick apart using attribute selectors in this fashion. –  Jan 06 '17 at 17:03
  • @torazaburo well, I have about 20 classes starting with the same string and being different by a couple of digits at the very end (building a grid system), so I want certain things to be the same no matter the final two digits. So I am trying to save some space using this selector combination. – Angelos Chalaris Jan 06 '17 at 17:06
0

You need to change your CSS in this format:-

Just need to change ^ by *

[class*='test-'].smart {
  background: red;
}
Vishal Thakur
  • 1,564
  • 16
  • 25
  • This will match `class="foo-test-bar"`. –  Jan 07 '17 at 06:29
  • Whether or not I voted or how is irrelevant. I don't need to check your code. I can tell just by looking at it that it will select an element with class 'foo-test-bar`, which it **should not**. –  Jan 07 '17 at 11:23
  • @torazaburo what is wrong in my code. did you check it before voting. I just answered as above question required. – Vishal Thakur Jan 07 '17 at 11:29
  • In the asked question there is no element with class 'foo-test-bar`. – Vishal Thakur Jan 07 '17 at 11:31
  • In the question "how do I compute the square of a number", and the test data provided uses the number "2", I would hope that your solution would permit handling the case of 3 as well, and not assume that the desired solution work only on the number 2. Any solution should work for a reasonable of set of test data, not just the one specific set of test data that was given. For this to inadvertently select `foo-test-bar` is a bug in your solution, which makes it incorrect, and there are easy solutions which do not have that bug. –  Jan 07 '17 at 11:38
0

This is not a direct answer to your specific question. Other answers are fine.

However, I would not recommend this approach of giving classes names with internal structure, which you then have to pick apart using the attribute selectors. Attribute selectors provide you with ability to find any attribute whose value starts with a string (^=), contains a string (*=), contains a token somewhere (~=), or whose value is a prefixed string (^=), but not to find an attribute whose a value is a list of tokens any of which has a particular prefix, which is what you want. Therefore, as explained in other answers, you are forced to say

[class^="test-"], [class*=" test-"]

That's ugly and not very dry. In addition, in case you are worried about performance, it's going to be a lot more work for the CSS engine. In contrast, CSS engines are highly optimized to do regular class matching.

Therefore, I would recommend that you adopt the approach of separate classes, test and me. The HTML would obviously look like <div class="test me">. The CSS would specify .test { } for all properties common to all flavors of test, and .test.me { } for properties specific to me.

If you are worried that me might conflict with a similarly-named class somewhere else in your code, then you could "namespace" it as "test-me", and specify the HTML as <div class="test test-me">, and the CSS as .test { } and .test-me { }.

Yes, this requires a few more bytes in your HTML. But the cost of downloading those after zippping and caching even for a million users over the course of a year is likely to be absolutely negligible.

But the fact remains that test test-me is a little bit more wordy than just test-me in your HTML. However, I would make the case that the former is ultimately more readable and semantic. Consider the poor guy who takes over your code a year from now. If he wants to find what styles are being applied to test-me, he is likely to search the CSS for classes of the form .test-me. He may not stumble across the other rule [class^="test-"], [class*=" test-"] which is affecting this class="test-me" element.

If you look at Bootstrap, notice that they do not use attribute selectors for classes such as col-md-3. That would be far too inefficient. Instead, the rules applied at run time explicitly name all the possibilities:

.col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9 {
  float: left;
}

Of course, in the case of Bootstrap, they do not write out all those classes in the source code; they use SASS to generate them at compile time. That is also an option you could consider, if you're willing to use SASS or some other pre-processor, or already do so.

Another useful reference point is icon libraries which allow you to specify icons with something like

<i class="icon-cloud"></i>

Some of them do use CSS rules of the type you are proposing, but they assume that there will be only one class specified, and so they can get away with just a single rule

[class^="icon-"]

which is going to be at least not quite so bad performance wise. However, other icon libraries make you explicitly specify another class just indicating icon-ness by itself:

<i class="icon icon-cloud"></i>

This allows them to write their CSS for properties common to all icons simply as

.icon { font-family: MyCoolIconFont; }

which will be as fast as it could possibly get.