I recently had a use for the CSS ::first-letter pseudo element. I was dealing with some inconsistently formatted strings from the back end, and wanted to make sure that on the front end, all strings were shown with lowercase letters except for the first, which needed to be uppercase. I thought it would be trivial to use the following CSS:
.parentElement {
text-transform: lowercase;
}
.parentElement::first-letter {
text-transform: uppercase;
}
In my case, however, the first letter was not being uppercased, and I was confused about why. Here is a modified version of the HTML I was attempting to style:
<div class="parentElement">
<div class="child1">
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<path>...</path>
</svg>
</div>
<span class="child2">
inconsistently FormattED string
</span>
</div>
Naïvely, I thought I would be able to apply the styles on the div with the class parentElement
, and the styles would cascade down to the span with the class child2. However, it turns out that the ::first-letter
pseudo element selector can only target text under a certain set of conditions.
The ::first-letter spec
The ::first-letter CSS pseudo-element applies styles to the first letter of the first line of a block container, but only when not preceded by other content (such as images or inline tables).
When drilling down into what exactly defines a “block container”, there is definitely some ambiguity across the CSS spec: “In specifications, block boxes, block-level boxes, and block containers are all referred to as block boxes in certain places. These things are somewhat different and the term block box should only be used if there is no ambiguity.” I won’t go deep into the specifics of block boxes and block containers in this post, but I will attempt to provide a summary:
An element is a block container only if it contains block-level or inline-level boxes. The following values for the CSS display property produce block containers:
- block
- inline-block
- list-item
- table-cell
- table-caption
- flow-root
- inline (on one condition: inline elements become block containers when they contain inline text! The spec is difficult to read but I think I got the gist.)
The following values for the CSS display property produce block-level boxes, but not block containers, given how their child elements are formatted:
- flex
- grid
The key issue in my case was that I was attempting to target a non-block container; the parentElement
class specified a display property of flex and attempting to cascade the styles for ::first-letter
down to a child element.
The rules of ::first-letter
To summarise, using CSS ::first-letter
will not work in the following conditions:
- The element you are targeting is set to
display: flex
ordisplay: grid
- The element you are targeting is preceded by other content, such as a ::before pseudo element, images or inline tables
- You are attempting to cascade
::first-letter
styles from a parent element, even if the elements are block containers
To make sure CSS ::first-letter
works for you:
- Apply the
::first-letter
styles directly to a block container (and those block containers are specified above) - Ensure the content you are attempting to target is not preceded by other content, such as a
::before
pseudo element, images or inline tables
A note on browser support
CSS ::first-letter may have limited browser support: caniuse.com reports this pseudo element selector is not supported in Firefox, but Baseline reports that "This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015."
Using ::first-letter in action
To better understand how ::first-letter
works, I threw together some examples of when this pseudo element can be correctly targeted, and when it fails to work. You can view the examples at CSS first-letter demo, and you can view the code here.