The CSS Selection
The state of real-world CSS usage, 2026 edition.
Introduction
Welcome to The CSS Selection 2026! In this article we’re having a look at how CSS is used at scale on over 100,000 websites. We’ll look at what things are common on most websites and discover interesting outliers. This is the first edition of what I hope to be many, so this is meant as a baseline for future editions, setting up the first numbers to compare with in coming years.
This article exists for several reasons, but the Web Almanac is the most prominent one. For several years the Web Almanac has skipped the CSS chapter, the last one published in 2022. This is mainly because of a shortage of authors and editors but mostly analysts who could wrangle BigQuery to get hold of large amounts of CSS data to analyze and put it in readable charts. Additionally, the Web Almanac uses a regex-based CSS analyzer that differs a lot from Project Wallace’s analyzer. This has bothered me for years because I think the CSS community deserves a yearly overview, as well as that overview having the best-in-class analysis. Now, I can’t crawl millions of websites like the HTTP Archive can, but I can do 100,000 and still make a pretty decent overview. This mostly explains why there will be some differences in what Almanac articles have found in previous years and what our analysis shows.
CSS has taken flight in recent years with many new features, properties, values, at-rules and so much more. With power shifting to CSS, it’s interesting to look at the use of these new features as well as to keep an eye on global metrics like file size, units used etc. We’ll work our way down from the top, so we’ll start by looking at stylesheet composition, followed by at-rule analysis, then rules, selectors, declarations, and lastly, values and units.
Stylesheet composition
To get a global idea of what we’re looking at, it’s good to take a look at some numbers from a bird’s-eye view. What is our CSS made up of?
CSS Size
How much CSS does a website ship to their users? For this we simply look at how large the string of CSS is if we put it in one single, continuous, uncompressed string.
Lines of code
Because most (?) websites minify their CSS it’s also relevant to look at the lines of code. In more traditional programming languages this is a common metric to get a sense of the magnitude of a program. For CSS, I’ve determined that:
source lines of code = # of rules + # of at-rules + # of declarations
With that established, let’s look at the numbers:
It looks like most websites manage to stay below or around 10,000 lines of code. That’s still a lot of CSS, but looking at any regularly sized website tells you that declaring your design system with a bunch of custom properties and loading some third-party tools quickly ramps up the numbers.
Further analysis of at-rules/rules/selectors/declarations per page is in their respective chapters.
Stylesheet complexity
One of my favorite metrics is complexity. Using it besides the Source Lines of Code and file size gives a quick impression, even more so when comparing the number between websites. Or different versions of the same website, like a staging environment and a production environment. This chart shows the total complexity of all the CSS on a page, including that for complex selectors, property browser hacks, use of vendor prefixes and much more.
The distribution across percentiles here is pretty much the same as for lines of code.
Embedded content
One contributor to file size is embedded content. A good portion of websites embed various types of content in their CSS, like encoded images or sometimes even entire WOFF2 files.
This is an interesting distribution because we can see both the 10th and 25th percentiles don’t embed any content at all. Only from the 50th percentile onwards do we see a little bit of embedding, with the chart steeply rising from the 90th percentile.
Comments
Another contributor to file size can be the presence of comments. Minifiers usually strip most comments but some remain, like copyright notices.
It’s encouraging that, like the embedded content, the amount of comments in shipped CSS is pretty low.
At-rules
At-rule totals
Let’s start by looking at how many at-rules we’re shipping. Not looking at which ones specifically, just count how many there are in total.
Looks like most websites have their at-rules well under control. With modern CSS there’s a lot to control with at-rules, like containers, media, keyframes and supports. You need a bunch of them to get a properly functioning website, so let’s dive into how we actually use the various at-rules.
A quick look at the adoption rates of the various at-rules that we will further analyze in this chapter.
| Atrule | Adoption % | Relative count |
|---|---|---|
| @media | 93.06% | |
| @font-face | 85.62% | |
| @keyframes | 83.90% | |
| @supports | 44.57% | |
| @charset | 39.57% | |
| @import | 18.04% | |
| @container | 9.61% | |
| @layer | 2.71% | |
| @property | 2.67% |
@media is the undisputed king of at-rules (93%), closely followed by @font-face (85%) and @keyframes (83%). This seems like a very reasonable list of adoption rates. Let’s look at the at-rules in more detail.
@media
Our versatile friend the media query: always there to help with that responsive design, making that page print-ready or adjusting the layout for the folks who love that extra dash of contrast. So many possibilities!
Let’s start by looking at how many of them are on the page at all:
Looking at the difference between this graph and the one above with at-rule totals, I think it’s safe to say that the largest amount of at-rules on a page would be @media. And that would make a ton of sense if you look at the versatility of it. So, what do we use it for actually?
| Feature | Adoption % | Relative count |
|---|---|---|
| max-width | 88.29% | |
| min-width | 86.45% | |
| prefers-reduced-motion | 44.15% | |
| orientation | 25.06% | |
| hover | 23.79% | |
| max-height | 23.23% | |
| -webkit-min-device-pixel-ratio | 22.60% | |
| min-resolution | 20.32% | |
| -ms-high-contrast | 15.65% | |
| max-device-width | 13.32% | |
| forced-colors | 8.59% | |
| pointer | 7.54% | |
| min-device-pixel-ratio | 6.35% | |
| -webkit-transform-3d | 6.07% | |
| transform-3d | 5.94% | |
| min--moz-device-pixel-ratio | 5.75% | |
| min-device-width | 5.66% | |
| min-height | 5.52% | |
| prefers-color-scheme | 5.11% |
The max-width and min-width features are quite unexpectedly used on most websites (88% and 86%). I did not expect prefers-reduced-motion to have such a great adoption rate even though it’s almost half of that of the top two. There are some other surprises here, like forced-colors being used a bunch more than prefers-color-scheme.
@font-face
With @font-face Baseline widely available since July 2015, it’s hard to imagine the days that we had to embed images or use Flash to make our typography look great. Luckily, @font-face has made our lives a lot easier. How much do we use it?
Given the fact that you need multiple @font-face rules for every weight and italics, it makes sense that the numbers here are as they are. I actually expected the numbers to be a bit higher. Maybe some of you are experiencing some faux-bolds?
@keyframes
Continuing the making-our-website-pretty theme with a look at @keyframes. It’s hard to imagine a website without any kind of animation and @keyframes has been a cross-browser help with that since September 2015. Every website has at least one spinner nowadays, right?
Okay, maybe not every website then. 10% of them are as static as can be!
| @keyframes name | Adoption % | Relative count |
|---|---|---|
| fa-spin | 25.68% | |
| spin | 23.90% | |
| progress-bar-stripes | 21.44% | |
| fadeIn | 18.53% | |
| pulse | 15.78% | |
| fadeOut | 15.07% | |
| swiper-preloader-spin | 14.51% | |
| bounce | 11.28% | |
| turn-off-visibility | 10.95% | |
| lightbox-zoom-out | 10.94% | |
| lightbox-zoom-in | 10.94% | |
| turn-on-visibility | 10.94% | |
| fadeInUp | 10.62% |
It looks like @keyframes do make things spin around. fa-spin is a clear sign of FontAwesome being present. The turn-off-visibility seems to come from the WordPress Gutenberg editor as well as the two lightbox-zoom-* keyframes.
@supports
This at-rule is still evolving into something better even though it has been in our toolbox for quite a while now (Baseline since September 2015!). Where we first only could check if a certain declaration was supported, we can now also check for the support of selectors, font-tech and font-format!
Uh-oh, not quite what I expected. Either everyone’s progressive enhancement game is top-notch or we’re missing out big time on some quality of life improvements. I really wonder why the adoption rate of @supports is this low. An underrated tool, it seems.
Unique @supports queries
Even though we don’t have a ton of @supports usage, it’s still fun to look at which unique queries are used most often. Note that this is not a normalized list of features, just unique values.
| @supports query | Adoption % | Relative count |
|---|---|---|
| (-webkit-mask-image:none) or (mask-image:none)) or (-webkit-mask-image:none) | 11.96% | |
| (position:sticky) | 11.91% | |
| (-webkit-touch-callout:inherit) | 9.89% | |
| (position:-webkit-sticky) or (position:sticky) | 4.59% | |
| not (container-type: inline-size)) or (not (selector(:has(*)) | 3.36% | |
| (-webkit-appearance:none) | 3.03% | |
| (display:grid) | 2.52% | |
| (-ms-ime-align:auto) | 2.21% | |
| (-webkit-touch-callout:none) | 2.12% | |
| (-webkit-overflow-scrolling:touch) | 1.90% | |
| (display: grid) | 1.49% | |
| (position: sticky) | 1.40% | |
| (padding:max(0px)) | 1.39% | |
| (outline-offset:-3px) | 1.24% | |
| (-webkit-touch-callout: none) | 1.20% | |
| (color:color-mix(in lab,red,red)) | 1.09% |
@supports browserhacks
Another creative use of @supports is to use it to target specific browsers. Again, a table of non-normalized data, but look at what people are doing to be very specific. There are some really nasty ones in this list, and I sure hope you’re not actively taking part in this.
| @supports query | Adoption % | Relative count |
|---|---|---|
| (-webkit-appearance:none) | 3.03% | |
| (-webkit-appearance:none) and (stroke-color:transparent) | 0.50% | |
| (-webkit-appearance: none) | 0.29% | |
| (-moz-appearance: meterbar) | 0.18% | |
| (font: -apple-system-body) and (-webkit-appearance: none) and (-webkit-hyphens: none) | 0.15% | |
| (hanging-punctuation:first) and (font:-apple-system-body) and (-webkit-appearance:none) | 0.10% | |
| (-moz-appearance:meterbar) | 0.09% | |
| (-webkit-appearance: none) and (stroke-color: transparent) | 0.08% | |
| (-webkit-appearance:none) or (-moz-appearance:none) or (appearance:none)) or ((-moz-appearance:none) and (mask-type:alpha) | 0.07% | |
| (-webkit-appearance: none) or (-moz-appearance: none) | 0.07% | |
| (-webkit-appearance:none) or (-moz-appearance:none) | 0.06% | |
| (font:-apple-system-body) and (-webkit-appearance:none) | 0.06% | |
| (hanging-punctuation: first) and (font: -apple-system-body) and (-webkit-appearance: none) | 0.04% |
@import
If our previous at-rule was used so little, let’s hope this one is too! Using @import is usually an anti-pattern unless you know what you’re doing.
Wow, job well done internet! Only 18.04% of websites analyzed use @import. The 90th percentile only has 1 @import at most.
For next year I’d be curious about the usage of layers, supports queries and media queries inside @import rules. This at-rule is a pretty versatile beast, and I wonder if we’re taking full advantage of it when we use it.
@layer
Baseline widely available since March 2022, we can control our specificity and cascade more with @layer. Compared to other at-rules, the adoption rate is quite low, so it shows nothing on our percentiles chart. At least I found one website with… checks notes… 1,100 @layer at-rules. It’s a good thing that we’re not tracking averages.
@layer use seems mostly powered by the use of TailwindCSS but also the use of layers like legacy and global seem encouraging patterns of developers using @layer. Especially @layer legacy seems like a trick that developers use to incrementally modernize their codebase. I’ve skipped some named layers at the bottom of the chart so I could include <anonymous>.
| @layer name | Adoption % | Relative count |
|---|---|---|
| base | 1.85% | |
| utilities | 1.80% | |
| components | 1.76% | |
| theme | 1.64% | |
| properties | 1.48% | |
| reset | 0.29% | |
| legacy | 0.16% | |
| component | 0.13% | |
| global | 0.10% | |
| tokens | 0.10% | |
| <anonymous> | 0.04% |
@charset
Almost 40% of websites use @charset, and an overwhelming majority use UTF-8 encoding. It is interesting to see that there is a distinct group using Chinese (gb2312), Japanese (shift_jis) and Cyrillic (windows-1251) encodings. This proves that there is a real use case for @charset even though most of us don’t use it that often.
| Charset | Adoption % | Relative count |
|---|---|---|
| utf-8 | 39.46% | |
| iso-8859-1 | 0.03% | |
| gb2312 | 0.02% | |
| shift_jis | 0.02% | |
| windows-1251 | 0.02% | |
| euc-jp | 0.02% | |
| euc-kr | 0.01% |
@container
Almost 10% of websites use @container already (well, it has been Baseline widely available since February 2023) is a pretty good number. It’s not enough to show anything on our percentiles chart so we’re omitting that.
Looking at the names that are used, there is no real pattern apart from the list of 8 hash-like gibberish names that appear in 0.16% of websites. Names like wrapper, card and welcome-panel speak more to the imagination.
| Container name | Adoption % | Relative count |
|---|---|---|
| wrapper | 3.38% | |
| welcome-panel | 0.35% | |
| welcome-panel-media | 0.35% | |
| dfposts | 0.26% | |
| card | 0.19% | |
| wpforms-field-row-responsive | 0.18% | |
| column | 0.17% | |
| [hash] like _1f7pmjs0 (7 rows omitted for brevity) | 0.17% | |
| wpforms-field-row-responsive-name-field | 0.15% | |
| wpforms-field-2-columns-responsive | 0.13% | |
| wpforms-field-3-columns-responsive | 0.13% | |
| media | 0.12% | |
| horizontal-product-card | 0.12% |
I wonder if the media container has anything to do with the ‘OG’ CSS media object.
@property
@property is Baseline newly available since July 2024, so it makes sense that most websites haven’t picked it up yet. In that regard, the current adoption ratio of 2.67% is already encouraging.
| Property name | Adoption % | Relative count |
|---|---|---|
| --tw-border-style | 1.63% | |
| --tw-font-weight | 1.61% | |
| --tw-leading | 1.53% | |
| --tw-inset-ring-shadow | 1.53% | |
| --tw-inset-shadow | 1.53% | |
| --tw-ring-offset-color | 1.53% | |
| --tw-ring-offset-shadow | 1.53% | |
| --tw-ring-offset-width | 1.53% | |
| --tw-ring-shadow | 1.53% |
After analyzing the most common @property names, I found that by far the most popular were TailwindCSS property names like --tw-border-style, --tw-font-weight etc. with most of them having an adoption rate between 1.63% and 0.12%. After that is a huge list of --x-[hash], properties which seem to be entirely computer-generated. It doesn’t really make sense to show the full chart here, but it’s an interesting observation. My conclusion is that the most common usage of @property seems to be tied to some kind of (CSS) framework.
Rulesets
Each stylesheet consists of one or more rulesets or rules, unless you only ship some at-rules, like the Google Fonts API does. They are the cornerstone of how CSS works: one or more selectors and some declarations and you have yourself a ruleset. With that in mind let’s see how most rulesets are constructed and used.
Rules per page
Starting off with a simple but relevant metric: how many CSS rulesets are there per website analyzed?
The maximum I could find was 210,695 rules on a single page.
Selectors per rule
The most common number of selectors on a rule is 1. For this metric we look at the maximum number of selectors found on a single rule.
The maximum number I could find was 128,528 selectors on a single rule. Most of the cases where there are so many selectors are caused by Sass’s @extend feature being used outrageously.
Declarations per rule
The most common number of declarations is 1, but for this metric we’ll look at the maximum number of declarations in a single rule.
These numbers are quite a bit higher than the ones above for maximum selectors per ruleset. My guess is that this is because we tend to author more rules with many declarations versus many rules with few selectors and declarations that tools like Tailwind generate. They show up as most common, but the human-authored outliers show up in this graph. Notice that the 90th percentile for this metric is a lot higher compared to that for selectors per rule.
The largest number of declarations in a single rule in our data set was 41,620.
Total rule size
When you add up selectors and declarations, you get the rule size.
rule size = # of rule selectors + # of rule declarations
This metric helps draw attention to those rules that don’t stand out for having many selectors or declarations but have a large total.
The most common size is 2, which makes sense if you look at the data for common selectors and declarations per rule. Also, like the chart for declarations per rule, the 90th percentile for this metric is quite a bit higher than the rest.
The maximum ruleset size in our dataset was 128,529.
Rule nesting depth
Developers love to nest CSS. For years we only had the option to nest media queries and some other at-rules but with CSS Nesting entering in 2023, we can go deep. But do we? Is it still only territory for preprocessors/postprocessors? Or do we avoid it?
That looks a bit underwhelming, but I think that’s a good sign. We’re not going overboard with this new feature yet.
Selectors
Love them or hate them, you need selectors to target the elements you want to change, whether it be for that fancy P3 color, or that 90’s grunge background-image.
Selector totals
Looking at the number of selectors in a stylesheet gives us a quick look into the magnitude of things.
From my experience analyzing websites, I expected the p90 and p75 to be a lot higher. I’m actually quite pleased with this distribution. Also, the top 10% of websites apparently have only 272 selectors or fewer, which is also surprising. This seems like a really low number to me.
Pseudo-classes
One concept I copied from the Web Almanac is their overview of popular pseudo-classes. It offers an interesting look into adoption of newer pseudo-classes and proportional use between classes.
| Pseudo-class | Adoption % | Relative count |
|---|---|---|
| hover | 95.24% | |
| where | 90.56% | |
| focus | 88.57% | |
| before | 87.58% | |
| after | 86.99% | |
| not | 86.39% | |
| last-child | 85.96% | |
| first-child | 85.76% | |
| active | 83.93% | |
| root | 79.14% | |
| nth-child | 76.82% | |
| disabled | 58.83% | |
| empty | 58.77% | |
| checked | 58.54% | |
| last-of-type | 57.32% | |
| visited | 54.65% | |
| nth-of-type | 53.19% | |
| first-of-type | 52.97% | |
| focus-visible | 48.20% | |
| -ms-input-placeholder | 44.10% | |
| has | 41.30% | |
| focus-within | 41.02% | |
| only-child | 36.97% | |
| is | 36.31% | |
| nth-last-child | 34.47% | |
| -moz-focusring | 32.99% | |
| -moz-placeholder | 25.68% | |
| link | 25.64% | |
| first-letter | 22.69% | |
| invalid | 21.42% | |
| host | 19% | |
| indeterminate | 18.31% | |
| -webkit-autofill | 17.04% | |
| valid | 16.50% | |
| placeholder-shown | 13.63% | |
| lang | 11.20% | |
| -moz-ui-invalid | 9.31% | |
| target | 6.11% | |
| -moz-placeholder-shown | 5.92% | |
| nth-last-of-type | 4.41% | |
| -webkit-full-screen | 3.77% | |
| enabled | 3.60% | |
| fullscreen | 3.59% | |
| required | 2.79% | |
| read-only | 2.53% | |
| only-of-type | 2.50% |
The 2022 Almanac listed :hover, :focus and :active as their top 3, but look at this: :where made it into the top 3! It has been Baseline widely available since January 2021, but this would be a good time to give yourself a pat on the back if you are a spec writer, or part of the CSS Working Group. And while you’re patting: :has is used on 41% of websites (Baseline newly available December 2023). That’s even higher than :is, clocking in at 36% (also Baseline widely available January 2021). It would be interesting to see if the use of :matches and :any will go down, as we use :is more. Regardless, these look like rock-solid adoption rates, if you ask me. We know that adoption of CSS features usually takes a bit, and seeing these newish ones up there makes you proud of the language.
Looking further down the list, we see :empty being used on 58% of websites. I find that surprising because I found it useful only on a handful of occasions.
Accessibility
Accessibility selectors are attribute selectors that check for the presence of [aria-] and [role=]. These are interesting because they tell a little bit about how accessibility is baked into the CSS. It’s never the complete story but it’s interesting nonetheless.
Based on my own experience, I find this number quite low. Most projects I’ve worked on have many more accessibility-focused selectors than this. Either everyone is setting their styles via regular class names (very valid), or we’re not focusing on accessibility that much.
Vendor prefixes
Sometimes we need to use non-standard selectors to select that one pesky thing in that one single browser. Think of :-moz-focusring, ::-webkit-scrollbar or ::-webkit-file-upload-button. Vendor-prefixed selectors are usually taken care of by CSS toolchains, where they take your modern authored CSS and add some prefixes where necessary, based on the required browser support.
Data shows that there are not that many vendor-prefixed selectors per website, and I’m curious to see if that number will go down in coming years as some of them will become obsolete. But on the other hand, some browser makers are shipping new vendor-prefixed selectors, so we might be seeing these for years and years to come.
Specificity
Our beloved metric: specificity. It’s a shame Wes Bos isn’t butchering the pronunciation of this as much as he did before. Jokes aside, specificity is one of the most misunderstood concepts in CSS, which is also a reason that so many people blogged about it and why online tools like Polypane’s Specificity Calculator exist. Some CSS analyzers get specificity wrong, but luckily we don’t, so we can show cool stuff like this:
The chart shows that 50% of websites have up to 39 unique specificities on their pages, which is a higher number than I had expected. Thinking about this a bit more actually leads me to think that this might be because CSS is such an expressive, capable language: there are so many ways to express selector intent, and increasingly more so with new selectors like :has() and :where().
Now let’s look at the most commonly used specificities:
| Specificity | Adoption % | Relative count |
|---|---|---|
| 0,1,0 | 97.06% | |
| 0,0,1 | 96.21% | |
| 0,1,1 | 96.14% | |
| 0,0,0 | 95.84% | |
| 0,2,0 | 94.57% | |
| 0,2,1 | 93.70% | |
| 0,3,0 | 91% | |
| 0,1,2 | 90.32% | |
| 0,3,1 | 88.96% | |
| 0,2,2 | 87.93% | |
| 0,4,0 | 85.65% | |
| 0,0,2 | 85.25% | |
| 0,3,2 | 81.94% | |
| 0,4,1 | 81.79% | |
| 1,0,0 | 79.28% | |
| 0,5,0 | 77.21% | |
| 0,1,3 | 76.62% | |
| 0,2,3 | 75.33% | |
| 1,1,0 | 72.63% | |
| 0,5,1 | 69.84% | |
| 0,4,2 | 69.50% | |
| 0,6,0 | 67.87% | |
| 0,3,3 | 66.32% | |
| 1,0,1 | 65.09% | |
| 1,1,1 | 64.69% | |
| 1,2,0 | 63.34% |
There’s absolutely nothing notable about this adoption rate chart. This is to be expected. One item I want to highlight is the no. 4 position of 0,0,0. This means that the high usage of :where() seems to translate into this chart, as well as it being very likely that most websites use the universal selector (*). The fun part is at the bottom (the list has 2,876 unique entries), where things definitely got out of hand.
Selector complexity
Selector complexity is an important metric next to measuring specificity. With the addition of :where() and :is(), we can not rely on counting the individual parts of the specificity, because these new pseudo classes nullify them. Therefore, we look at how many parts the selector is made up of. Example: :where(#reset-theme > *) has a specificity of 0,0,0 it has a complexity of 4.
Most selectors on most sites are simple selectors: they have only 1 or 2 parts to them. But what about the most complex selectors on any site?
This shows a different picture. Even simple websites sometimes need more complex selectors to express more complex state.
Combinators
Selector combinators let you define relationships between your selectors. We often use the descendant combinator without thinking about it, but what about the others?
As expected, the descendant combinator takes the top spot, but the child combinator (>) is a close second. The other two are a bit below that and for me that makes sense. That seems to align with how I write CSS myself.
Declarations
Declarations are where we start to apply actual styles. So far we’ve only selected the conditions: the device properties, media types, support levels, container requirements. Now it’s finally time to paint the pixels.
Declaration totals
We’ll start by looking at how many declarations websites are shipping in total.
The highest number of declarations in a single website found in our data set is 346,750.
!important usage
When cascade layers and specificity are not enough, there’s always the trusty !important.
Compared to the totals in the section above, I’m happy to see that the majority of websites only use a respectable number of importants. But what do the ratios of !important look like? What percentage of our declarations is marked as !important?
The maximum of !important found on a single website is 249,021. A small side note on the use of different !important notation just because I found some of them hilarious:
Important!IMPORTANT!impotant!imporStant!i!imporatnt!imPORTANT!importan!importantl!imporant!importnat!imortant!imprtant
Custom properties
One of the great power-ups of CSS in the last decade was the addition of custom properties. Finally we have our variables!
It’s slightly surprising that up to the 50th percentile custom properties aren’t used at all. Looking at adoption rates of far newer functionalities, I’d expect custom properties to have a higher adoption ratio. But once websites use them, they use them good.
In terms of custom property ratios compared to the total amount of properties, we see a similar picture.
Property browserhacks
A recent addition to our collective toolboxes has been ReliCSS. It detects a ton of questionable CSS hacks, including property hacks. These hacks were used in earlier times to target specific browsers and versions. For example, you could target IE6-7 specifically by writing *font-size: 10px or _width: 100px for IE 6 specifically. Are these property hacks still used in the wild?
A quick explainer using data from browserhacks.com:
*property: IE 6/7_property: IE 6/property: IE 6/7#property: IE 6/7+property: IE 6/7$property: IE 6/7
This was going to be an uplifting article about the new era of CSS and great adoption rates of shiny new features, but look at this monstrosity. Let’s move on before it affects our mood.
Vendor-prefixed properties
Slightly less disturbing than browser hacks but still a sign of the times and sometimes browser quirks: prefixes. A slightly more readable way of targeting specific browsers or rendering engines. Hopefully you don’t author them by hand because tools like LightningCSS and PostCSS can handle these things for you.
The use of vendor-prefixes for properties is not surprising. Many reset stylesheets include properties like -webkit-text-size-adjust, including the modern ones that everyone is blogging about in 2026.
A closer look at what percentage of all properties is vendor-prefixed tells a slightly reassuring story. Not that many properties are prefixed.
Values
Let me start with a rather disappointing note about this chapter. Because analyzing values is one of the areas where Project Wallace shines, but I made a bad decision early on in the scraping process. After an initial crawl started to fill my laptop’s drive rather quickly, I decided to cut out some ‘non-important’ metrics to save some disk space and speed up analysis. Boy, do I regret that. This decision means that I’ve analyzed and then thrown away all analysis about popular colors, font sizes, shadows, everything. All I have at this point are aggregate percentiles. So. This is not the values chapter I was hoping for but it’s the best I can do for this year.
Colors
Unique colors are analyzed by looking at their string representation, so Red is a different value than red and #f00. From a design systems perspective, counting like this makes sense, because you want all your colors to be uniform and coming from the same, single source of truth. This is what is counted when we look at the metric like that:
It would be very interesting to compare colors value-wise, so that is on my list for next year.
Then on to color formats. This table highlights what the most used color formats are across all websites analyzed.
| Color format | Adoption % | Relative count |
|---|---|---|
| hex6 | 97.10% | |
| hex3 | 94.94% | |
| transparent | 90.65% | |
| rgba | 90.44% | |
| named | 79.24% | |
| currentcolor | 59.29% | |
| rgb | 55.26% | |
| hex8 | 42.32% | |
| hex4 | 33.86% | |
| hsla | 32.08% | |
| system | 23.08% | |
| hsl | 6.20% | |
| oklch | 1.89% | |
| color | 0.56% | |
| oklab | 0.54% | |
| lab | 0.24% | |
| lch | 0.05% | |
| hwb | 0.03% |
The top 7 are very much as expected, but I’m surprised that 8-character hex colors are catching on so well with 42% adoption. Although I might be an old-school dev, because apparently this and 4-character hex codes have been Baseline widely available since January 2020…
Further down the list we have HSL(A) still beating OKLCH by some margin despite active campaigns to get us onto the better format.
Font families
Apart from declaring custom @font-face families, there’s also the point of using actual families for your styling. How many unique families do websites use in their CSS?
This data actually overlaps quite well with the use of @font-face, with the number of families used always being slightly higher than the number of custom families declared.
Font sizes
Similar to the color analysis, we only compare font sizes by their string representation, so 1.2em is not the same as 120% in our analysis, even though the browser will render them the same.
This table matches my expectations quite well, but I’m always slightly surprised how we end up with so many unique font sizes on our websites. There are countless articles out there explaining how to create a font scale and use that but I guess reality comes at us quickly when it comes to CSS.
Line heights
Line heights are often, but not always, set in combination with font-size and/or font-family, often in the font shorthand. So it is no surprise that this graph is similar in shape to the the font-size and font-family charts, but just lower in numbers.
Box shadows
One metric that is under-analyzed but often used in design systems is the humble box shadow. Not contributing to the box model, but it does play a role in branding and UX.
Text shadows
Like box shadows, text shadows can help with creative effects or even help improve readability of text on top of images (wow, that CSS-Tricks article is from 2014!).
The use of text-shadows appears to be very limited. It makes me wonder why that is. Don’t we see the value in it? Is it too distracting?
Z-indexes
The seemingly never-ending battle of putting things over other things. Modern CSS gives us modern ways to create stacking contexts, like isolation: isolate. So once you have that stacking context, what number do you put on it?
It seems that we are a creative bunch when it comes to thinking of numbers. Given the wide range of unique z-index values it’s probably time for a word from our sponsor, Polypane. If you can master the concept of stacking contexts, you will not need so many z-indexes anymore. Kilian knows his stuff and if you need help debugging your stacking contexts, just use Polypane!
Animation durations
Oh, I wish that I could look into the actual values used for animation-duration. It would be very interesting to see what durations are used most so we could spark a healthy debate on social media as to why everyone is wrong.
At least we know that most websites don’t use a lot of unique durations. Again, these are compared string-wise, so 200ms is different from 0.2s and .2s. Now fight.
Animation timing functions
Are timing functions debated as heavily as durations? At least there are fewer unique values per website, so perhaps this is less of an issue?
Vendor prefixed values
Sometimes even values have vendor prefixes, like selectors and properties do. Think of background-image: -webkit-linear-gradient() or display: -ms-flexbox.
Value browserhacks
Unfortunately I have not broken down usage of value hacks properly but based on the source data I can tell that two main types of value hacks are still commonly used in at least 5% of websites:
\9is used to target IE 6-8!ieis used to target IE <= 7 and acts as!importantwhere the string after!can be anything
Source: browserhacks.com
Value resets
Value resets are measured as declarations where any level of margin or padding is set to 0 with or without units. Resets are usually a code smell because you’re overriding a value previously set, and now it’s ‘in the way’, and you need to do double work. That’s a sign that something is wrong with your CSS composition or cascade setup.
Luckily there’s a small group of websites that seem to use the bare minimum, but looking further down the graph, we can see CSS resets quickly ramping up.
Units
Looking at how many unique CSS units are used is useful because you probably don’t want to mix too many sorts of units to stay consistent. But there are also good reasons to not use the same unit for every single property, so it’s tradeoffs all the way down. How many units do websites actually use?
This might be one of the least eventful graphs in the entire report. Not a lot of diversity of CSS units across the biggest percentiles. Things get more interesting when we look at adoption rates of specific units:
| Unit | Adoption % | Relative count |
|---|---|---|
| px | 98.28% | |
| em | 91.72% | |
| s | 91.50% | |
| deg | 89.30% | |
| rem | 80.15% | |
| vh | 76.04% | |
| vw | 72.34% | |
| fr | 60.23% | |
| ms | 56.70% | |
| turn | 35.64% | |
| ch | 23.06% | |
| pt | 15.50% | |
| dvh | 10.32% | |
| ex | 7.82% | |
| cm | 7.19% | |
| svh | 3.98% | |
| lh | 2.80% | |
| x | 2.75% | |
| dvw | 2.30% | |
| vmax | 1.69% | |
| vmin | 1.52% | |
| cqw | 1.48% | |
| pc | 1.29% | |
| mm | 1.18% | |
| in | 1.08% | |
| lvh | 0.95% | |
| svw | 0.55% | |
| cqi | 0.55% | |
| cqh | 0.32% | |
| cap | 0.15% | |
| vi | 0.15% | |
| lvw | 0.14% | |
| dppx | 0.13% | |
| m | 0.11% | |
| rad | 0.08% | |
| cqmin | 0.07% |
px remains the ruler with some distance over em and s although I’m surprised s is actually that high on the list. It’s even well above rem! Looking down the list we can see that deg is used more than turn (89.3% vs. 35.6%); s more than ms (91.5% vs. 56.7%); cm more than mm and in (7.19% vs. 1.18% vs. 1.08%). The list also shows that we’ve started using viewport and container units.
Conclusion
That was a wild ride along some of the most-used but also most obscure pieces of CSS usage around the world. The goal of this first edition of Project Wallace’s CSS Selection was to have a look at how CSS is being used in the real world, and I am pleased to say that this article has shown us some real eye-openers as well as opportunities for mad respect and deep regret.
What stands out most to me is the adoption rate of various newish features in CSS, like @container, :where and :has. On the other hand, adoption of great features, like @supports and @layer, seems to lag behind. Perhaps this is my bias towards my own authoring style, but I expected the balance to be more in favor of the old-but-good.
Looking at the bigger picture, I expected the overall state of global CSS usage to be a lot worse than it is. Perhaps that stems from the fact that people send me their worst websites to analyze, and that causes my bias to shift towards a negative outlook. On the other hand, there is still a lot of improvents that could be easily made, if people would just look at the CSS they send to their customers’ browsers. I am tooting my own horn here, obviously, but if you occasionally analyze your website using Project Wallace, you’ll always find a couple of spots that could be improved.
Analytical gaps
After spending dozens of hours analyzing and writing these chapters, I found that there are some flaws in my overall analysis that I plan to improve in future editions:
- Do deeper analysis on comparing string-based values:
redandRedshould be marked as the same values for colors, as should(position: sticky)and(position:sticky)for@supportsqueries. - There is no correlation analysis: does having lots of embedded content always mean having a bigger file size than usual? Do websites with a large
!importantratio also have different specificity metrics? This is worth exploring next time.
Next editions
- Because this is the first edition, I haven’t done comparisons to other years yet. This is ‘the big plan’ for future editions: to have a look at how CSS usage evolves as we drop legacy browser support and adopt more modern features.
- For the next edition I’ll use a more realistic scraper, probably based on a headless browser. Our current scraper fetches static HTML, parses it to get CSS resources and downloads each of them. A headless browser would yield more realistic results, because it is able to see the actual network requests, even the ones initiated by JavaScript.
- Continuing on the last point: if we’re going to use a headless browser, then we can also look at CSS coverage analysis.
- Multiple suggestions came in through the review process, some of which we might incorporate into upcoming editions: comparing usage of
gridandflex; analyzing@scopeand:scope; analyzing adoption ratios of pseudo-elements and attribute selectors; looking for adoption ratio of the PostCSS@layerpolyfill; Web component selectors: the list goes on and on!
Acknowledgements
This article would not have existed without the prior work of all those who contributed to the Web Almanac CSS chapter over the years. Your work is invaluable, and let’s hope we will get an updated CSS chapter this year.
Declan Chidlow (vale.rocks) helped out by meticulously reviewing this article and schooling me in how to write properly. Thank you for that. Go check out that blog, it’s awesome.
A tremendous word of thanks to Kilian from Polypane for sponsoring (and reviewing) this inaugural edition of The CSS Selection. Your support means the world to me personally, because it encourages me to write about CSS more and to make better tools, like you do yourself. Folks, seriously, if you are not using Polypane yet, you’re missing out.
Research method
This article used the following methodology:
- Use the Majestic Million list to get the top ~100,000 website domains to scrape, although in practice it turned out to be more than 200,000 websites because a lot of them errored or blocked the scraper.
- Run a CSS Scraper (v1.0.2) to get the CSS for the homepage of each of those domains. Only homepages were analyzed, no deeper URLs. All CSS is collected into a single string for analysis.
- Use @projectwallace/css-analyzer (v7.6.3) to analyze the CSS.
- Analysis is stored in a local SQLite database and SQL queries are used to gather unique values, medians, percentiles, min, max etc.
- No AI was used to write this content. If you think it’s slop, it’s simply because I’m a lousy writer. Some AI was used to generate the SQL queries to generate the data but they were all checked by my human eyes.
All conclusions and opinions are mine, a mere mortal with an above-average interest in looking at CSS in a different way than most people do. You may not agree, and that’s fine.
