The CSS Selection
The state of real-world CSS usage, 2026 edition.
Welcome to The CSS Selection 2026! In this article we’re having a look at how CSS is used at scale in over 100,000 websites. We’ll look at what things are common in most websites and discover interesting outliers.
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: a shortage of authors, editors but mostly analysts who could mangle BigQuery stuff to get hold of large amounts of CSS data to analyze and put them 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. So, that’s what we’re looking at now.
CSS has taken a flight in recent years witch many new features, properties, values, atrules and so much more. With power shifting to CSS it’s interesting to look at the use of these new features as well as keeping 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 global metrics followed by atrule analysis, then rules, selectors, declarations and lastly, values.
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.
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:
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 regulary-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 atrules/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 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 browserhacks, 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 contributer to file size is embedded content. A good portion of websites embed various types on 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 percentile don’t embed any content at all. Only from the 50th perentile onwards we see a little bit of embedding, with the chart steeply rising from the 90th percentile.
Comments
Another contributer 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.
Atrules
Totals
Let’s start by looking at how many atrules we’re shipping. Not looking at which ones specifically, just count how many there are in total.
Looks like most websites have that atrules well under control. With modern CSS there’s a lot to control with atrules, like containers, media, keyframes, supports. You need a bunch of them to get a properly functioning website!
@media
Adoption rate: 93.06% Baseline since: July 2015
Our versatile friend the media query. Always there to help with that responsive design, making that page print-ready or adjust 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 atrule totals I think it’s safe to say that the largest amount of atrules 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 surpises here, like forced-colors being used a bunch more than prefers-color-scheme.
@font-face
Adoption rate: 85.61% Baseline since: July 2015
It’s hard to imagine these days that we had to embed images or use Flash to make our typograhpy 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 like this. I had actually expected the numbers to be a bit higher. Maybe some of you are experiencing some faux-bolds? 😉
@keyframes
Adoption rate: 83.90% Baseline since: September 2015
Continuing the making-our-website-pretty theme with a look at @keyframes. 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 go spin round. fa-spin is a clear sign of FontAwesome being present. The turn-off-visibilty seems to come from the WordPress Gutenber editor as well as the two lightbox-zoom-* keyframes.
@supports
Adoption rate: 44.57% Baseline since: September 2015
This atrule 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 now also can 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.
@import
Adoption rate: 18.04% Baseline since: July 2015
If our previous atrule was used so little, let’s hope this one is too! Using @import usually is an anti-pattern unless you know what you’re doing.
Wow, job well done internet! At least 75% of websites don’t use @import at all and even the top 90% 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 atrule is a pretty versatile beast and I wonder if we’re takin full advantage of it when we use it.
@layer
Adoption rate: 2.71% Baseline since: March 2022
@layer has been baseline supported since March 2022. It’s use seems mostly powered by the use of TailwindCSS but also the use of layers like legacy and global seem encouring patterns of developers using @layer. Especially @legacy seems like a trick that developers are using to incrementally modernize their codebase.
| @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% |
@charset
Adoption rate: 39.57% Baseline since: July 2015
Almost 40% of websites are using @charset and an overwhelming majority uses UTF-8 encoding. But what’s interesting is that there’s a pretty clear group using Chinese (gb2312), Japanese (shift_jis) and cyrillic (windows-1251) encodings. This proves that there’s 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
Adoption rate: 9.61% Baseline since: February 2023
Almost 10% of websites using @container already (well, it has been Baseline since February 2023) is a pretty good number. Looking at the names that are used there’s 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
Adoption rate 2.67% Baseline since: July 2024
@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 is already encouraging.
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 atrules, like the Google Font API does. They are the corner stone 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
Max: 210695
Selectors per rule
(Most common = 1) award for max: 128528
Declarations per rule
(Most common = 1) Award for max: 41620
Total rule size
When you add up selectors and declarations you get the rule size.
most common: 2 award for max: 128529
Rule nesting depth
most common: 1 award for max nesting: 37
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.
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 less which is also surprising. This seems like a really low number to me.
Pseudo classes
One concept that I blatantly stole from the Web Almanac is their overview of popular pseudo classes. It offers an interesting look into adopoption 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.00% | |
| 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 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% percent of websites (Baseline December 2023). That’s even higher than :is clocking in at 36% (also baseline 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 new-ish 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
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’s 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 analysis wrong for specificity 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 with new selectors like :has() and :where().
Now let’s looks 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.00% | |
| 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 to this chart as well, 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.
TODO: insert link to source data
Selector complexity
Most common selector complexity per website:
Most selectors on most sites are simple selectors: only 1 or 2 parts to them. But what about the most complex selectors on any site?
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
!important usage
Max is 249021
Custom properties
Property browserhacks
Vendor prefixed properties
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 is aggregates 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. This is what is counted when we look at the metric like that:
It would be very interesting to compare colors value-wise so it’s 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 is very much as expected bt 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 are baseline widely available since January 2020…
Further down the list we still 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 the use of @font-face with the amount of families used always being slightly higher than the amount 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 their 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 so it is no surprise that this graph is similar in shape to the one of them 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 contibuting 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
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 previxed values
Value resets
Credits to Ana.
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% |
Research method
This article used the following methodology:
- Use the Majestic Million list to get the top ~100,000 website domains to scrape
- Run a CSS Scraper (v1.0.2) to get the CSS for the homepage of each of those domains
- 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.
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.