For twenty years I have been reaching for style="text-shadow: 0 1px 2px rgba(0,0,0,.4)" whenever a hero heading needed to stay legible over a photograph. Tailwind had drop-shadow-* and shadow-*, but neither put a shadow on the actual glyphs — and every project ended up with one custom CSS file just for that one heading. Tailwind v4.1 finally shipped text-shadow-* utilities, and most blog posts still pretend the feature does not exist.
This is the cheat sheet: the five sizes, the colour tinting modifier, how it differs from drop-shadow and shadow, and how to wire your own brand-specific shadow into @theme. If you have not migrated yet, the Tailwind v3 to v4 Laravel upgrade walkthrough handles the codemod first — everything below assumes you are on v4.1 or later.
The five text-shadow sizes Tailwind ships#
The default theme ships five sizes that step up from a hairline shadow to a soft drop:
<p class="text-shadow-2xs">The quick brown fox jumps over the lazy dog.</p>
<p class="text-shadow-xs">The quick brown fox jumps over the lazy dog.</p>
<p class="text-shadow-sm">The quick brown fox jumps over the lazy dog.</p>
<p class="text-shadow-md">The quick brown fox jumps over the lazy dog.</p>
<p class="text-shadow-lg">The quick brown fox jumps over the lazy dog.</p>
Each one resolves to a CSS variable so you can read the defaults in the generated stylesheet. text-shadow-2xs is a single 1px offset at 15% opacity — that is the embossed-button look. text-shadow-lg layers three offsets and produces something close to a soft drop shadow on the text itself. There is no xl or 2xl by default — if you need bigger, you customise the theme (covered below).
The real use case is the one Adam Wathan called out in the v4.1 release: legible headings on busy background images.
<section class="relative bg-[url('/img/hero.jpg')] bg-cover bg-center p-24">
<h1 class="text-5xl font-bold text-white text-shadow-lg text-shadow-black/50">
Ship faster. Sleep better.
</h1>
<p class="mt-4 text-lg text-white/90 text-shadow-md text-shadow-black/40">
Production-grade Laravel from day one.
</p>
</section>
That used to be three classes plus an inline style="" to override Tailwind's text-shadow: none default. Now it is two utility classes, and the shadow colour is part of the utility set too.
Colouring the shadow without writing custom CSS#
The colour API mirrors the rest of v4: pick a palette colour, optionally append an opacity. Both work on text shadows.
<!-- a punchy black shadow at 50% opacity -->
<h1 class="text-shadow-lg text-shadow-black/50">Hero copy</h1>
<!-- a coloured shadow that picks up the page accent -->
<h2 class="text-shadow-md text-shadow-sky-500/40">Section title</h2>
<!-- adjust the opacity on the size itself — shorthand for size + black/30 -->
<p class="text-shadow-lg/30">Subtle drop</p>
The text-shadow-{size}/{opacity} shorthand is the one that catches people out. text-shadow-lg/30 is identical to text-shadow-lg text-shadow-black/30 — Tailwind treats it as a sugar over the explicit colour. If you want anything other than black, you have to spell it out: text-shadow-lg text-shadow-cyan-500/40.
Pair this with the OKLCH-driven palette in v4 and you can derive shadow colours from your brand tokens without precomputing anything — I covered that in Tailwind v4 OKLCH and color-mix design tokens.
text-shadow vs drop-shadow vs shadow — pick the right one#
This is where I see teams reach for the wrong utility most often. They all do different things:
<!-- text-shadow: shadow drawn on the glyphs themselves -->
<h1 class="text-shadow-lg text-shadow-black/40">Glyph-level shadow</h1>
<!-- drop-shadow: SVG filter applied to the bounding box, follows alpha edges -->
<h1 class="drop-shadow-lg drop-shadow-black/40">Filter-based shadow</h1>
<!-- shadow: box-shadow on the element's rectangular box -->
<div class="shadow-lg">Box shadow on a card</div>
text-shadow-* is the only one of the three that actually paints around the letter shapes. drop-shadow-* is an SVG filter — it will follow the alpha channel of whatever is inside the element, which means it works on text but also on transparent PNGs, irregular SVGs, and anything with cut-outs. shadow-* ignores text entirely and only ever shadows the element's bounding rectangle.
For headings on photography, text-shadow-* is the one you want. For floating an SVG icon off the page, drop-shadow-*. For card edges, shadow-*. The v4.1 release notes also added colour support to drop-shadow — sibling improvements in the same family worth knowing about.
Customising shadows in @theme#
The whole utility set is just CSS variables under the hood, so you can add your own sizes, override the defaults, or define branded shadows in @theme:
@import "tailwindcss";
@theme {
/* a fourth, larger size */
--text-shadow-xl: 0 4px 10px rgb(0 0 0 / 0.3);
/* a branded neon glow */
--text-shadow-neon: 0 0 1.5rem var(--color-fuchsia-500);
/* tweak the default md to be a bit punchier */
--text-shadow-md:
0px 1px 1px rgb(0 0 0 / 0.18),
0px 2px 4px rgb(0 0 0 / 0.18);
}
After that, text-shadow-xl and text-shadow-neon work the same as the built-in classes:
<h1 class="text-5xl font-bold text-fuchsia-100 text-shadow-neon">
Welcome to the future
</h1>
Same applies to shadow colours — adding --color-brand-500 in @theme automatically gives you text-shadow-brand-500. You do not need to declare anything extra.
If you are wiring this into a dark-mode toggle, the cleanest pattern is to strip the shadow in dark mode where the background is already dark enough:
<h1 class="text-shadow-lg text-shadow-black/40 dark:text-shadow-none">
Adaptive heading
</h1>
That works with both default prefers-color-scheme dark mode and the class-based strategy I walk through in the Tailwind v4 CSS-first dark mode guide.
Gotchas and edge cases#
A few things to watch for once you start sprinkling these classes around:
Browser support is universal but the visual fallback differs. Every browser shipped text-shadow over a decade ago, so you do not need a fallback. The reason it had to wait for Tailwind v4.1 was the @property registration for the shadow colour — the rest of the v4 type system needed to land first.
Opacity defaults are deliberately low. All five sizes default to 10–20% opacity. If your hero photo is busy, that is too subtle. The opacity modifier is doing real work — start at /40 for hero copy and adjust from there.
The /opacity shorthand only colours black shadows. text-shadow-lg/50 does not adjust the opacity of a coloured shadow you set on the same element. To get a 50%-opacity sky-500 shadow you write text-shadow-lg text-shadow-sky-500/50 — the size class and the colour class with an opacity modifier on the colour, not on the size.
Container queries need it on the right element. If you are styling a heading inside a card that uses container queries, put text-shadow-* on the heading itself, not the card. The utility cascades to descendants the same way color does, but container variants resolve on the element they are applied to.
Stripping inherited shadows. Children of an element with text-shadow-lg will inherit the shadow. text-shadow-none on the child clears it — useful inside dark sections or pill components where the heading shadow leaks into nested labels.
Wrapping up#
Five utilities, one shorthand, one @theme block — that is the whole feature, and it replaces a CSS file most projects had been carrying for a decade. Start by swapping any existing style="text-shadow: …" for text-shadow-md or text-shadow-lg with a colour modifier, then customise --text-shadow-* in @theme once you know what your brand actually needs.
For the rest of the v4.1 surface, the Tailwind v4 starting-style and transition-discrete article covers the other quietly-significant change shipped alongside text shadows — display: none transitions without JavaScript.
FAQ#
Which Tailwind version added the text-shadow utility?
Tailwind CSS v4.1 added the text-shadow-* utility family, released on April 3, 2025. Prior to v4.1 you either inlined CSS, registered a plugin, or used the drop-shadow-* filter as a workaround — but none of those put a shadow on the text glyphs themselves the way the native text-shadow CSS property does.
How do I change the colour of a Tailwind text shadow?
Apply a colour utility from the standard palette alongside the size utility: text-shadow-lg text-shadow-sky-500/40. The /40 is an opacity modifier on the colour. If you only want to dial the opacity of the default black shadow, the shorthand text-shadow-lg/40 does the same thing in one class. Custom palette colours added to @theme work the same way — --color-brand-500 gives you text-shadow-brand-500 automatically.
Can I use text-shadow with dark mode in Tailwind v4?
Yes — text-shadow-* works with the dark: variant identically to any other utility. A common pattern is text-shadow-lg dark:text-shadow-none to drop the shadow when the background is already dark. It also pairs with the class-based dark-mode strategy using @custom-variant in v4, so you can manually toggle the .dark class on <html> and have the shadow respond accordingly.
What is the difference between drop-shadow and text-shadow in Tailwind?
text-shadow-* paints a shadow on the actual letter shapes using the native CSS text-shadow property. drop-shadow-* is an SVG filter applied to the element's bounding box that follows the alpha channel — it works on text but also on transparent PNGs, SVGs, and any element with cut-outs. For headings on photography you want text-shadow-*; for floating icons or shapes off the page you want drop-shadow-*. They look subtly different on the same text and they layer differently with other filters.
How do I add a custom text-shadow size in Tailwind v4?
Declare a --text-shadow-* variable in your @theme block: @theme { --text-shadow-xl: 0 4px 10px rgb(0 0 0 / 0.3); }. After that, text-shadow-xl is available as a utility class everywhere in your markup. The same approach works for any name — --text-shadow-neon, --text-shadow-letterpress, anything. You can also override the defaults by redeclaring --text-shadow-md and friends with your own values.