A partial archive of discourse.wicg.io as of Saturday February 24, 2024.

Text-content() CSS value akin to attr()

stuartpb
2014-09-15

As seen here:

This CSS trick works by setting an attribute to the text content of the element, then using that value as the content of a :before.

I’ve seen other similar CSS tricks that use the same pattern. It would be nice if these stylesheets could just reference their elements textContent with a value like text-content().

tabatkins
2014-09-19

It was last brought up in Oct 2013, and there weren’t any objections. It makes sense, I think. Just needs us to revive the Content spec, which I haven’t had time to do yet.

stuartpb
2014-09-26

http://www.w3.org/TR/css3-gcpm/#funcdef-content says this already exists, as content(text)? Is that what you mean by “the content spec”?

tabatkins
2014-09-29

Nah, I meant http://dev.w3.org/csswg/css-content/. The content() function defined by GCPM is only for string-set, and neither are implemented anyway.

stuartpb
2014-09-29

Hmm… it seems like that spec only allows contents to be specified once (it’s functionally equivalent to the insert-point for all descendant elements). That wouldn’t allow for tricks like the one described above. It sounds like the content() function from GCPM is needed in css-content as a valid element of content-list, although that does have the unfortunate effect of making the same word applicable as both a property and function name (I’m not sure if this already happens in CSS, or if CSS preproccessing languages like Stylus would have an issue with it.)

(content() and contents both being a thing is also a little weird, although the css-content spec already has counter() and counters()).

That, or just forget the content() function and add text-content and first-letter values for content-list. (Ditching content(before) and content(after) because they’re incompatible with the css-content model, and, from what I can tell, they’re just hacks that, in GCPM terms, would be better served by creating a separate string-set to use with ::before and reusing it where you would have used content(before).)

tabatkins
2014-10-02

Don’t worry about what the spec says right now - as the note indicates, the spec is currently abandoned and will need to be rewritten.

heydonworks
2014-10-03

+1 for text-node based selectors. Like @leaverou, I’ve often found myself reaching for them.

I imagined something constructed like the negation pseudo, so

elem:text('hello') { }

but it needs searches, like the attr selectors mentioned above:

elem:text(*='hello') { }
elem:text(^='hello') { }
elem:text($='world') { }
/* etc */

Perhaps the attr selector style would be better, but we’d have to avoid a text attribute clash ie. text="bla"

Maybe

[:text="hello"]
[:text$="world"]
/* etc */
stuartpb
2014-10-03

I have a feeling a text-content selector would be prohibitively expensive to compute. To fake it, and get a sense of how expensive this would be, set a JS mutation observer that sets the value of an attribute to text-content every time an element changes (up the entire tree), and add a few CSS rules selecting on that attribute. (It’s possible I’m wrong on this, but this test would prove its complexity either way.)

I’m talking about text-content as a CSS value, for the content CSS attribute.

heydonworks
2014-10-03

If you mean transferring the text node as a value of content, then that would be less expensive. There would be few applications for it, however.

It would just mean being able to duplicate content as pseudo-content. Aside from for tricks like the one provided I see little value in this so far. Should probably remain the domain of javascript?

Out of interest I wonder how inner HTML and encodings would be handled?

leaverou
2014-10-03

This used to be in the spec, as :content(), but was removed for reasons I don’t remember. Perhaps @tabatkins can enlighten us on that.

tabatkins
2014-10-14

It was too slow, especially since it could reach across element boundaries.

leaverou
2014-10-28

Btw note that this would be very useful for doing pull quotes without duplicating text. That’s a pretty common use case.

tabatkins
2014-10-28

You need to preserve formatting in that case, though. (At least sometimes.)

chenzx
2016-10-19

Hi, i just want to post a article titled as “Extend CSS Selector’s capacity to select element’s text content”, and i see this, great! CSS cannot directly select text content, since it’s not DOM nodes… Anyway, it can be poly-filled by use JavaScript code and Range API.

But as a spec, i think the biggest problem to how to provide flexibility by a simple syntax. What i could imagine is to provide a inject a UDF(user defined function) into the CSS Selector, this will be the most flexible…

tomhodgins
2017-01-01

Reading through this thread I was inspired by a number of different possibilities for how text could be manipulated with a CSS-like syntax!

Currently CSS can:

  • Set a pseudo-element’s content with text from an HTML attribute()
  • Set a pseudo-element’s content with a text string from CSS

And CSS cannot:

  • Set an element’s innerHTML with text from an HTML attribute
  • Set an element’s innerHTML with a text string from CSS
  • Target an element based on a search of the text it contains.

For those last three I have experimented with element queries and the possibility of evaluating JavaScript inside CSS (from the context of the scoped element) and here are some demos of those three ideas CSS currently cannot do:

Text from attribute as innerHTML

<h2>Text from attribute as innerHTML</h2>
<div id=text data-text="This is <strong>HTML</strong> text."></div>

<style>
  @element '#text' {
    eval('innerHTML = getAttribute("data-text")');
  }
</style>

<script src=http://elementqueries.com/EQCSS.js></script>

This example grabs the text content from the HTML attribute data-text on the scoped element #text and sets it as the innerHTML of the scoped element (itself).

I’ve used a similar technique in the past to supply responsive truncated text excerpts of longer passages: http://codepen.io/tomhodgins/pen/KgKggX

@element '[data-truncate]' {
  $this:before {
    content: 'eval("getAttribute('data-truncate').substring(0,scrollWidth/3)")…';
  }
}

Text from CSS as innerHTML

<h2>Text from CSS as innerHTML</h2>
<div id=from-CSS></div>

<style>
  @element '#from-CSS' {
    eval('innerHTML = "This is <strong>HTML</strong> text."')
  }
</style>

<script src=http://elementqueries.com/EQCSS.js></script>

This example does a similar thing as the last example, except the text content is fully supplied from within the stylesheet. This would make it really easy to add responsive content, or to swap out content by language, etc.

Element Selected by its Text Content

<h2>Element Selected by its Text Content</h2>
<div>This element is highlighted because it contains the word: Rubber</div>

<style>
  @element 'div' {
    eval('(/rubber/i).test(innerHTML) ? "$this" : ""') {
      background: yellow;
    }
  }
</style>

<script src=http://elementqueries.com/EQCSS.js></script>

This one is really cool! I was wondering if there was a way you could target an element with a style based on its own text content. Now a few notes about this, you want to scope the element the closest to the content you’re searching. Since tags can contain other tags, if there’s any tag inside your HTML element and you write the rule for @element 'html' {} it will come back true. Likewise if you scoped the * wildcard selector, this rule would be true for every parent element containing an element that contained the test you were searching.

My first attempt at doing this was formatted with indexOf() like this:

@element 'div' {
  eval('innerHTML.indexOf("Rubber") !== -1 ? "$this" : ""') {
    background: yellow;
  }
}

But the downside to innerHTML.indexOf("Rubber") !== -1 was that I had to enter an exact string Rubber, and I would have to write a separate selector if I wanted to target rubber or rUbBeR. So instead I formatted the string I’m searching as a Regular Expression so I can use flags like i for a case insensitive search, and using test() to see if that regular expression matches anything in the innerHTML. If the result of the test is true, then the selector used will output $this (which inside of the scoped style, refers to div in this case) otherwise if it’s false, no selector text is output and our rule of { background: yellow } applies to zero elements on the page.

Here’s a Codepen demo of all 5 ideas on the same page: http://codepen.io/tomhodgins/pen/ggYzmY?editors=1000

Just some fuel for the fire :smiley:

Neverlands
2019-06-30

Hello, I have seen that this thread is a rather old thread but I have a similar situation. I would like to set a list class (".non-zero") to display:none if the innerHTML of a span (".bubble") contains 0.

HTML

<li class="tab non-zero">
  <span class="counter"><span class="bubble">0</span></span>
</li>

I tried it with this but it does not work:

<li class="tab non-zero">
<style>
@element 'li' {
  eval('/0/i.test(innerHTML) ? "$this" : ""') {
    display: none !important;
  }
}
</style>
     <span class="counter"><span class="bubble">0</span></span>
</li>
strarsis
2020-07-30

Other use case: Style/select the text shadow differently, e.g. with a blend mode for a multiply text shadow. Being able to use the same text as content in a pseudo-element would allow for this.

sollyucko
2021-05-12

Another use case is for sizing an element as if the text had a specific formatting (e.g. for avoiding navbar items shifting around when making text bold on hover). This works by adding an invisible :after pseudoelement with content: attr(data-content) and the target formatting.

Currently, this requires duplicating the text in the content and an attribute, either manually or using JavaScript. However, a text-content function (with a way to target the current element) would avoid that.

P.S. an extended version of the element function would probably also work in this case.