Skip to content

[css-pseudo-5] ::text / ::text-node pseudoelement #2208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Nadya678 opened this issue Jan 19, 2018 · 36 comments
Open

[css-pseudo-5] ::text / ::text-node pseudoelement #2208

Nadya678 opened this issue Jan 19, 2018 · 36 comments

Comments

@Nadya678
Copy link

https://drafts.csswg.org/selectors-4/

Please add ::text or ::text-node selector to select all text nodes inside any element
for example for

#text1: Here is text #text2: Here also is text #text3: Here is second text in the paragraph

the p::text will select #text1 and #text3 and the pseudoelement can be styled like ::after and ::before (without need to set "content").

@js-choi
Copy link

js-choi commented Jan 19, 2018

I’ve also been thinking about pseudo-elements for anonymous boxes, first to the WICG to be eventually moved here. I talked about it at bit at #2084 (comment) – and I’ve have been writing a strawman when I can. I could find barely any prior mention of this idea before, when I searched on the web and in the old mailing lists.

For instance, a pseudo-element for anonymous block boxes would be generally useful in other cases as an approximate analogue to HTML’s paragraphs, e.g., anonymous-block “paragraphs” such as those in

First block

This is the second block in this example.

This is the third.

  • Fourth.
  • Fifth.

    Sixth.

. Such pseudo-elements, however, might have broad ramifications for rendering performance, so I had planned on courting authors in the WICG before the CSSWG to bolster my case. This is the first mention of this idea I have yet seen elsewhere on the web, so it’s good to have another use case.

My own biggest use case would be uniformly styling the boxes of paragraph text in

s,

  • s,
    s and
    s,
  • s and s,

    s, and so forth, even when they’re arbitrarily nested. @AmeliaBR wrote about some other use cases in #2084 (comment). These include selecting
    text content not within a element and fallback text for things like and .

    I don’t know if I should keep working on my WICG strawman if there’s going to be discussion here. I would be happy with any discussion: I’m certainly still surprised that there’s been barely any discussion about this idea over the past thirty years.

    @AmeliaBR
    Copy link
    Contributor

    I'm definitely supportive of the idea in general.

    A more detailed proposal with examples would probably be helpful in gaining more supporters, so if you're already working on something @js-choi, please don't stop just because this has now been formally raised as a working group issues.

    Some things to think of:

    • An anonymous block is different from a text node, since it can include inline elements. (In your code snippet, the text nodes that are direct children of the body would be "This is the" and "block in this example.") If there are use-cases for both, then maybe there needs to be more than one selector.
    • Woule a pseudoelement selector only select direct-child text nodes for the element it is attached to, or any descendent text nodes?
    • Whitespace-only text nodes are treated differently than content nodes in CSS layout, so would probably need to be distinguished.

    Which are all probably good prompts for a WICG discussion if you want to start one.

    @Nadya678
    Copy link
    Author

    Nadya678 commented Jan 19, 2018

    1. In my opinion ::text should select only direct childs. we can write in my example:
      p::text, p ::text /* note a space before ::*/
      to select all text nodes.
    2. Yes, whitespace nodes should be treated in other manner.

    @emilio
    Copy link
    Collaborator

    emilio commented Jan 19, 2018

    It's unclear about what most of the CSS properties would mean on a text-node, though, right? Specially non-inheriting ones.

    @Nadya678
    Copy link
    Author

    In my opinion all styles including absolute positioning. But ::text::before forbidden.

    @emilio
    Copy link
    Collaborator

    emilio commented Jan 19, 2018

    What does an absolutely positioned text-node mean? Can you set display: block on a text-node? What about display: none? What happens to text layout when you combine stuff using display: contents and such?

    @tabatkins
    Copy link
    Member

    There's two ways to go about this. One is to style the text node itself - only a subset of the CSS properties would apply, those that work on text. (More or less just the properties that are inherited, but there are some inherited properties that wouldn't apply.) We'd have to define the precedence of this vs ::first-line and ::first-letter.

    The second is to create an anonymous box around the text node and style that. In this case all of CSS applies. There is a minor problem here, tho - one of the use-cases for this is to solve issue 2 in the Scoping spec, and it's not totally clear where in the flat tree the anonymous box would be.

    @js-choi
    Copy link

    js-choi commented Jan 20, 2018

    @tabatkins What about selecting anonymous inline blocks? They’re not exactly the same as text nodes (especially regarding positioning), but they overlap quite a bit. My impression had been that those are already being generated by current agents and thus would not necessitate creating new boxes.

    @Nadya678
    Copy link
    Author

    Nadya678 commented Jan 22, 2018

    It's unclear about what most of the CSS properties would mean on a text-node, though, right? Specially non-inheriting ones.

    Works it like ::after and ::before.

    ::text
    {
       display:block;
       border:2px solid red;
    }
    

    will be shown identically like:

    ::before
    {
       content:"the node text"; 
       display:block;
       border:2px solid red;
    }
    

    I think it is the easiest solution.

    display: none

    The text is removed and not shown.

    display: contents

    Need clarification and any idea. It can be by exception treated as display:inline

    The second is to create an anonymous box...

    Yes, in my opinion the anonymous inline box will be the solution. Only exception to styles that cannot be applied like display:contents. Please note, if the element

    in my example will be styled display:flex; the text nodes are switched to show like display:block.

    @Loirooriol
    Copy link
    Contributor

    I have desired something like this several times when I have

    Foo:
    and I want to vertically center the text with the textarea via vertical-align: middle, but there is no way to select the text.

    Always wrapping text runs inside new boxes may be a breaking change, e.g. boxes inside a flex container become flex items, so this would prevent sequences of text runs to be wrapped inside a single anonymous flex item. CSS Flexbox could discriminate boxes generated by ::text from all the other ones, but this would be confusing and inconsistent.

    It has also been suggested to inject the styles into anonymous boxes, but I think this is a bad idea because the cascade is supposed to happen in the element tree, and I prefer to avoid another fiasco like ::first-line.

    I think what makes more sense would be:

    • Text nodes (or sequences of text nodes?) in the DOM tree are wrapped inside a ::text pseudo-element in the CSS element tree.
    • These ::text pseudo-elements must act as if they were assigned display: contents via a rule in the UA origin. Therefore, they do not generate any boxes and are treated as if they had been replaced with their children, which would be the current behavior. (There is precedence for this)
    • This must be possible to override via display, so they do generate boxes if desired. But maybe mark this at-risk.

    @Nadya678
    Copy link
    Author

    Nadya678 commented Jan 22, 2018

    What is formal initial display value for ::after if the display, float and position are not defined?

    BTW ::first-letter is greater fiasco than ::first-line

    @Loirooriol
    Copy link
    Contributor

    @Nadya678 ::before and ::after have the initial display: inline by default. But they also have content: none, which behaves like display: none.

    @gsnedders gsnedders added the selectors-4 Current Work label Jan 22, 2018
    @Nadya678
    Copy link
    Author

    OK. Thus here one exception: ::text - default display:contents.

    @fantasai fantasai removed the selectors-4 Current Work label Jan 27, 2018
    @fantasai fantasai changed the title [css-selectors-4] ::text / ::text-node pseudoelement [css-pseudo-5] ::text / ::text-node pseudoelement Jan 27, 2018
    @inoas
    Copy link

    inoas commented Mar 8, 2018

    I wasn't aware of this ticket existing when I wrote this: #2406 (comment)

    The basic take-away is that I don't think a text node selector should be a pseudo element prefixed with colon(s) - that creates all kind of problems with specificity imho.

    Instead it should be like an invisible p-tag with slightly less specificity than tags that auto opens on text and auto closes before any new block context / when the parent element closes.

    @ByteEater-pl
    Copy link

    I believe some of the above thinking too tightly tied to an artifact in the DOM (that never results from parsing) which is the possibility of having empty or adjacent text nodes. Let's have CSS, if in a DOM-based implementation of the host language, pretend that document.normalize() was performed before styling. (And do something sensible for APIs using CSS selectors.)

    @Loirooriol
    Copy link
    Contributor

    @ByteEater-pl It could be tied to the concept of text run:

    each contiguous sequence of sibling text nodes generates a text run [...]. If the sequence contains no text, however, it does not generate a text run.

    @mauriciogior
    Copy link

    mauriciogior commented Jun 28, 2021

    A pseudo element like ::text would have its benefits, but I'm still trying to figure out how to use it in the following use case:

    HTML:

    <p class="message">
         Hi there
         <span class="emoji-native" id="1">😋span>
         this is cool!
         <span class="emoji-native" id="2">😍span>
         <span class="emoji-native" id="3">😍span>
    <p>

    CSS:

    .emoji-native {
          margin: 0 3px;
    }
    .emoji-native + .emoji-native {
          margin-left: 0px;
    }

    Basically in this example I want to consider ::text as a node between the two .emoji-native nodes when using the + operator, but it turns out it simply ignores it and adds that CSS on both ID 2 and 3.

    To be honest I don't see a syntax that would make this work, this feels out of context. I would vouch though for a new kind of element that would represent displaced text written directly into the source code, or even a simple "invisible" HTML tag that represents all texts that are siblings to actual nodes.

    @Loirooriol
    Copy link
    Contributor

    @mauriciogior No, adding ::text shouldn't break existing combinators or selectors, they would continue to skip text nodes

    <p class="message">                   
      Hi there                            
      <span class="emoji-native" id="1">  
        😋                                
      span>
      this is cool!                       
      <span class="emoji-native" id="2">  
        😍                                
      span>
      <span class="emoji-native" id="3">  
        😍                                
      span>
    p>

    If what you want is selecting a .emoji-native whose previous sibling node (including text) is a .emoji-native element, then I don't think that's covered by this feature. You would need a new kind of combinator.

    @jonathantneal
    Copy link
    Contributor

    jonathantneal commented Oct 11, 2022

    This selector would have been useful to me when targeting slotted text nodes, created either from implicit elements, or created from programatic, manual slot assignments. The CSS ::slotted(*) selector does not currently select text nodes.

    I would also like to point out some prior art. At the time of this writing, other fully or partially shipping CSS text selectors or text-capable selectors include ::first-letter, ::first-line, ::selection, ::target-text, ::spelling-error, ::grammar-error, ::before, and ::after.

    As a related aside, ::slotted(*)::before and ::slotted(*)::after can be used to target generated, slotted ‘before’ and ‘after’ text content. I am a little confused as to why the ::before or ::after selector comes after the ::slotted selector rather than inside of it. I could imagine that raises some parity issues for something like ::slotted(::text) or even ::slotted(::first-letter), as either those selectors may include ::before content.

    @samuelbradshaw
    Copy link

    samuelbradshaw commented Oct 20, 2022

    Instead it should be like an invisible p-tag with slightly less specificity than tags that auto opens on text and auto closes before any new block context / when the parent element closes.

    This is an interesting idea, especially since the closing tag for

    is already optional. However, there may be some issues with backwards compatibility. If my website uses CSS to add a background color on all

    elements (for example), suddenly my website would have a lot more background color than I intended. Also,

    is a block element, and the text node may not be.

    @mauriciogior mentions a new element – if we went that route, both the start tag and end tag could be optional. But I guess I'm not clear on what the difference would be in practice between an implied element or a ::text pseudo-element.

    Another downside of an implied element is that it would get confusing when using positional pseudo-classes, like :nth-child().

    @dotterian
    Copy link

    I think this selector will enable pretty cool features in combination with :has.
    For example:

    .button {
      display: flex;
      height: 40px;
      padding: 8px 16px;
      column-gap: 8px;
      align-items: center;
    }
    
    .button:has(svg):not(:has(::text)) {
      width: 40px;
      padding: 0;
      justify-content: center;
    }

    This will make one class for regular buttons with icon and text and for square buttons if it contains only icon.
    For now we have to make additional class for square buttons and I wish we could have more smart, "component-like" classes.

    @Loirooriol
    Copy link
    Contributor

    While that may be possible, pseudo-elements are currently invalid inside :has(), so it would need special handling.

    Also note that ::text means *::text, so :has(::text) would match elements that have some descendant element that has a text node child. As I said in #7463 (comment), you probably want :has(:> text).

    Also note that ::text would presumably affect white space, so it may not work as desired if you have

    <button>
      <svg>...svg>
    button>

    @dotterian
    Copy link

    Imo, in case of button *::text is ok. Since there'll be no descendants, it's simply svg + text node.
    But that doesn't excuse my bad syntax.

    On the topic of whitespaces: I think ::text should ignore "empty" text nodes, which contain only whitespace characters. As mentioned in this comment

    @Loirooriol
    Copy link
    Contributor

    That comment refers to empty text nodes like new Text(), white space characters are text, and ::text { white-space: pre } seems reasonable.

    @dotterian
    Copy link

    Is somehing like ::text:empty possible? It could solve the whitespace problem, according to CSS Selectors Level 4 Draft

    @Loirooriol
    Copy link
    Contributor

    Seems possible as long as the content property is not able to change the text (otherwise it would be circular).

    @akira-cn
    Copy link

    akira-cn commented Dec 25, 2023

    @mauriciogior

    I also want to consider ::text as a real node when using the + operator.

    This will be useful while using the streaming mode calling the ChatGPT's completion api with an asynchronous function call.

    When ChatGPT is outputting content in Streaming mode, it usually involves text nodes. At this time, if it needs to draw an image, it will pause to execute a function call. During this, a Loading sign should be displayed in the UI. The simplest way to do this is to add a tag within its streaming content. Once the image is loaded and it continues to output other text content, this tag should be set to display: none.

    p .loading:has(+::text) {
        display: none;
    }

    @contentfree
    Copy link

    Having ::text (as a real node) would also be supremely useful when doing something seemingly simple like hiding text next to an icon. Consider (using Font Awesome):

    Currently, one must do tricks with overflow: hidden and width (or several varieties of this hack) to show just the icon. It would be wonderful to just have a rule a ::text { display: none; } (or better yet: i.fa + ::text { display: none; }

    @Crissov
    Copy link
    Contributor

    Crissov commented Jan 12, 2024

    Looks like display: icon which never came to be. https://www.w3.org/TR/2012/WD-css3-ui-20120117/#element-icons

    @Loirooriol
    Copy link
    Contributor

    @contentfree a ::text means a *::text, i.e. the text pseudo-element originated by an element which is a descendant of a. And i.fa + ::text means i.fa + *::text, i.e. the text pseudo-element originated by the next sibling element of i.fa.

    So with this proposal you would actually need a::text { display: none }, and can't do different things for text nodes that precede or follow i.fa.

    @emilio
    Copy link
    Collaborator

    emilio commented Jan 12, 2024

    Having ::text (as a real node) would also be supremely useful when doing something seemingly simple like hiding text next to an icon. Consider (using Font Awesome):

    Currently, one must do tricks with overflow: hidden and width (or several varieties of this hack) to show just the icon. It would be wonderful to just have a rule a ::text { display: none; } (or better yet: i.fa + ::text { display: none; }

    content: none ought to work for this, right? But when we implemented it wasn't web compatible, we'd need a separate keyword

    @contentfree
    Copy link

    @contentfree a ::text means a *::text, i.e. the text pseudo-element originated by an element which is a descendant of a.

    That's why I qualified it with "as a real node".

    Jumping back to the comment at #2208 (comment) and its example: It seems like some mechanism (maybe not ::text?) to include the current element's text nodes as actual children would allow for i.fa + or targeting the text node after the first span.emoji-native element in @mauriciogior's example. (Would a::text:last-child work? That would solve for my icon example, I believe.)

    @SebastianZ
    Copy link
    Contributor

    So with this proposal you would actually need a::text { display: none }, and can't do different things for text nodes that precede or follow i.fa.

    Correct, but you could still target the text node next to the icon via a:has(> .fa)::text.
    Also, when adjacent sibling pseudo-element combinator suggested in #7346 becomes reality, one could write i.fa :+ text instead.

    If we introduce a ::text pseudo-element for that, I'd suggest to also add a functional version of it to target specific text nodes. That function takes an An+B notation to restrict it to specific nodes. So applying p::text(1) to the example in the initial comment would target the text node "Here is text".
    Alternatively, we could define that the different :nth-*() pseudo-classes, when applied to text nodes, only refer to text nodes. So the first text node would then be targeted via p::text:nth-child(1).

    I also want to point out that this could be a potential performance footgun, because as @Loirooriol pointed out several times, ::text means *::text, so it targets all text nodes on a page. So authors might be inclined to define ::text { color: black; } instead of body { color: black; } and relying on the cascade.

    Sebastian

    @contentfree
    Copy link

    Just wanted to add another example where this would be useful. I'm pretty sure there's currently no (CSS-only) way to distinguish between these to only add right-margin to the img in the first div:

    Some label

    @Loirooriol
    Copy link
    Contributor

    @contentfree That seems a different feature, maybe :has(:+ text) from #7346 as Sebastian said.

    But anyways, div { margin-trim: inline } img { margin-right: 1em } seems a better approach. See https://drafts.csswg.org/css-box-4/#margin-trim

    @starball5
    Copy link

    Related on Stack Overflow: Is there a CSS selector for text nodes?

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    No branches or pull requests