Skip to content
Release notesStyling & UI

Tailwind CSS 4.1: text shadows, masks and safer utilities

Tailwind CSS 4.1.0 reached npm on 1 April and the announcement followed on 3 April. Nothing breaks. Several things you used to hand-write in CSS become utilities, and older browsers get fallbacks.

vuejs.school team3 Apr 20257 min readTailwind CSS 4.1

Tailwind CSS 4.1.0 was published to npm on 1 April 2025, and the team's announcement, written by Adam Wathan and Dan Hollick, followed on 3 April. It is a minor release on top of 4.0 from January. Text shadows, masks and other effects that used to need a custom @utility or a <style> block become utilities, and a set of fallbacks keeps a v4 stylesheet usable in browsers older than the ones v4 was built for.

Most projects have nothing to migrate: there are no configuration changes, and the position utilities it renames still accept their old names. If your Vue app has visitors on older iPhones, iPads or Firefox releases, the fallbacks alone are a reason to upgrade now.

What shipped in 4.1

The announcement lists these additions:

  • Text shadows. Five sizes from text-shadow-2xs to text-shadow-lg, a colour with text-shadow-sky-300, and an opacity modifier, so text-shadow-lg/50 is a large shadow at half opacity.
  • Masks, which hide parts of an element using the transparency of a gradient or an image. Linear masks per side, such as mask-t-from-50% or mask-l-from-50% mask-l-to-90%, radial masks with mask-radial-from-15% and mask-radial-at-right, conic masks, and all of them composable on one element.
  • Better rendering in older browsers, with fallbacks for colours, shadows, gradients and opacity modifiers (explained below).
  • Fine-grained text wrapping with wrap-break-word and wrap-anywhere, which map to overflow-wrap.
  • Coloured drop shadows: drop-shadow-xl drop-shadow-cyan-500/50 on an SVG.
  • Input device variants: pointer-fine for mice and trackpads, pointer-coarse for touchscreens, and any-pointer-* to match when any connected device qualifies.
  • Baseline alignment to the last line of text with items-baseline-last and self-baseline-last.
  • Safe alignment utilities such as justify-center-safe, which switch to start alignment when the content overflows.
  • Source control in CSS: @source not to exclude a folder from class scanning, and @source inline() to force classes into the build, replacing the old safelist option.
  • New variants: details-content:, inverted-colors:, noscript:, and user-valid: with user-invalid:.

The GitHub release notes add a few more: opacity modifiers on shadow-* and inset-shadow-*, node_modules ignored by default when scanning for classes, and the two-word position utilities renamed, so bg-left-top is deprecated in favour of bg-top-left and object-left-top in favour of object-top-left. The old names still work.

Use three new utilities in a Vue component

This course card uses a text shadow, a mask and a pointer variant, all new in 4.1. The rest of its classes are ordinary Tailwind.

components/CourseCard.vue
<script setup lang="ts">
defineProps<{
  title: string
  tags: string[]
}>()
</script>

<template>
  <article class="rounded-2xl bg-slate-900 p-6 text-white">
    <h2 class="text-2xl font-semibold text-shadow-md text-shadow-black/40">
      {{ title }}
    </h2>

    <ul class="mt-4 flex gap-2 overflow-x-auto mask-r-from-80%">
      <li
        v-for="tag in tags"
        :key="tag"
        class="shrink-0 rounded-full bg-slate-700 px-3 py-1 text-sm"
      >
        {{ tag }}
      </li>
    </ul>

    <button
      type="button"
      class="mt-6 rounded-lg bg-sky-500 px-4 py-2 pointer-coarse:px-6 pointer-coarse:py-3"
    >
      Start lesson
    </button>
  </article>
</template>

text-shadow-md text-shadow-black/40 lifts the heading off the dark background with a medium shadow in black at 40% opacity. Before 4.1 this needed a text-shadow declaration in a style block or an @utility of your own.

mask-r-from-80% fades the right edge of the tag list to transparent, starting 80% of the way across. Because the list scrolls sideways, the fade hints that more tags sit past the edge. The class does the job of mask-image: linear-gradient(to right, black 80%, transparent) in plain CSS.

pointer-coarse:px-6 pointer-coarse:py-3 makes the button bigger on touchscreens only. A finger is less precise than a mouse, and this variant gives fingers a larger target without making every desktop button oversized. It compiles to @media (pointer: coarse), which checks only the primary pointing device, so a touchscreen laptop with a mouse usually counts as fine. Use any-pointer-coarse: to match whenever any connected device is coarse.

Show form errors only after the user interacts

Style a required field with invalid:border-red-500 and it turns red as soon as the page loads, before anyone has typed in it. The new user-invalid: and user-valid: variants match only after the user has interacted with the control. This email field uses them for its border (defineModel() lets a parent bind it with v-model):

components/EmailField.vue
<script setup lang="ts">
const email = defineModel<string>({ default: '' })
</script>

<template>
  <label class="block">
    <span class="text-sm">Email</span>
    <input
      v-model="email"
      type="email"
      required
      class="mt-1 block w-full rounded-md border border-slate-300 px-3 py-2 user-invalid:border-red-500 user-valid:border-green-500"
    >
  </label>
</template>

MDN's compatibility data lists :user-invalid and :user-valid from Chrome 119, Safari 16.5 and Firefox 88. In an older browser the rule is skipped and the border stays border-slate-300, so the field still works without the colour hint.

What the fallbacks fix in older browsers

Tailwind v4 builds on three newer CSS features: @property, which registers typed custom properties and drives shadows, transforms and gradients; color-mix(), which produces the opacity modifiers; and oklch colours for the default palette. In browsers without them, 4.0 degraded badly. The announcement says even colours and shadows might not render at all on an old iPhone or iPad stuck on Safari 15. It shows the same page in Safari 15.5 on 4.0 and 4.1, and on 4.0 several background gradients are missing.

The announcement lists four fallbacks in 4.1:

  • Colours defined in oklab now render in older Safari.
  • Features built on @property, such as shadows, transforms and gradients, now work in older Safari and Firefox.
  • Colours with an opacity modifier get an inlined fallback.
  • Gradients with an explicit interpolation method fall back to the browser's default.

Tailwind writes these fallbacks into the CSS it generates, so they arrive with the upgrade and need no change to your Vite config. The announcement's promise is modest: pages render and stay usable in older browsers, though in some cases a shadow colour may differ.

The release matters most if your analytics show visitors on an older iPad, or on a locked-down Firefox ESR older than 128. Check that share before you decide how much to test. The compatibility page also lists bleeding-edge features with limited support, such as @starting-style and field-sizing: content: avoid those utilities where older browsers matter.

Keep overflowing content reachable

The "safer utilities" in the title are two sets that stop content wider than its box from getting lost.

justify-center-safe fixes the centred flex row that overflows, and there is a -safe version for every alignment property. With justify-center, content wider than its container overflows on both sides, and the left part cannot be scrolled into view. With justify-center-safe the browser switches to start alignment as soon as the content overflows, so it overflows only to the right and stays reachable. A toolbar of chips on a narrow phone is the usual victim.

wrap-break-word breaks a long word or URL that would otherwise push past its box. Inside a flex container it may not, and the announcement suggests wrap-anywhere there. The difference is that wrap-anywhere also allows those breaks when the browser works out the item's intrinsic (content-based) size, so the item can shrink without the min-width: 0 that flex children usually need.

Choose which files Tailwind scans

Two new forms of the @source directive go in your CSS entry file, after the import. Their paths are relative to that file:

src/assets/main.css
@import "tailwindcss";
@source not "../legacy";
@source inline("{hover:,}bg-red-{50,{100..900..100},950}");

@source not keeps a folder out of class detection. Use it for an old part of the project that still ships its own stylesheet, or for a large folder that only slows the scan down.

@source inline() generates classes that never appear in your source, for example colours chosen at runtime from data. It supports brace expansion, so the line above produces bg-red-50 through bg-red-950 plus their hover: variants. It replaces the safelist array from the v3 JavaScript config. There is also @source not inline("container") to exclude a class even if it is detected.

Upgrade and check your oldest browser

In a Vue project that uses the Vite plugin, update both packages:

npm install tailwindcss@latest @tailwindcss/vite@latest

If you set Tailwind up through PostCSS, install @tailwindcss/postcss@latest instead of @tailwindcss/vite@latest. Your CSS entry still starts with @import "tailwindcss", and your @theme block stays as it is. Restart npm run dev after the install.

Then check two things:

  1. Open the pages that use gradients and shadows in the oldest browser you support, and confirm they render.
  2. If a component library in node_modules got its classes from Tailwind's automatic scan, add a line for it to src/assets/main.css, such as @source "../../node_modules/that-library";. Tailwind 4.0 scanned node_modules unless your .gitignore listed it; 4.1 skips it by default, and an explicit @source overrides that.
Check it stuck3 questions from the Tailwind CSS with Vue quiz.
Practice now
Was this helpful?

Keep reading