CSS Last of Type: A Practical Guide for Modern Layouts
Sidharth Nayyar

:last-of-type styles the last element of a specific tag among siblings, even when it isn't the final child overall. It has been broadly available across major browsers since July 2015, and the related :nth-last-of-type() selector has 96.27% global usage support.
You've probably hit this bug already. You try to remove the bottom border from the “last item,” use :last-child, and nothing happens because a different element sits after it in the markup.
That's the moment :last-of-type starts to make sense. It doesn't ask, “What is the final child in this parent?” It asks, “What is the last p, or the last li, or the last img, among these siblings?” That difference feels small until you build real layouts with cards, wrappers, helper text, icons, and conditionally rendered UI.
Introduction Styling the Final Sibling
A common example is article content. You want the last paragraph to lose its bottom margin so the section ends cleanly. But the parent also contains a button, a caption, or a note, so p:last-child fails. p:last-of-type works because it only compares paragraphs against other paragraphs.

Here's the mental shortcut I give junior developers. Think in tag families, not just in document order. If a parent contains three p elements and then a button, the last paragraph is still the last paragraph. It's just not the last child.
.article-content p:last-of-type { margin-bottom: 0; } <div class="article-content"> <p>Intro copy.</p> <p>Main explanation.</p> <p>Closing note.</p> <button>Read more</button> </div> That selector hits Closing note, not the button, and not nothing. That's why it's such a useful part of the CSS toolkit for mixed layouts.
When this selector saves you time
- Mixed content blocks: A heading, paragraph, list, and button can live in one parent.
- Editorial layouts: CMS content often produces uneven markup.
- UI cleanup: Removing the final border, separator, or margin becomes simpler.
Practical rule: Use
:last-of-typewhen your styling intent is tied to an element's tag, not to whether it happens to be the absolute last node.
If you're also working on clearer markup structure, this guide to semantic HTML best practices is a useful companion because selector behavior becomes easier to predict when the HTML itself is intentional.
How CSS Last of Type Understands Your Code
CSS doesn't “see” your page the way a user sees it. It reads the DOM as a tree of parent, child, and sibling relationships. That's the part many tutorials rush past, and it's the part that prevents bugs.
Think of the DOM like a family
A parent element contains children. Children inside the same parent are siblings. :last-of-type compares an element only with siblings that share the same tag name.
If you have this HTML:
<div class="content"> <p>First paragraph</p> <span>Inline note</span> <p>Second paragraph</p> </div> then p:last-of-type selects the second p. The span doesn't disqualify it, because the span belongs to a different tag group.

That's the key mental model. The browser doesn't ask, “Is this the final thing in the box?” It asks, “Is this the last p among all p siblings under this parent?”
What the spec-level definition tells you
The CSS Selectors spec defines :last-of-type as equivalent to :nth-last-of-type(1), which means it's the count-from-the-end version for a given element type, not a generic “last item” selector. The W3C also shows a classic example, tr > td:last-of-type, for styling the last table cell in each row, which makes the behavior especially easy to picture in structured markup like tables and content grids (W3C reference on :last-of-type).
That equivalence matters because it explains why the selector feels so consistent. It belongs to a broader family of structural selectors that count position relative to siblings of the same type.
A compact way to debug your thinking
When a selector surprises you, run this checklist:
- Find the parent: Which element contains the target and its siblings?
- List the siblings of the same tag: Ignore everything with a different tag name.
- Count from the end: The last one in that tag group is the match.
If your selector feels “wrong,” the DOM usually isn't wrong. Your mental model of the sibling group is.
Developers who get comfortable with this tend to write cleaner section-level markup too. That's one reason WebAbility.io's guide to section tags pairs nicely with selector work. Better structure makes structural selectors easier to reason about.
Comparing Last of Type with Similar Selectors
The most common bug here is simple. You mean “last paragraph,” but you write :last-child. Those are not the same thing.

:last-of-type versus :last-child
Use this HTML:
<div> <p>First P</p> <span>Span</span> <p>Second P</p> <i>Italic</i> </div> Now compare:
p:last-of-type { color: blue; } p:last-child { color: red; } p:last-of-type matches Second P. p:last-child matches nothing, because that paragraph is not the final child in the parent. The final child is <i>.
A frequent source of bugs is exactly this confusion. :last-of-type selects the last sibling of its specific element type, while :last-child only selects an element if it is the absolute last child within its parent, regardless of type (W3Schools reference on the distinction).
Side-by-side quick reference
| Selector | What it checks | Best use |
|---|---|---|
:last-of-type | Last sibling of the same tag | Last paragraph, image, table cell, list item type |
:last-child | Absolute final child in parent | Parent's true final child |
:nth-last-of-type(n) | Nth matching tag from the end | Reusable count-from-end patterns |
Where :nth-last-of-type(n) fits
Once you understand :last-of-type, :nth-last-of-type(n) feels natural. It's the more general tool. You're saying, “Give me the matching tag that sits n places from the end.”
Examples:
li:nth-last-of-type(2) { font-weight: 600; } img:nth-last-of-type(4) { opacity: 0.8; } That can be useful in content blocks where you want a slightly different treatment for the last few repeated items of the same tag.
A real-world decision rule
Pick the selector based on your intent:
- Use
:last-childwhen any final node should match. - Use
:last-of-typewhen only the last occurrence of a specific tag matters. - Use
:nth-last-of-type(n)when the pattern needs to scale.
Don't ask, “Which selector is more powerful?” Ask, “Which one matches the relationship I mean?”
This also ties into visibility and rendered state. If you're troubleshooting why a node still affects layout or selection logic, WebAbility.io insights on display properties help clarify how hidden versus non-rendered elements affect what you see versus what exists in the DOM.
Practical Examples for Everyday Layouts
Most developers don't need another toy example with three empty tags. You need patterns you can paste into a real project and trust.

Remove spacing from the final paragraph
This is the classic editorial use case.
<article class="post-body"> <p>Shipping updates are posted weekly.</p> <p>Members get early access to feature rollouts.</p> <p>Questions can be sent to support.</p> <a href="/contact">Contact us</a> </article> .post-body p { margin-bottom: 1rem; } .post-body p:last-of-type { margin-bottom: 0; } Why this works: the link after the paragraphs doesn't matter. CSS still finds the last p among its p siblings.
Style the final image in a gallery
Responsive layouts often mix images with captions, wrappers, or controls. If you're refining gallery behavior, it helps to also understand how responsive websites work because image flow and DOM structure often interact in subtle ways.
<div class="gallery"> <img src="one.jpg" alt="Product front view"> <img src="two.jpg" alt="Product side view"> <img src="three.jpg" alt="Product detail"> <button>Open lightbox</button> </div> .gallery img { border-radius: 0.5rem; opacity: 0.9; } .gallery img:last-of-type { opacity: 1; outline: 2px solid #337eee; } That last image gets special treatment even though the button comes afterward.
Clean up the final form field group
This pattern shows up in account settings, checkout forms, and profile editors.
<div class="field-group"> <div class="field">Name input</div> <div class="field">Email input</div> <div class="field">Phone input</div> <p class="help-text">We only use this for order updates.</p> </div> .field-group .field { padding-bottom: 1rem; border-bottom: 1px solid #ddd; margin-bottom: 1rem; } .field-group .field:last-of-type { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; } The helper paragraph doesn't interfere because the selector targets the last .field div among div siblings of that type in that parent context.
Here's a short walkthrough if you want to see the idea in motion:
One more place this pattern shines is tables. For example, tr > td:last-of-type is a reliable way to target the final cell in each row. If you're building or debugging tabular markup, WebAbility.io's table code guide is worth keeping nearby.
Advanced Use Cases and Accessibility
A common production bug starts like this. A card list looks tidy in Storybook, then a status badge, hidden helper message, or tracking hook gets inserted by the app, and the spacing rule that depended on :last-of-type starts hitting the wrong element.
That happens because :last-of-type reads the DOM tree, not the layout you see on screen. It behaves like a teammate checking element tags in the parent container and asking, “Which p, div, or li is the last one of this kind here?” If the component gains another sibling of that same element type, the answer changes.
CSS-Tricks makes this tradeoff clear. In component-driven interfaces, :last-of-type can become brittle when content is reordered or when assistive-only nodes change sibling structure, because the selector follows document structure rather than visual order (CSS-Tricks on :last-of-type).
The practical lesson is simple. Use :last-of-type where the parent-child structure is predictable across states, and choose a different spacing strategy where children are added, removed, or rearranged at runtime.
Choosing patterns that hold up in dynamic UIs
For layout work, the safest option is often to put spacing on the container instead of removing it from the final item. gap on flex and grid is a good example. You are defining the space between items, not creating extra space on every child and then cleaning up the tail end later.
Other dependable options work well in component systems too:
- Use
gapfor repeated items. This avoids “remove the last margin” rules entirely. - Let rendering logic mark the final item. If the component already knows which child is last, a class or data attribute is often clearer than a structural selector.
- Keep spacing decisions at the component boundary. Design tokens and component-level utilities make behavior easier to predict across templates.
- Reserve structural selectors for stable markup. Static article content, fixed form groups, and predictable tables are usually good candidates.
A good rule of thumb is to avoid styling that depends on cleanup. Spacing systems age better when they define relationships between items instead of patching the final one.
Accessibility starts with predictable structure
This is not only a layout concern. It affects accessibility too.
Accessible interfaces depend on consistency. If helper text appears conditionally, if validation messages are inserted after an input, or if visually hidden content shares the same element type as visible content, :last-of-type can shift in ways that change separators, spacing, or emphasis. Screen reader users may not notice the visual change directly, but inconsistent grouping can make the interface harder to scan for people using zoom, keyboard navigation, or cognitive support strategies.
A simple example is a settings panel where each .field has a divider except the last one. If an error message is injected as another div, your CSS may remove the divider from the wrong block. The form still functions, but the visual grouping becomes less clear right when the user needs clarity most.
That is why structural selectors should be reviewed as part of accessibility work, not treated as isolated CSS tricks. Browser DevTools, component tests, and WebAbility.io can help teams catch template states where layout and accessibility behavior drift apart.
A safer mindset for teams
Use :last-of-type when the markup is stable and the element type has a clear, lasting meaning in that parent.
If the UI is assembled from conditional children, slot content, or framework-driven wrappers, choose spacing patterns that stay correct even when the DOM changes. That small decision prevents the kind of visual regressions that are easy to miss in review and expensive to clean up later.
Browser Support and Performance Insights
You can use :last-of-type with confidence in modern front-end work. MDN notes that it has been broadly available across major browsers since July 2015, and the related :nth-last-of-type() selector has 96.27% global usage support, which shows how mature this selector family is today (MDN browser support for :last-of-type).
That support story matters because structural selectors used to make teams nervous, especially on older projects. For current browser targets, this one is established.
Performance in practice
Performance usually isn't the deciding factor here. Modern browser engines are built to evaluate selectors efficiently. The bigger concern is clarity.
A selector like this is easy to reason about:
.card p:last-of-type { ... } A selector like this gets harder to maintain:
.page .wrapper .card-content p:last-of-type { ... } Keep selectors scoped and readable. If a rule becomes difficult to explain to another developer, it's usually a sign the markup or the selector strategy needs a small cleanup.
Common Questions About CSS Last of Type
Can I combine :last-of-type with a class
Yes. The class filters which elements can match, but :last-of-type still asks a separate question: “Is this the last p among its sibling p elements?”
.article p.note:last-of-type { color: #444; } That distinction matters in component code. If another p is rendered after this one, your .note paragraph stops matching, even though its class never changed. The selector is reading the DOM tree, not your naming intent.
Why isn't my style applying in nested markup
Start by checking the parent element. :last-of-type only compares siblings that share the same parent, which trips people up in nested layouts.
<div class="wrapper"> <div><p>One</p></div> <div><p>Two</p></div> </div> Here, each p sits in a different div, so each one is the last p in its own small branch of the tree. The browser does not look across wrappers.
A helpful way to read this is branch by branch. The DOM works like a family tree. :last-of-type checks for the last matching child in one household, not the last matching relative anywhere on the page.
How do I target the fourth item from the end instead
Use :nth-last-of-type(4). It follows the same logic, just with a different count from the end.
li:nth-last-of-type(4) { background: #f5f8ff; } If you already understand :last-of-type, this is the same mental model with a larger number.
What should I do when dynamic content makes this brittle
In dynamic interfaces, structure can shift without warning. A badge appears. A helper message is injected. A hidden element becomes visible at a breakpoint. Suddenly the element you thought was “last” is no longer last, and spacing or borders break in ways that are hard to spot during review.
That is why :last-of-type works best when the markup pattern is stable and the rule expresses real document structure, not just visual cleanup. For layout spacing in flexible components, gap, container spacing rules, or explicit utility classes are often easier to maintain.
Accessibility is part of this decision too. If a screen reader only user encounters extra status text, validation help, or live region updates added to the DOM, selectors tied to “the last paragraph” can start styling the wrong thing. Visual polish should not depend on assumptions that dynamic, assistive, or state-driven content might change.
Use
:last-of-typewhen the document structure means something. Use component-level styling when the UI can rearrange itself.
If your team is refining CSS patterns while also improving inclusive UX, WebAbility.io provides accessibility tooling, monitoring, and implementation support that fits into front-end workflows without changing how you build everyday components.
Quick Questions
Tap to ask AI about this article







