CSS performance: size, selectors and rendering cost
CSS blocks the first paint and also drives style, layout and paint work. How its size, loading, selectors and animations affect speed, and what to fix first.
In this guide
Four ways CSS costs time
1. It blocks rendering. MDN states that CSS is treated as a render-blocking resource by default. The browser will not draw any content until the CSS Object Model is built, because a later rule can override an earlier one. That makes the CSS in the head the file that decides when the first paint can happen. See critical CSS and render-blocking files.
2. It adds bytes. In the 2025 Web Almanac the median mobile home page loaded 77 KB of CSS in 8 files, and at the 90th percentile pages made around 30 CSS requests. The weight is small next to images and JavaScript, but it all sits on the critical path.
3. It has to be matched. Whenever the page changes, the browser works out which rules apply to which elements. web.dev explains that this is roughly half matching selectors and half building the resulting style, and that the worst-case cost grows with the number of elements multiplied by the number of rules.
4. It decides how much work a change causes. MDN groups CSS properties by what they trigger. Those that change geometry or position, such as left, max-width, border-width, margin-left and font-size, cause recalculation, layout and repaint. Those that only change appearance, such as color, cause recalculation and repaint. A few, transform and opacity among them, can be handled in their own layer and cost only the recalculation.
Fix 1: send less CSS
Start where the saving is largest.
- Remove unused rules. Themes, frameworks and plugins often ship far more CSS than a page uses. Lighthouse's Reduce unused CSS diagnostic reports style sheets with more than 10 KB of unused rules, and the Coverage tool in Chrome DevTools shows which lines are never applied. Unused code shows the steps, and warns to test every state of the page before deleting anything.
- Minify. Removing whitespace and comments reduces size and risks nothing. Lighthouse flags it as Minify CSS. The 2025 Web Almanac found that only about 61% to 62% of pages passed that check, so roughly four in ten did not.
- Compress. CSS compresses very well. See text compression.
- Stop the pile-up. Each plugin or widget on a CMS may add its own style sheet to every page. Check the list of CSS files in the Network panel, and ask for each one whether this page needs it.
Fix 2: load it well
- Link the files in the head. A stylesheet that is discovered late delays the first paint.
- Avoid `@import` inside CSS. An imported file is found only after the file that imports it has downloaded, so the files load one after another instead of together. Lighthouse's Network dependency tree insight draws such chains. Link the files in the HTML instead, or combine them when you build the site.
- Use media queries to unblock rendering. MDN advises using media types and queries so that style sheets that do not apply do not block. A file with
media="print"is not needed to draw the screen, and a file withmedia="(orientation: portrait)"only matters in that orientation.
<link rel="stylesheet" href="/css/site.css">
<link rel="stylesheet" href="/css/print.css" media="print">
<link rel="stylesheet" href="/css/wide.css" media="(min-width: 1200px)">- Put only what the first screen needs in front of the visitor first. Inlining the critical rules and loading the rest after is covered in the critical CSS guide, with its trade-offs.
- Split by audience. If the checkout and the blog have different styles, load each on its own pages. With HTTP/2 and HTTP/3 several small files are not a problem. See HTTP/2 and HTTP/3.
Fix 3: keep matching and invalidation small
- Prefer simple, class-based selectors. web.dev suggests a class-centered approach such as BEM, where each element has one class that describes it, instead of selectors that depend on the position of an element in the tree. A selector such as
.box:nth-last-child(-n+1) .titlemakes the browser check the siblings and the parents, and a class such as.final-box-titlemakes it a direct match. - Reduce what a change invalidates. The bigger factor is how many elements a change touches. Changing a class on a small container affects fewer elements than changing one on the body. Modern browsers track which elements are really affected, so this is less severe than it once was, but the principle holds: smaller trees with simpler rules are cheaper to process.
- Do not overdo it. MDN's critical rendering path guide notes that selector tuning saves only microseconds and is rarely the best first step. Measure before spending time on it.
Fix 4: use cheap properties, and let the browser skip work
- Animate `transform` and `opacity`. Moving an element with
transformis much cheaper than changingtoporwidth, which trigger layout in every frame. Lighthouse's Avoid non-composited animations audit lists animations that cannot use the cheap path. - Skip what is off screen.
content-visibility: autolets the browser skip layout and painting of sections until they are near the screen. See DOM size and rendering. - Do not force layout from script. Changing a style and then reading a measurement in the same task makes the browser lay the page out at once. Read first, then write.
Fix 5: keep it small as the site grows
CSS grows by accumulation. Nobody deletes a rule when a feature is removed.
- Name styles by component, and remove a component's styles when you remove the component.
- Set a budget for CSS bytes, with a warning level and an error level, and check it when the site is built. MDN describes this approach to performance budgets. See page weight and requests.
- Run the unused CSS check after each redesign or plugin change.
How to measure it
| What to check | Where |
|---|---|
| Is CSS blocking the first paint? | The Render-blocking requests insight, and the Network panel |
| How much CSS is unused? | The Reduce unused CSS diagnostic, and the Coverage tool |
| Is it minified and compressed? | Minify CSS, and the content-encoding header |
| Are there chains? | The Network dependency tree insight |
| How costly are style updates? | In the Performance panel, the Recalculate Style events and the number of elements they affect |
Check your own page
Run the free audit. Look first at the render-blocking list and the unused CSS figure. If one stylesheet is large and mostly unused, remove or split it before you touch anything else. The unused code guide and the critical CSS guide take it from there.
Sources
- Lazy loading and Critical rendering path on MDN: CSS as a render-blocking resource, media queries, and selector performance.
- Animation performance and frame rate on MDN: which properties trigger layout and paint.
- Reduce the scope and complexity of style calculations on web.dev.
- Page weight chapter, Web Almanac 2025: CSS bytes, requests and minification.
- Performance budgets on MDN.
Start here
Free website audit
Live Google data for any public page: mobile and desktop.
Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it
Rather have this done for you? Speed optimization · Book a call