Best Practices & Performance

CSS · Lesson 54 of 55 9 min read Updated

Your CSS can look perfect on your own laptop and still let real visitors down. On a budget phone with a slow mobile connection, a heavy stylesheet delays the whole page. A missing focus style locks out keyboard users. A tiny grey font frustrates someone with weak eyesight.

This lesson collects the habits that separate working CSS from good CSS: loading styles efficiently, keeping the layout stable, animating cheaply, building in accessibility and checking everything before you publish.

Load CSS the right way

CSS is render-blocking: the browser does not draw the page until it has downloaded the stylesheets linked in the <head>. That is deliberate, because otherwise visitors would see a flash of unstyled content. But it means every extra kilobyte and every extra file delays the first view of the page.

HTML
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Rohtak Book Store</title>
  <!-- One main stylesheet, early in the head -->
  <link rel="stylesheet" href="/css/site.css">
  <!-- Print styles do not block the screen version -->
  <link rel="stylesheet" href="/css/print.css" media="print">
</head>

A few rules follow from this:

  • Link stylesheets in the <head> with <link rel="stylesheet">, not at the bottom of the page.
  • Avoid @import inside CSS files for loading other files. The browser only discovers an imported file after the first file has arrived, so the downloads happen one after another instead of in parallel. Combine files with a build tool instead.
  • Use the media attribute for styles that only apply sometimes. A stylesheet with media="print" is still downloaded, but it does not hold up the page on screen.
  • Minify and compress. Minifying removes spaces and comments; the server's compression (gzip or Brotli) shrinks the file further. Both are usually switched on in your build tool or hosting settings.
  • Remove unused CSS. Old rules for deleted pages still cost download and processing time.

For more loading hints such as preload, see Loading & Performance Hints.

Prevent layout shift

Layout shift is when content jumps around while the page loads: you are about to tap a link, an image above it finishes loading, and you tap an advert instead. Google measures it as Cumulative Layout Shift (CLS), one of the Core Web Vitals.

The fix is to reserve space for everything before it loads. For images, write the real width and height attributes in the HTML. The browser uses them to calculate the aspect ratio and keep the right space free, even when CSS makes the image responsive. For boxes without an image file, such as video embeds or advert slots, use aspect-ratio.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Reserve space</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 16px; max-width: 520px; }
    img {
      max-width: 100%;
      height: auto;          /* keeps the ratio from width and height */
      display: block;
      border-radius: 8px;
    }
    .thumb {
      width: 100%;
      aspect-ratio: 1;       /* a square, whatever the file's shape */
      object-fit: cover;     /* crop to fill without stretching */
    }
    .video-slot {
      aspect-ratio: 16 / 9;  /* space for an embed that loads later */
      background: #e2e8f0;
      display: grid;
      place-items: center;
      border-radius: 8px;
    }
    .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-block: 12px; }
  </style>
</head>
<body>
  <img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" width="800" height="500" alt="Illustration of green hills and a river at sunrise">
  <div class="grid">
    <img class="thumb" src="https://course.ethicscomputer.in/assets/samples/landscape-400.svg" width="400" height="250" alt="Hills and a river at sunrise, cropped to a square">
    <img class="thumb" src="https://course.ethicscomputer.in/assets/samples/portrait.svg" width="400" height="600" alt="Illustration of a tall brick clock tower under a blue sky">
  </div>
  <div class="video-slot">Video loads here</div>
</body>
</html>

Each element has its final size before any image arrives, so nothing below it moves. object-fit: cover crops the tall clock tower picture into a square instead of squashing it. You can read more about these properties in Sizing, Aspect Ratio & Overflow.

Fonts without long waits

Web fonts are often the largest files a stylesheet asks for. While a font downloads, some browsers hide the text completely. font-display: swap shows the text straight away in a fallback font and swaps in the web font when it arrives:

CSS
@font-face {
  font-family: "Mukta";
  src: url("/fonts/mukta-regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

Load only the weights you use (each one is a separate file), use the compact woff2 format, and consider the system font stack (font-family: system-ui, sans-serif) for body text, which needs no download at all. Fonts & Web Fonts covers this in detail.

Animate cheaply

To draw a page, the browser works out the layout (where every box goes), then paints the pixels, then composites the layers together. Animating width, height, top, left or margin repeats the layout step on every frame. Animating transform (translate, scale, rotate) and opacity usually only repeats compositing, which is far cheaper.

  • Prefer transforms and opacity for motion, as in Transitions and Keyframe Animations.
  • Do not sprinkle will-change everywhere. It asks the browser to prepare extra layers, which costs memory. Use it only on an element you know will animate, and only if you see a problem.
  • Keep large blur() and backdrop-filter effects small; they are expensive to paint.

Render less with content-visibility

On a long page, such as a blog post with dozens of comments, the browser normally lays out and paints everything, even sections far below the screen. content-visibility: auto lets it skip that work for sections that are off screen until the reader scrolls near them:

CSS
.comment-section,
.footer-links {
  content-visibility: auto;
  contain-intrinsic-size: auto 600px; /* estimated height until rendered */
}

contain-intrinsic-size gives the skipped section an estimated height, so the scrollbar does not jump. The auto keyword tells the browser to remember the real height once it has rendered the section. This works in all current major browsers, and older ones simply render everything as before, so it is a safe improvement for long pages.

Accessibility is part of quality

Good CSS works for everyone. Check these on every project:

  • Text contrast of at least 4.5:1 for normal text and 3:1 for large text against the background.
  • Visible focus for every link, button and field, using :focus-visible. Never remove outlines without a replacement.
  • Text sizes in rem, so the page respects the user's browser font setting.
  • Zoom: the page must still work when zoomed to 200%, without text overlapping or being cut off.
  • Not colour alone: an error needs a message or an icon, not just a red border.
  • Reduced motion: respect prefers-reduced-motion for large movement.
  • Hide content correctly: display: none hides from everyone, including screen readers. To hide something visually but keep it for screen readers, use a "visually hidden" class.
HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Accessible icon button</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 24px; font-size: 1rem; }
    .visually-hidden {
      position: absolute;
      width: 1px;
      height: 1px;
      margin: -1px;
      padding: 0;
      border: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }
    .icon-btn {
      display: inline-grid;
      place-items: center;
      width: 44px;
      height: 44px;
      border: 0;
      border-radius: 50%;
      background: #1e3a8a;
      color: white;
      font-size: 1.25rem;
      cursor: pointer;
    }
    .icon-btn:focus-visible {
      outline: 3px solid #f59e0b;
      outline-offset: 3px;
    }
    .muted-bad  { color: #b8b8b8; } /* too light: fails contrast */
    .muted-good { color: #595959; } /* passes on white */
  </style>
</head>
<body>
  <button class="icon-btn" type="button">
    <span aria-hidden="true">&#9825;</span>
    <span class="visually-hidden">Add Chai Corner to favourites</span>
  </button>
  <p class="muted-bad">Hard to read: very light grey text.</p>
  <p class="muted-good">Easy to read: darker grey text.</p>
</body>
</html>

The heart symbol is hidden from screen readers with aria-hidden, and the visually hidden text gives the button a real name. Press Tab to see the focus ring. The two grey paragraphs show why contrast matters: the first one fails the 4.5:1 guideline on a white background, while the second passes.

Write CSS people can maintain

  • Use a consistent format: one declaration per line, the same indentation, a blank line between rules.
  • Write comments that explain why, not what: /* matches the header height, so links do not hide under it */.
  • Replace magic numbers with tokens and custom properties.
  • Prefer logical properties such as margin-inline and padding-block (see Logical Properties), which also work for right-to-left languages such as Urdu.
  • Follow the structure from Organising CSS.

Check before you ship

Before publishing, run through this list:

  1. Test on a real phone, not only in a resized desktop window.
  2. Use only the keyboard: can you reach and see every control?
  3. Switch your system to dark mode and turn on reduced motion.
  4. Zoom the page to 200%.
  5. Run Lighthouse in Chrome or Edge DevTools for performance and accessibility hints.
  6. Open the Coverage panel in Chrome or Edge DevTools to find CSS that is never used.
  7. Check your CSS for typing mistakes with the W3C CSS Validator.

Common mistakes

  • Many separate CSS files linked or imported one after another. Combine them for production.
  • Images without width and height. The page jumps as they load.
  • outline: none on focus. Keyboard users get lost. Style :focus-visible instead.
  • Animating layout properties. Use transforms and opacity.
  • Light grey text on white. It looks elegant on a designer's bright screen and is unreadable in sunlight on a phone.

Key points

  • CSS blocks rendering, so keep it small, link it in the <head> and avoid @import chains.
  • Reserve space with width and height attributes and aspect-ratio to prevent layout shift.
  • Use font-display: swap, few font files and woff2; consider system fonts.
  • Animate transform and opacity; use will-change only when needed.
  • content-visibility: auto with contain-intrinsic-size skips work for off-screen sections.
  • Check contrast, focus styles, zoom, reduced motion and real devices before you publish.

Exercise

Take a product card that contains an image without size attributes, light grey price text (#c0c0c0), a heart button with no text, and the rule button:focus { outline: none; }. Fix all four problems: reserve the image's space, give the price readable contrast, give the button an accessible name without showing extra text, and replace the removed outline with a clear :focus-visible style.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Fixed product card</title>
  <style>
    .card img { width: 100%; height: auto; display: block; }
    .price { color: #4b5563; }
    .fav:focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px; }
    .visually-hidden {
      position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
      overflow: hidden; clip-path: inset(50%); white-space: nowrap;
    }
  </style>
</head>
<body>
  <article class="card">
    <img src="https://course.ethicscomputer.in/assets/samples/logo.svg" width="240" height="80" alt="Chai Corner logo with a tea cup">
    <h2>Chai Corner gift box</h2>
    <p class="price">₹1,299</p>
    <button class="fav" type="button"><span aria-hidden="true">&#9825;</span><span class="visually-hidden">Save to wishlist</span></button>
  </article>
</body>
</html>

The width and height attributes reserve the image's space, the darker grey passes contrast on white, the visually hidden span names the button for screen readers, and keyboard users get a clear outline again.

Quick Quiz

1. Why should you avoid loading stylesheets with @import inside another CSS file?

2. What prevents an image from making the page jump when it finishes loading?

3. Which properties are cheapest to animate?

4. How do you hide text visually but keep it available to screen readers?

Learn CSS with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call