1

I'm breaking up a long string using the method here: https://stackoverflow.com/a/24357132

.line {
  display: inline-block;
}
<p>
  <span class="line">This text should break</span>
  <span class="line">after the word "break"</span>
</p>

However, if the line break appears, I'd like to capitalize the first letter of the second line. So if the line break doesn't appear, it'll say:

This text should break after the word "break"

If the line break appears, it should say:

This text should break
After the word "break"

I tried the following, but none of them work:

.line:not(::first-line)::first-letter {
  text-transform: uppercase;
}

.line::first-letter {
  text-transform: uppercase;
}

.line::first-line {
  text-transform: none !important;
}

.line::first-letter {
  text-transform: uppercase;
}

.line::first-line::first-character {
  text-transform: none !important;
}

Is this possible without JS?

isherwood
  • 58,414
  • 16
  • 114
  • 157
Leo Jiang
  • 24,497
  • 49
  • 154
  • 284
  • 1
    Why is JS not an option? Or just curious, if it is possible with CSS? – Geshode Mar 27 '23 at 02:39
  • Looking at [this answer](https://stackoverflow.com/a/5577380/9038475), it seems , that `::first-character` should be `::first-letter`. – Geshode Mar 27 '23 at 02:42
  • Typo in the question, "first-letter" didn't work either – Leo Jiang Mar 27 '23 at 05:33
  • It works fine in jsfiddle with `.line::first-letter {text-transform: uppercase;}`. Do you have other CSS, which you use on the line class or span elements, which might have an influence on it? For example, is there something, which might change the display to something not block related? – Geshode Mar 27 '23 at 09:02

4 Answers4

0

Is this possible without JS?

Only with difficulty, and by manually tuning your CSS rules to the specific content. CSS is not really row oriented. You're looking for a selector that can target the first element in each row, on which you can apply ::first-letter, but there's nothing in CSS which can do that. Probably the closest thing would ::first-line, but since that and ::first-letter are both pseudo-elements they can't be used together. If you're fine with each of your span elements being exactly the same width you could do it with CSS grid, but that would cause whitespace gaps between the spans when they're fitted to the grid, breaking up the text flow, so I doubt that's the solution you're looking for.

For a CSS-only solution you're unfortunately limited to finding every screen size at which a new wrap occurs, and writing a media query that uses nth-child to target the specific elements that are at the beginning of each line.

.line {
  display: inline-block;
}
@media (max-width: 792px) {
  .line:nth-child(1)::first-letter { text-transform: uppercase; }
  .line:nth-child(2)::first-letter { text-transform: none; }
  .line:nth-child(3)::first-letter { text-transform: none; }
  .line:nth-child(4)::first-letter { text-transform: none; }
  .line:nth-child(5)::first-letter { text-transform: none; }
  .line:nth-child(6)::first-letter { text-transform: uppercase; }
}
@media (max-width: 698px) {
  .line:nth-child(1)::first-letter { text-transform: uppercase; }
  .line:nth-child(2)::first-letter { text-transform: none; }
  .line:nth-child(3)::first-letter { text-transform: none; }
  .line:nth-child(4)::first-letter { text-transform: none; }
  .line:nth-child(5)::first-letter { text-transform: uppercase; }
  .line:nth-child(6)::first-letter { text-transform: none; }
}
@media (max-width: 502px) {
  .line:nth-child(1)::first-letter { text-transform: uppercase; }
  .line:nth-child(2)::first-letter { text-transform: none; }
  .line:nth-child(3)::first-letter { text-transform: none; }
  .line:nth-child(4)::first-letter { text-transform: uppercase; }
  .line:nth-child(5)::first-letter { text-transform: none; }
  .line:nth-child(6)::first-letter { text-transform: none; }
}
@media (max-width: 441px) {
  .line:nth-child(1)::first-letter { text-transform: uppercase; }
  .line:nth-child(2)::first-letter { text-transform: none; }
  .line:nth-child(3)::first-letter { text-transform: none; }
  .line:nth-child(4)::first-letter { text-transform: uppercase; }
  .line:nth-child(5)::first-letter { text-transform: none; }
  .line:nth-child(6)::first-letter { text-transform: uppercase; }
}
@media (max-width: 368px) {
  .line:nth-child(1)::first-letter { text-transform: uppercase; }
  .line:nth-child(2)::first-letter { text-transform: none; }
  .line:nth-child(3)::first-letter { text-transform: uppercase; }
  .line:nth-child(4)::first-letter { text-transform: none; }
  .line:nth-child(5)::first-letter { text-transform: uppercase; }
  .line:nth-child(6)::first-letter { text-transform: none; }
}
@media (max-width: 313px) {
  .line:nth-child(1)::first-letter { text-transform: uppercase; }
  .line:nth-child(2)::first-letter { text-transform: none; }
  .line:nth-child(3)::first-letter { text-transform: uppercase; }
  .line:nth-child(4)::first-letter { text-transform: none; }
  .line:nth-child(5)::first-letter { text-transform: uppercase; }
  .line:nth-child(6)::first-letter { text-transform: uppercase; }
}
@media (max-width: 310px) {
  .line:nth-child(1)::first-letter { text-transform: uppercase; }
  .line:nth-child(2)::first-letter { text-transform: uppercase; }
  .line:nth-child(3)::first-letter { text-transform: none; }
  .line:nth-child(4)::first-letter { text-transform: uppercase; }
  .line:nth-child(5)::first-letter { text-transform: uppercase; }
  .line:nth-child(6)::first-letter { text-transform: uppercase; }
}
@media (max-width: 226px) {
  .line:nth-child(1)::first-letter { text-transform: uppercase; }
  .line:nth-child(2)::first-letter { text-transform: uppercase; }
  .line:nth-child(3)::first-letter { text-transform: uppercase; }
  .line:nth-child(4)::first-letter { text-transform: none; }
  .line:nth-child(5)::first-letter { text-transform: uppercase; }
  .line:nth-child(6)::first-letter { text-transform: uppercase; }
}
@media (max-width: 207px) {
  .line:nth-child(1)::first-letter { text-transform: uppercase; }
  .line:nth-child(2)::first-letter { text-transform: uppercase; }
  .line:nth-child(3)::first-letter { text-transform: uppercase; }
  .line:nth-child(4)::first-letter { text-transform: uppercase; }
  .line:nth-child(5)::first-letter { text-transform: uppercase; }
  .line:nth-child(6)::first-letter { text-transform: uppercase; }
}
<p>
  <span class="line">This text should break</span>
  <span class="line">after the word "break".</span>
  <span class="line">and then</span>
  <span class="line">we have some more</span>
  <span class="line">text to demonstrate spans with</span>
  <span class="line">varying length</span>
</p>

The above code could be optimised somewhat, but goes to show how arduous this is to set up in pure CSS. And, of course, if the text content changes at all you'll need to recompute all the breakpoints.


If you are open to using JavaScript then a simple function can compute whether each span is wrapped to the far left of its parent, and if so have a class added that applies text-transform: uppercase to the first letter.

const setCapitals = () => {
  const spans = document.querySelectorAll( '.wrap-capitals span' )
  spans.forEach( span => {
    span.classList.toggle( 'upper', span.offsetLeft === 0 )
  } )
}

window.addEventListener( 'resize', setCapitals )
window.addEventListener( 'load', setCapitals )
.wrap-capitals {
  position: relative;
}
.wrap-capitals > span {
  display: inline-block;
}
.wrap-capitals > span.upper::first-letter {
  text-transform: uppercase;
}
<p class="wrap-capitals">
  <span>This text should break</span>
  <span>after the word "break".</span>
  <span>and then</span>
  <span>we have some more</span>
  <span>text to demonstrate spans with</span>
  <span>varying length</span>
</p>
jla
  • 4,191
  • 3
  • 27
  • 44
0

Since ::first-letter does not work on inline elements such as a span. Using ::first-letter will not affect your style since it only works on block elements such as a paragraph(<p>), or those with their display property set to inline-block.

By doing so, you need to set your span first and display it as block using -> span {display:block;} then make your selector.

span {display:block;}
p span.line:not(:first-of-type)::first-letter {
  text-transform: uppercase;
}
<p>
  <span class="line">This text should break</span>
  <span class="line">after the word "break"</span>
</p>
Newbee
  • 702
  • 1
  • 13
0

If you read the specs on the ::first-line and ::first-letter you'll see that what your looking for is impossible.

They way that these work, is that the CSSDom creates "fictional tags" like the following:

// Example
p { color: red; font-size: 12pt }
p::first-letter { color: green; font-size: 200% }
p::first-line { color: blue }

<P>Some text that ends up on two lines</P>

// CSSDom output
<P>
<P::first-line>
<P::first-letter>
S
</P::first-letter>ome text that
</P::first-line>
ends up on two lines
</P>

The CSSDom can't figure out where to put these "fictional tags" when you use inline elements. You need to have a single block element for these "fictional tags" to even work.

That said, even if you DID have a single block element, you still wouldn't be able to select the second line's ::first-letter because that fictional tag is wrapped inside the ::first-line, and there isn't support for ::second-line or anything beyond the first.

So, if you want to do this without JS

You're going to have to get creative, and write a lot of Media queries, and do a lot of quality assurance to make sure that your type fits nicely inside what you're trying to create.

Based on what you've shared, here's a relatively flexible, and simple version of how I'd handle these requirements.

/* 1 item on mobile */
@media (max-width: 599px) {
  .line-container {
    width: 20ch;
  }

  .line-container .line {
    display: inline-block;
  }

  .line-container .line:first-letter {
    text-transform: uppercase;
  }
}

/* 2 items on Tablet */
@media (min-width: 600px) and (max-width: 1023px) {
  .line-container {
    width: calc(20ch * 2);
  }

  .line-container .line {
    display: inline-block;
  }

  .line-container .line:nth-child(2n + 3):first-letter {
    text-transform: uppercase;
  }
}

/* 3 items on Desktop */
@media (min-width: 1024px) {
  .line-container {
    width: calc(20ch * 3);
  }

  .line-container .line {
    display: inline-block;
  }

  .line-container .line:nth-child(3n + 4):first-letter {
    text-transform: uppercase;
  }
}
<p class="line-container">
  <span class="line">This text should break</span>
  <span class="line">after the word "break"</span>
  <span class="line">after the word "break"</span>
  <span class="line">after the word "break"</span>
  <span class="line">after the word "break"</span>
  <span class="line">after the word "break"</span>
  <span class="line">after the word "break"</span>
</p>
tubstrr
  • 947
  • 2
  • 9
  • I would like to use CSS var's inside here, but they don't work inside `:nth-child()` selectors unfortunately. – tubstrr Apr 23 '23 at 16:29
-1

Use this one for capitalize first letter:

.line::first-letter { text-transform: uppercase; }