Tailwind CSS Advanced: Systems, Plugins and Production
Accessibility and Performance
In this lesson you will
- Apply accessibility utilities and avoid common Tailwind accessibility mistakes
- Understand why Tailwind CSS stays small and how to keep it that way
- Review a page against an accessibility and performance checklist
Introduction
Utility classes make visual styling quick. They do not make an interface accessible or fast by themselves. This lesson collects what you need to check. The class behaviour described was verified by compiling with Tailwind 4.3.3.
Accessibility with utilities
Keep focus visible
Keyboard users need to see where they are. Do not remove outlines without a replacement.
outline-hiddenhides the default outline visually but keeps a visible outline in forced-colors (high-contrast) mode. I compiled it: it setsoutline-style: noneand, inside@media (forced-colors: active), a 2px transparent outline that high-contrast mode turns visible.- The plain
outline-nonesetsoutline-style: nonewith no such fallback, so avoid it for focusable elements. focus-visible:shows styles for keyboard focus only.
HTML
<a href="/docs"
class="rounded outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600">
Docs
</a>Screen-reader-only text
sr-only hides content visually but keeps it available to screen readers, for instance a label for an icon button. not-sr-only reverses it, which is how skip links appear on focus:
HTML
<a href="#main" class="sr-only focus:not-sr-only focus:absolute focus:left-3 focus:top-3 focus:bg-white focus:p-2">
Skip to content
</a>
<button type="button">
<span class="sr-only">Close menu</span>
<svg aria-hidden="true" class="size-5">...</svg>
</button>Quick quiz
Which is the better way to hide a focus outline visually while keeping accessibility in high-contrast mode?
Contrast and colour
- Check contrast for every text and background pair in every theme: at least 4.5 to 1 for normal text, 3 to 1 for large text and UI parts.
- Do not rely on colour alone. An error state needs an icon or text as well as red.
- Test with a browser's emulation of colour-blindness and high contrast.
Motion
Large or constant animation can cause discomfort. Respect the setting:
HTML
<div class="animate-spin motion-reduce:animate-none">...</div>
<div class="transition hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">...</div>I compiled motion-reduce:transition-none and it sits inside @media (prefers-reduced-motion: reduce).
Targets and structure
- Make touch targets large enough, around 44 by 44 CSS pixels, with
min-h-11 min-w-11or enough padding. - Use the right HTML elements:
buttonfor actions,afor navigation, headings in order, labels linked to inputs. Utilities style elements, but semantics come from the markup. - Use
aria-*attributes where needed, and thearia-expanded:variant to style them, so visual state always matches the accessible state. - Do not hide content with
hiddenoropacity-0and expect screen readers to behave the same way.hiddenremoves content from the accessibility tree, andopacity-0does not.
Performance: why Tailwind CSS stays small
Tailwind generates CSS only for classes it finds. A large site with thousands of elements often ships a modest stylesheet because the same utilities repeat. Other effects:
- Theme variables are emitted only when used. I defined an unused variable and the output omitted it. (
@theme staticforces emission.) - Utilities are reusable, so CSS grows slowly as the site grows, unlike hand-written CSS where every new component adds rules.
- Modern CSS output with cascade layers and CSS variables needs no extra prefixing step.
What can make it bigger
- Safelisting everything with
@source inline()ranges for classes you rarely use. - Scanning huge folders, such as build output or large data files, with
@source. Exclude them with@source not. - Many arbitrary values, each of which adds a separate rule.
- Whole custom palettes with every shade used. Only used ones are emitted, so this is usually fine.
Production habits
- Minify and compress the CSS (your build tool usually does this) and serve it with gzip or Brotli.
- Cache the file with a long lifetime and a hashed filename.
- Load fonts carefully: limit the number of font files, use
font-display: swap, and preload important ones. - Avoid layout shift: give images width and height, and reserve space for dynamic content with
aspect-videoor fixed sizes. - Measure with the browser's performance tools and Lighthouse instead of guessing.
- Check unused CSS only if something looks wrong. Tailwind already trims it.
Audit checklist
Accessibility:
- Every interactive element can be reached and used with the keyboard.
- Focus is always visible.
- Contrast passes in each theme.
- Icons have text alternatives or
aria-hidden. - Animation respects reduced motion.
- Zoom to 200 percent and check that nothing overlaps.
Performance:
- CSS is minified, compressed and cached.
- No giant safelists or scanned build folders.
- Fonts and images are optimised.
- Core Web Vitals are checked on a real mid-range phone.
Try it yourself
Take a page you built. Tab through it and fix any element whose focus is hard to see. Run a contrast checker on each colour pair, add motion-reduce: to one animation, and compare the compressed size of your CSS before and after removing an unneeded safelist.
Summary
- Keep focus visible (
outline-hiddenwithfocus-visible:styles), usesr-onlytext, check contrast in every theme and respect reduced motion. - Tailwind's CSS stays small because it only emits used classes and used theme variables, so avoid broad safelists and scanning large folders.
- Minify, compress and cache the CSS, optimise fonts and images, and measure on real devices.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading