The Complete CSS First Of Type Interactive Tutorial

The Complete CSS First Of Type Interactive Tutorial

Table of Contents

What is the CSS first of type selector?

The :first-of-type pseudo-class selects the first sibling of a given element type inside the same parent. In plain English, it means “find the first p among these siblings”, or “find the first li among these siblings”, or “find the first article among these siblings”. It does not mean “the first child overall”, and it does not mean “the first element with a class”.

This selector is widely supported in modern browsers, and MDN lists it as a broadly available feature. That makes it a very safe choice when you want to style the first heading, first paragraph, first list item type, or first card element of a certain tag within a parent.

The key idea is this: “of type” means of the same tag name. So p:first-of-type is about paragraphs, h2:first-of-type is about second-level headings, and article:first-of-type is about articles. CSS is not looking at classes for this selector. It is looking at the element type.

p:first-of-type {
  background: #ffe08a;
  border-left: 6px solid #ff9f1c;
  padding: 0.75rem;
}
h2:first-of-type {
  color: #b42318;
  text-decoration: underline;
}
article:first-of-type {
  outline: 3px dashed #7c3aed;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
display: grid;
gap: 1rem;
font-family: Arial, sans-serif;
}

section,
article {
border: 1px solid #d0d7de;
padding: 1rem;
border-radius: 0.75rem;
background: #fff;
}

p,
h2 {
margin: 0.5rem 0;
}

Section heading

First paragraph in this section.

Second paragraph in this section.

Article heading

First paragraph in this article.

Second paragraph in this article.

CSS first of type vs first child

Beginners often mix up :first-of-type and :first-child, and honestly, the names do invite a little confusion. The difference is simple once you see it:

  • :first-child matches an element only if it is the very first child of its parent.
  • :first-of-type matches an element if it is the first sibling of its own tag name, even when something else appears before it.

So if a parent starts with an h3 and then a p, that paragraph is not :first-child, but it is p:first-of-type because it is the first paragraph among its siblings.

p:first-child {
  color: #b42318;
  font-weight: bold;
}
p:first-of-type {
  color: #0f766e;
  font-weight: bold;
}
:is(p:first-child, p:first-of-type) {
  background: #ecfeff;
  padding: 0.5rem;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.demo {
font-family: Arial, sans-serif;
border: 1px solid #d0d7de;
border-radius: 0.75rem;
padding: 1rem;
background: #fff;
}

.demo > * {
margin: 0.5rem 0;
}

I am the real first child

I am the first paragraph, but not the first child.

I am the second paragraph.

In that example, p:first-child matches nothing, because the first child is an h3. But p:first-of-type matches the first paragraph just fine. That is one of the most useful mental models to keep in your toolbox.

CSS first of type example

Let’s look at the classic case. Suppose you have several paragraphs in a content block and you want only the first paragraph to look like an introduction or lead paragraph. That is a perfect use case for p:first-of-type.

This is especially handy in blog posts, article summaries, cards, and documentation pages where the first paragraph often deserves a little visual emphasis without adding extra classes in your HTML.

p:first-of-type {
  font-size: 1.2rem;
  font-weight: 700;
}
p:first-of-type {
  color: #1d4ed8;
}
p:first-of-type {
  border-left: 5px solid #1d4ed8;
  padding-left: 0.75rem;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.article {
max-width: 42rem;
font-family: Arial, sans-serif;
line-height: 1.6;
border: 1px solid #d0d7de;
border-radius: 0.75rem;
padding: 1rem;
background: #fff;
}

.article p {
margin: 0 0 1rem;
}

This is the lead paragraph. It introduces the topic and gets a bit more attention.

This is the second paragraph with regular body text.

This is the third paragraph, quietly doing paragraph things.

How first of type really matches elements

The selector works among siblings that share the same parent. CSS checks the children of one parent container and asks: “Which one is the first p? Which one is the first li? Which one is the first article?” It does this separately for each parent.

That means the first paragraph in one section and the first paragraph in another section can both match p:first-of-type. The selector is not global. It is scoped to sibling groups under a single parent.

p:first-of-type {
  background: #fef3c7;
}
section p:first-of-type {
  color: #92400e;
  font-weight: 700;
}
aside p:first-of-type {
  color: #1d4ed8;
  font-style: italic;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 1rem;
font-family: Arial, sans-serif;
}

section,
aside {
border: 1px solid #d0d7de;
border-radius: 0.75rem;
padding: 1rem;
background: #fff;
}

p {
margin: 0.5rem 0;
}

First paragraph in the section.

Second paragraph in the section.

CSS first of type nested

Nested structures are where people sometimes squint at the screen and wonder whether CSS has betrayed them. It has not. It is just being very literal. A nested element is checked against its own siblings inside its own parent.

So if you write .card p:first-of-type, CSS finds every paragraph inside a .card that is the first paragraph within its own parent. That can include a paragraph in the card body, another paragraph inside a nested blockquote, and another inside a footer, as long as each one is the first p in its local sibling group.

.card p:first-of-type {
  color: #7c2d12;
  font-weight: 700;
}
.card > p:first-of-type {
  color: #0f766e;
  font-weight: 700;
}
.card blockquote p:first-of-type {
  background: #ede9fe;
  padding: 0.5rem;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.card {
font-family: Arial, sans-serif;
border: 1px solid #d0d7de;
border-radius: 0.75rem;
padding: 1rem;
background: #fff;
display: grid;
gap: 0.75rem;
}

.card p,
blockquote {
margin: 0;
}

blockquote {
border-left: 4px solid #8b5cf6;
padding-left: 0.75rem;
color: #374151;
}

The first direct paragraph inside the card.

The second direct paragraph inside the card.

The first paragraph inside the blockquote.

The second paragraph inside the blockquote.

Notice the important difference between these two selectors:

  • .card p:first-of-type looks for first paragraphs anywhere inside the card.
  • .card > p:first-of-type looks only at direct child paragraphs of the card.

That little > combinator can save you from accidentally styling nested content that was minding its own business.

Learn more about the direct child selector in the CSS Direct Child Selector (>) Interactive Tutorial.

CSS first of type class

Here is the big gotcha: :first-of-type is not a “first element with this class” selector. It is based on the tag name only. So a selector like .note:first-of-type means “an element with class note that is also the first element of its tag type among siblings”.

That is a subtle but very important difference. If you have several div elements and only some of them have class note, then .note:first-of-type does not mean “the first .note”. It means “a .note that also happens to be the first div”.

.note:first-of-type {
  background: #dcfce7;
  border: 2px solid #16a34a;
}
div.note:first-of-type {
  background: #dbeafe;
  border: 2px solid #2563eb;
}
.note {
  background: #fef3c7;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.stack {
display: grid;
gap: 0.5rem;
font-family: Arial, sans-serif;
}

.stack > div {
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #d0d7de;
background: #fff;
}
Regular div before the notes
First .note
Second .note
Regular div after the notes

In that markup, the first .note is not the first div, because another div came before it. So .note:first-of-type does not match it. This is one of the most common misunderstandings around the selector.

Using :nth-child(1 of .selector) for the first matching class

When you truly want “the first element matching this class or selector”, the better modern tool is :nth-child(1 of .selector). The optional of <selector> part filters the sibling list before counting. So :nth-child(1 of .note) means “the first sibling that matches .note”.

This is why it is such a good companion to :first-of-type. Think of it this way:

  • Use :first-of-type when you care about the first tag type.
  • Use :nth-child(1 of .selector) when you care about the first matching class or selector.

The of S clause is newer than classic :nth-child(). Before using it in production, check current support on Can I use: caniuse.com/css-nth-child-of. As of writing, support sits at ~92% globally.

:nth-child(1 of .note) {
  background: #dcfce7;
  border: 2px solid #16a34a;
}
.stack > :nth-child(1 of .note) {
  color: #166534;
  font-weight: 700;
}
.stack > .note:first-of-type {
  background: #fee2e2;
  border: 2px solid #dc2626;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.stack {
display: grid;
gap: 0.5rem;
font-family: Arial, sans-serif;
}

.stack > div {
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #d0d7de;
background: #fff;
}
Regular div before the notes
First .note
Second .note
Regular div after the notes

The third snippet in that playground is there on purpose: it shows how .note:first-of-type and :nth-child(1 of .note) are not interchangeable. One counts tag type. The other counts matching selector results. Similar vibe, very different job.

Learn more about the :nth-child(1 of .selector) syntax in the CSS Nth Child (N of Selector) Interactive Tutorial.

CSS first of type with different element types

Another useful thing to understand is that every tag type has its own “first”. Inside one parent, you can have a first h2, a first p, a first ul, and a first img, all at the same time.

That means multiple elements inside one parent can match different :first-of-type selectors. CSS is not picking one universal winner. It is picking the first of each type.

h2:first-of-type {
  color: #7c3aed;
}
p:first-of-type {
  background: #fef3c7;
  padding: 0.5rem;
}
ul:first-of-type {
  border: 2px dashed #1d4ed8;
  padding: 0.75rem 0.75rem 0.75rem 2rem;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.box {
font-family: Arial, sans-serif;
border: 1px solid #d0d7de;
border-radius: 0.75rem;
padding: 1rem;
background: #fff;
}

.box > * {
margin: 0.5rem 0;
}

First h2

First paragraph

  • First list
  • Still first list

Second paragraph

Second h2

  • Second list

CSS first of type not working

When :first-of-type seems not to work, the problem is usually one of a few familiar culprits. The selector itself is usually innocent. Let’s put it on the witness stand anyway.

It is not the first of that tag type

This is the most common issue. Maybe you expected the first element with a class, but CSS is actually checking the first div, first p, or first li. If another element of the same tag appears earlier, your target will not match.

The element is in a different parent

Structural pseudo-classes work within one parent’s child list. If your elements are wrapped differently than you thought, the selector may be matching somewhere else, or not at all.

A more specific selector is overriding it

Your :first-of-type rule may match correctly, but another rule with higher specificity or later source order may be winning in the cascade. If the browser inspector shows the rule crossed out, this is your clue.

Learn more about the cascade in the CSS Cascade Interactive Tutorial, and about specificity in the CSS Specificity Interactive Tutorial.

You needed a direct child selector

A selector like .wrapper p:first-of-type can match nested paragraphs. If you meant only the direct child paragraph, use .wrapper > p:first-of-type.

You actually needed a class-based first match

If your real goal was “the first .item” rather than “the first li” or “the first div”, use :nth-child(1 of .item) instead of :first-of-type.

.item:first-of-type {
  background: #fee2e2;
}
:nth-child(1 of .item) {
  background: #dcfce7;
}
.list > .item:first-of-type {
  outline: 3px solid #2563eb;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.list {
display: grid;
gap: 0.5rem;
font-family: Arial, sans-serif;
}

.list > div {
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #d0d7de;
background: #fff;
}
Regular box before the items
First .item
Second .item

Specificity and best practices

:first-of-type is usually nicest when it reads naturally with a clear parent context. For example, .article-body > p:first-of-type is easier to understand than an overly broad selector that might style paragraphs in unexpected nested areas.

  • Prefer a parent scope when styling content sections.
  • Use > when you mean direct children.
  • Use :first-of-type for tag-based structure.
  • Use :nth-child(1 of .selector) for selector-based counting.
  • Check the browser inspector when something appears not to work.
.article-body > p:first-of-type {
  font-size: 1.15rem;
  color: #1d4ed8;
}
.article-body p:first-of-type {
  color: #7c2d12;
}
.article-body > :nth-child(1 of .callout) {
  border: 3px solid #7c3aed;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.article-body {
display: grid;
gap: 0.75rem;
font-family: Arial, sans-serif;
border: 1px solid #d0d7de;
border-radius: 0.75rem;
padding: 1rem;
background: #fff;
}

.callout {
padding: 0.75rem;
border-radius: 0.5rem;
background: #f5f3ff;
}

blockquote {
margin: 0;
padding-left: 0.75rem;
border-left: 4px solid #c4b5fd;
}

Direct child introduction paragraph.

First callout block.
Second callout block.

Nested paragraph inside blockquote.

Another direct child paragraph.

Quick cheat sheet

  • p:first-of-type = first p among siblings.
  • .card p:first-of-type = every paragraph inside .card that is first among its own paragraph siblings.
  • .card > p:first-of-type = first direct child paragraph of .card.
  • .note:first-of-type = a .note element that is also the first of its tag type.
  • :nth-child(1 of .note) = the first sibling that matches .note.

Final thoughts on CSS first of type

:first-of-type is one of those selectors that feels tiny but solves a surprising number of layout and content-styling problems. It is excellent for lead paragraphs, first headings, first cards of a given tag, and structured content where you want styling to follow the HTML pattern instead of relying on extra classes.

The most important takeaway is this: it counts element types, not classes. Once that clicks, the selector becomes much easier to predict. And when you do need “the first matching class”, that is where :nth-child(1 of .selector) steps onto the stage in a dramatic but very helpful fashion.

Share:

Leave the first comment