Short version:
Using Chrome, I noticed that the selector
#main-content p:first-of-type::first-letter:not(.subhead) { ... }
seems to fail to apply first-letter styles regardless of the class of the paragraph, but if I place the :not pseudo-class earlier, as in either of
#main-content p:first-of-type:not(.subhead)::first-letter { ... }
#main-content p:not(.subhead):first-of-type::first-letter { ... }
then it works as I would expect. Is this an issue in Chrome, or is there some aspect of CSS selectors which explains this, that I just don't understand?
(Additionally, if it's not just an issue in Chrome, can someone suggest a better title for this question?)
Long version:
The short version seems like a good self-contained question to lead with, but there's some more complicated background info which might have its own interesting ramifications. (And which might warrant its own separate question, or be another bug. Help very much appreciated???)
Originally I was trying to apply initial drop-caps to the first paragraph in a div and any subsequent paragraphs that immediately followed a horizontal rule, unless they were part of a subheading. Here's a highly-abbreviated mock of the page structure:
<div id="main-content">
<section class="panel">
<header>
<h1>Welcome Friend!</h1>
<p class="subhead">This is a subheader! (0)</p>
</header>
<div class="customhr"></div> <!-- consciously NOT an hr tag -->
<p>Here's a paragraph (1)</p>
<hr>
<p>First paragraph of a new content section (2)</p>
<p>And another paragraph. (3)</p>
</section>
</div>
And the extremely-abbreviated CSS:
#main-content p:first-of-type::first-letter,
#main-content hr + p::first-letter {
/* Drop-cap styles */
}
.subhead::before {
/* Indent sub-headings with a cute fleuron */
content: "\00a0\00a0\2767\00a0";
}
While testing in Firefox, this resulted in paragraphs (1) and (2) having drop-caps, which is what I desired. In Chrome however, the fleuron from the ::before rule for paragraph (0) also has the drop-cap styles applied. I'm fairly confident Firefox is in error here, as :first-letter should match text from the ::before pseudo-element if it is present, but instead the ::before causes :first-letter not to match anything.
In trying to fix it for Chrome, I added the :not pseudo-class to the end of the first selector (see the first CSS rule in the short version). In Firefox, ironically, this produces the desired effect just as before, but in Chrome, this now caused paragraph (1) to no longer have the drop-cap styles applied. That, I have no explanation for, but I determined it has nothing to do with the .subhead element; Chrome apparently just never applies the rule, and paragraphs following a horizontal rule are the only ones that get styled. But if I move the :not pseudo-class earlier in the rule, as in the two alternatives I gave in the short version, it once again works as expected.
I feel as though I somehow simultaneously stumbled onto two different bugs in Firefox and Chrome, but the use of pseudo-classes and pseudo-elements is slightly arcane to me and I'm not fully confident with them. So I'm curious a) whether there's anything in-spec that would explain this CSS behavior in either browser as being correct, and b) if not, whether these are already known bugs with consistent behavior that has been documented somewhere. [And c) if not, let me know whether you plan to report said bugs so that I don't accidentally file a duplicate.]
::first-letter and ::after/::before do interact in Firefox, but only when the first character is a letter, a number or some punctuation. Other symbols (like the fleuron) seem to be ignored by ::first-letter whether ::after/::before are present or not (JSFiddle).
Chrome is IMO right regarding the relationship between :not() and pseudo-elements. The documentation says that a pseudo-element must always appear at the end (that's why the first selector from the short version doesn't work):
Only one pseudo-element may appear per selector, and if present it
must appear after the sequence of simple selectors that represents the
subjects of the selector
Related
This is impossible to Google for because every article talking about the :before and :after pseudo-elements seems to use the word 'content'.
I heard about it in this CSS-Tricks article, explaining how to implement an image slider as an example use-case for web components. The code example it appears inside is thus:
CSS
#slides ::content img {
width: 25%;
float: left;
}
HTML
<template>
...
<div class="inner">
<content select="img"></content>
</div>
</template>
It seems to be referring to this <content> tag, which is used to allow the user to include Web Components, but I would love to understand this more deeply.
EDIT:
After reading further, in the aforementioned article, I discovered a link the author's "Shadow DOM CSS Cheatsheet" which includes a passage that explains what the ::content pseudo-element is:
Selects distributed nodes inside of an element. Needs to be paired
with polyfill-next-selector for browsers that do not support the
native selector.
::content h1 {
color: red;
}
Source: http://robdodson.me/blog/2014/04/10/shadow-dom-css-cheat-sheet/
This is helpful, but I still find the whole affair rather opaque. Any additional insights?
The ::content pseudo-element is being replaced in future implementations of Web Components / Shadow DOM with the ::slotted pseudo-element. Likewise, the element targeted by this pseudo-element has changed from <content to <slot> in the latest version of the Shadow DOM specification. You can see related discussion about that change here.
Currently browsers still support <content> and ::content.
Original answer:
Summary:
::content is essentially a way to dig deeper and style descendants of the ShadowHost, which normally aren't available to be styled, because your CSS doesn't know to look for the ShadowDOM fragment without ::content.
This answer assumes you are at least somewhat familiar with the <template> element and Web Components, specifically the ShadowDOM, which deals with ShadowTrees and their two main elements, ShadowHost and ShadowRoot.
Note - As of this writing, there is less than 50% support (even prefixed, off-by-default support) for Web Components across the five major browsers. While all modern browsers support <template>, only recent versions of Chrome and Opera support the ShadowDOM fully; with Firefox supporting parts of it after you toggle the requisite feature in about:config (dom.webcomponents.enabled) to true.
The goal of using the ShadowDOM is similar to MVC's separation of concerns. That is, we want to separate our content from our presentation and allow for encapsulated templates in our code to help make it more manageable. We have this already in various programming languages, but it's remained a problem for some time in HTML and CSS. Further, there can be conflicts with class names when styling elements in web apps.
Normally, we interact with the LightDOM (a sort of "Light Realm"), but sometimes it would be helpful to take advantage of encapsulation. Crossing into this sort of "Shadow Realm" (part of Web Components) is a new method to prevent the problems mentioned above by allowing encapsulation. Any styles applied to markup in your ShadowTree won't apply to markup outside of your ShadowTree, even if the exact same classes or selectors are used.
When the ShadowTree (which lives in the ShadowDOM) has a tree from the LightDOM distributed within it, and/or when the ShadowTree is rendered, the result is converted by the browser into what is called a composed tree.
When the browser renders your code, content is being distributed and inserted at new locations other than where it was physically typed. This distributed output is what you see (and what the browser sees), and is called the composed tree. In reality, the content is not originally typed in the order that it now appears, but you won't know this, and neither will the browser. This separation between "end result" and "original code", if you will, is one of the main benefits of encapsulation.
Web Components & the Future of CSS is a great 40-minute video on Web Components and specifically the ShadowDOM, pointed out to me by ZachSaucier.
Specific to your question, the ::content pseudo element applies to what are called distributed nodes. A distributed node is another term for whatever you put within the <content></content> tags. The content is distributed from its place in the original markup to wherever you have placed your <content> tags in the template.
So, when you need specificity in CSS, one way you can handle selectors normally is that you go to the parent element and add that in as part of the selector. Ex: if .container {} is not specific enough, you might use div .container {} or .main .container {} in order to make your selector work.
Thinking about the point of the ShadowDOM, which is scoping and encapsulation, you have to realize that this new ShadowTree you've created is a completely new (discrete) DOM fragment. It's not in the same "Light Realm" as the rest of your content; it's in a "Shadow Realm". So, how does the CSS know to target this "Shadow Realm"? By using the ::content pseudo-element!
The ::content pseudo-element selector acts as the parent element of distributed nodes.
HTML5Rocks has a great sequence of tutorials here, here, and here which cover more information and give some great examples (be sure to visit with Chrome or Opera until more browsers support these features).
For example, see this modified and improved (by Leo) version of the code from HTML5Rocks:
var div = document.querySelector('div');
var root = div.createShadowRoot();
var template = document.querySelector('template');
root.appendChild(template.content);
<template>
<style>
h3 { color: red; }
content[select="h3"]::content > h3 { color: green; }
::content section p { text-decoration: underline; }
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
<content select="section"></content>
</template>
<div>
<h3>Light DOM</h3>
<section>
<div>I'm not underlined</div>
<p>I'm underlined in Shadow DOM!</p>
</section>
</div>
Also available on JSFiddle (Remember to visit in a WebKit-based browser like Chrome or Opera)
Here you can see that the ::contentsection p pseudo element is first selecting the content of the ShadowRoot, which is the contents of the div element in your markup, and then specifying further by adding section p.
This may seem unnecessary when compared to normal CSS selector usage (for example, why not just use section p {}?), until you recall that, when traversing a ShadowTree, you cannot normally select descendants of host elements (which distributed nodes are), because they are in the "Shadow Realm" I mentioned earlier.
Too bad! Unfortunately ::content is v0, and was deprecated.
You should now use the v1 ::slotted.
Also, <content> was deprecated in favor of <slot>.
Please see: https://hayato.io/2016/shadowdomv1/
Also see: Web Components - why <content> was replaced with <slot>
My goal is to create rows of lines (with line numbers) such that:
line numbers line up with the appropriate line even if each "line" exceeds its parent element's width and wraps to a new physical line
only the text is selected, not the line numbers
whitespace is preserved both within and before each line
I've got a snippet that accomplishes some of these goals on specific browsers:
.code { counter-reset: line; }
.line { counter-increment: line; white-space: pre-wrap; }
.line:before { content: counter(line) " - "; }
<div class="code">
<div class="line">Line 1</div>
<div class="line"> Line 2</div>
<div class="line">Line 3</div>
</div>
Here's the problem:
everything works in Chromium
everything works in Firefox except for the third goal - whitespace at the beginning of the line is truncated for some reason
everything works in IE except for the second goal - line numbers are included in the selection
(I don't have access to a Mac, so I have no idea about Safari. I'm too lazy to test Opera.)
Is there a way to make this work in all common browsers?
Short answer: you probably can't get IE & FF to behave the same as Chrome, or even each other.
Opera is essentially Chrome at this point (even using Google's engine, not Webkit), so consider it tested if Chrome works.
Longer info i found...
This question reveals two bad behaviors from the other two browsers:
MSIE:
It's natively selecting generated content in the ::before pseudo-element. By my understanding (and that of the other browsers) the rules say it's NOT supposed to: it's not part of the DOM, it's generated by CSS.
There's an HTML attribute unselectable="on" which generally gets IE to stop selecting text. However, if you open this question in IE, you'll see you can select the "unselectable" text if you start on the block before & continue dragging past it, or right-click inside the box & choose "Select All" then Copy. So it's not even mildly bullet-proof. Besides, an attribute doesn't help you protect a pseudo-element.
There's CSS user-select:none (and vendor prefixes), but when trying it in a JSFiddle with IE, i saw no effect.
The last option for IE is Javascript: onselectstart="return false", which is the perfect chainsaw for clipping coupons.
Firefox:
It doesn't copy formatting, even when CSS dictates the display should show white-space. i saw a Bugzilla issue on this dating back to 2001(?!??!!), with comments over the last 14 years arguing why pre white-space should be preserved to the clipboard, but Mozilla mostly ignoring it.
i have an add-on to copy as plain-text, but i believe it strips markup from a Firefox-originated copy, so the add-on doesn't receive the white-space either.
There's an about:config option that tells Firefox whether to take the space next to a word when double-clicking to select it, but it's utterly useless here. i mention it only to exclude it. :(
For some time now I'm using a little trick that I thought was smart.
That is combining the same css selector to add specificity to the rule's selector.
CSS Specs do mention :
Note: Repeated occurrances of the same simple selector are allowed and
do increase specificity.
http://www.w3.org/TR/css3-selectors/#specificity
For example if HTML is
<body>
<section id="main">
<header class="titles">
<h2>Title red</h2>
<h2 class="blue">Title blue</h2>
</header>
<h2 class="blue">Title blue</h2>
</section>
</body>
And CSS
#main .titles h2{
color: red;
}
#main .blue.blue{
color: blue;
}
This way I can use the class .blue to override styles, event in the header...
(I'm doing this because I hate using !important. To me it should be avoided at all costs.)
First selector weighs 0111 (1 id, 1 class, 1 element)
Second selector weighs 0120 (1 id, 2 classes)
Sometimes I do it with IDs. And it works... in real browsers...
This selector :
#main#main .blue{}
should weigh 0200, as it's got 2 IDs right?
Well IE9 (didn't try others) does not interpret multiple identical IDs in selectors.
This selector won't override #main .titles h2{} in IE9...
IE's css console shows a computed selector equal to #main .blue and removes the second occurence...
Why is that?
To me this is just another IE implementation "bug".
As #BoltClock suggested, I filed a report here :
https://connect.microsoft.com/IE/feedbackdetail/view/958790/repeated-occurrences-of-the-same-simple-selector-should-increase-specificity-even-with-ids
Yes, judging by the behavior shown in F12, this is definitely a bug. It's also a violation of the spec, if you interpret "do increase specificity" as "must increase specificity". This issue seems to only affect ID selectors. Class selectors, attribute selectors and pseudo-classes are OK.
This appears to have been reported before as when I search Microsoft Connect, it turns up an existing report, but I can't view it for some reason. The issue is still present in IE11; if you can't view the report either, feel free to file another one.
I have a fixed footer on my website and in order to create space between it and the main contents I gave the latter a bottom margin. This works fine in Chrome and Firefox.
The rule I used for this is
div#wrap>div:last-child{
margin-bottom:45px;
}
However, IE8 does not seem to respect this (very basic!) CSS rule as shown below.
Insofar as IE8 provides any errors/warnings whatsoever, it doesn't mention anything about this rule being problematic. Increasing the margin-bottom has no effect so the rule seems to be completely ignored. Does anybody understand why? And what is a sound workaround for this?
:last-child is a css3 selector and will not be applied in ie8 if used.
First alternate solution(not recommended)
:first-child+(all the tags until the end) //:first-child+div+div+td+....
{
margin-bottom:45px;
}
Option 2(Recommended)
Give a class to the last child and add your css styling
HTML:
<p>Hover</p>
CSS:
p::after {
content: " here";
transition: all 1s;
}
p:hover::after {
font-size: 200%;
color: red;
}
Live demo: http://jsfiddle.net/SPHzj/13/ (works in Firefox and Chrome)
As you can see, I've set up CSS transitions on the ::after pseudo-element of the paragraph. Then, when the paragraph is hovered, two new styles apply for the pseudo-element which are transitioned.
This works in Firefox and Chrome, but not in IE10. My reasoning was that IE doesn't understand the p:hover::after selector, as it works in IE if you set the hover on an ancestor element, e.g. div:hover p::after - live demo: http://jsfiddle.net/SPHzj/14/.
However, this is not the case, as IE is indeed able to understand that selector. The trick is to define a p:hover {} rule as well. (Discovered by #maxw3st.)
p:hover {}
This rule can be empty. The mere presence of this rule will make the transitioning work in IE10.
Live demo: http://jsfiddle.net/SPHzj/15/ (also works in IE10)
What's going on here? Why does IE require that rule to be present in order for transitions to work on the pseudo-element? Should this be considered a bug?
Appears to be a Regression
This does appear to be a legitimate regression in Internet Explorer 10. As indicated on MSDN, since Internet Explorer 7 users have been able to target the hover state of any element , and not only a.
Curiously I tried the :active pseudo-class, and this appears to work as expected. Further establishing that this is a regression, you can see that by changing this to an a element, the transition takes place as expected (since historically, a and :hover go hand-in-hand).
Optional Work-Arounds
There are only a few solutions that I can think of at this point (while waiting for this to be fixed):
Use the empty p:hover {} fix.
Modify your markup to target ::after on a child of the p.
Modify the selector to use combinators.
The first item is that which you specified in your question, and is very attractive given its simplicity. In fact, you could use :hover{} and get the same results (probably the best solution).
The second item is also do-able, but a little less desirable since it requires modifying the markup, which is not always possible, and to be frank, a bit silly.
The last option is somewhat interesting. If you modify the selector to be based on sibling relationships, it magically begins to work again. For instance, suppose we have multiple elements in the body:
<h1>Hello, World</h1>
<p>This is my first paragraph. it does not animate.</p>
<p>This animates, with a pseudo-element.</p>
We can now use combinators to target the second paragraph:
p+p:hover::after {}
This selector will match any paragraph following a paragraph though, which isn't desirable. At this point we could consider :nth-child, or :nth-of-type to further specify which paragraph we want, even using the general sibling combinator:
h1~p:nth-of-type(2):hover::after {} /* Targets second <p> nearest <h1> */
But more ideally we would target with a class:
h1~.hoverme:hover::after {} /* Targets <p class="hoverme"> */
A Two-Char Solution?
One step further, maybe you don't want to be locked down explicitly providing a general sibling tag. You could also use the Universal Selector:
*~.hoverme:hover::after {} /* Targets <p class="hoverme"> among siblings */
This requires that the p tag have siblings, which is typically expected. Very rarely does a document consist of nothing more than a single paragraph tag.
I understand that these aren't ideal, but they are a means to an end for now. Let's hope to see this resolved in future releases of Internet Explorer.
Strangely, the effect will work on a <a> link rather than a paragraph tag.
It certainly appears to be an IE10 bug or regression. Fortunately, you've found a nice fix.
This same phenomenon popped up when I tried adding a rule to change the cursor to a pointer. However, cursor: pointer; has to be included in the pseudo's parent, it can't be used to target just the pseudo's content string in IE10.
http://jsfiddle.net/maxw3st/SPHzj/22/ uses a div as a container, http://jsfiddle.net/maxw3st/7sBVC/ uses the p:hover workaround. Adding the div was suggested by #simevidas, and works fine for the transition, just not the pointer. The pointer only seems to appear in IE10 when it is applied to the parent of the pseudo-element.