If you have ever seen a heading with a soft glow behind it, or letters that look like they are lifted off the page with a hard offset, that is CSS text shadow doing its job. Some people call it a text drop shadow, others call it a font shadow, but it is all the same single property: text-shadow. It looks like a complex property when you first encounter it, but once you understand what each value controls, it becomes one of the easiest ways to add depth and personality to your typography.
This guide covers the full syntax, what each value does, how to stack multiple shadows, and a set of ready-to-use patterns for the effects you will actually need.
What is CSS text-shadow?
The text-shadow property adds one or more shadows directly behind the characters in a text element. You control where the shadow sits, how blurry it is, and what color it should be. That is the entire model.
It has been supported in all major browsers for many years. No vendor prefixes, no polyfills, no fallbacks needed. It works in Chrome, Firefox, Safari, Edge, and on every major mobile browser without any extra setup.
The syntax
The full syntax for a single shadow is:
text-shadow: offset-x offset-y blur-radius color;
Each value has a specific role:
- offset-x: How far the shadow moves horizontally. Positive moves it right, negative moves it left.
- offset-y: How far the shadow moves vertically. Positive moves it down, negative moves it up.
- blur-radius: How blurry the shadow is. A value of
0gives a hard edge. Higher values spread the shadow out and soften it. This value is optional and defaults to 0 if omitted. - color: The color of the shadow. Accepts any valid CSS color format: hex, rgb, rgba, hsl, hsla, or named colors like
red. Technically optional, but always set it explicitly to avoid inconsistent browser defaults.
A basic example
Here is the most common use case: a soft drop shadow on a heading.
h1 { text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4); }
Reading that: the shadow is 2px to the right, 2px down, blurred by 4px, and is semi-transparent black. This is the pattern you will use most often for headings on light or medium backgrounds.
Using text-shadow directly in HTML
Every example on this page targets a selector in a stylesheet, but text-shadow is a normal CSS property, so it works just as well as an inline style or inside a <style> block right in the HTML file:
<!-- Inline style attribute --> <h1 style="text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);">Hello</h1> <!-- Inside a <style> block --> <style> h1 { text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4); } </style> <h1>Hello</h1>
Both render identically. Inline styles are fine for a quick test in a single file, but for anything beyond a one-off demo, keep text-shadow in an external stylesheet along with the rest of your CSS. It is easier to maintain and reuse across pages.
What each value does to the result
Changing the offset
A small offset creates subtle depth. A large offset creates a bold, graphic shadow. Zero offset with only blur creates a centered glow with no directional light source.
/* Subtle depth */ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); /* Bold retro offset */ text-shadow: 5px 5px 0px #000; /* Centered glow: no directional offset */ text-shadow: 0px 0px 12px rgba(124, 106, 247, 0.9);
Changing the blur radius
Zero blur gives a hard, sharp-edged shadow. This works well for retro and graphic styles. Higher blur values produce soft glows. Use these for neon effects or atmospheric display headings.
/* Hard edge: zero blur */ text-shadow: 3px 3px 0 #222; /* Soft atmospheric glow */ text-shadow: 0 0 20px rgba(124, 106, 247, 0.8);
Using rgba instead of solid colors
Using rgba lets you control how strong the shadow appears through the alpha channel. Semi-transparent shadows almost always look more natural than solid black or solid color shadows, which can feel flat and dated.
/* Harsh: avoid this for body text */ text-shadow: 2px 2px 4px #000000; /* Natural: prefer this */ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.35);
Controlling Text Shadow Opacity
The most direct way to control how strong a text shadow appears is through the alpha value in rgba() or hsla(). Lower alpha means a more transparent, subtle shadow. Higher alpha means a harder, more visible one.
/* Very subtle: almost invisible */ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15); /* Medium: natural depth */ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.35); /* Strong: high contrast shadow */ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.75); /* Using hsla for color-matched shadows */ text-shadow: 2px 2px 6px hsla(250, 80%, 20%, 0.5);
Multiple text shadows
You can stack multiple shadows on a single element by separating them with commas. Each shadow is rendered independently and layered in the order they are written, with the first one on top.
h1 { text-shadow: 1px 1px 0 #ccc, 2px 2px 0 #bbb, 3px 3px 0 #aaa, 4px 4px 0 #999; }
That example builds a 3D stacked effect by placing four offset shadows in progressively darker shades. Each one sits one pixel further than the last, creating the illusion of depth and volume.
Note: There is no limit to how many shadows you can stack. The cost is that each additional shadow adds rendering work. For most effects, two to four shadows is enough.
All values at a glance
| Value | Required | What it controls |
|---|---|---|
| offset-x | Yes | Horizontal position of the shadow |
| offset-y | Yes | Vertical position of the shadow |
| blur-radius | No (default: 0) | How soft and spread out the shadow is |
| color | No (recommended) | The color of the shadow |
Practical CSS text shadow examples
Classic drop shadow
h2 { text-shadow: 2px 3px 6px rgba(0, 0, 0, 0.3); }
Solid choice for headings on light backgrounds. Adds depth without drawing attention to itself.
Neon glow effect
h1 { color: #fff; text-shadow: 0 0 7px #fff, 0 0 10px #fff, 0 0 21px #fff, 0 0 42px #7c6af7, 0 0 82px #7c6af7; }
Stack multiple centered shadows with increasing blur radii. Works best on dark backgrounds. The white inner glow creates the bright core, and the colored outer glows create the neon bleed effect.
Hard retro shadow
h1 { color: #fff; text-shadow: 4px 4px 0px #222; }
Zero blur, solid offset. Bold and graphic. Works best with thick display fonts and high-contrast backgrounds. This is the retro poster look.
3D stacked shadow
h1 { color: #e2e8f0; text-shadow: 1px 1px 0 #555, 2px 2px 0 #444, 3px 3px 0 #333, 4px 4px 0 #222, 5px 5px 0 #111; }
Each layer adds one more pixel of offset and slightly deeper color. Gives the impression that the letters are extruded off the page.
Text outline / stroke effect
h1 { color: #fff; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; }
Four zero-blur shadows, one offset toward each diagonal, trace a fake stroke around every character. This is the classic cross-browser workaround for outlining text, and it's useful as a fallback anywhere -webkit-text-stroke isn't supported or when the text needs to stay selectable and copyable, which -webkit-text-stroke can distort on some font weights.
Embossed text (inset-style shadow)
p { color: #94a3b8; text-shadow: 1px 1px 1px #fff, -1px -1px 1px #444; }
Two opposing shadows, one light and one dark. The combination makes the text look raised out of the surface like a physical emboss stamp.
Engraved text
p { color: #94a3b8; text-shadow: -1px -1px 1px #fff, 1px 1px 1px #444; }
Same two shadows but reversed. The light source appears to come from the opposite direction, making the text look carved into the surface instead of raised above it.
For a deeper look at these effects including SVG filter techniques and the background-clip approach, see the CSS text shadow inset guide.
Legibility shadow for text over images
p { color: #fff; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6); }
This is a practical pattern, not a decorative one. When white text sits over a photo or gradient background, a subtle shadow underneath ensures the text stays readable as the background changes behind it.
text-shadow vs filter: drop-shadow()
These two produce similar results on text but they are not the same property.
| Property | Applies to | Performance | Use for |
|---|---|---|---|
| text-shadow | Text characters only | ✅ Lighter | Text styling |
| filter: drop-shadow() | Entire element including transparent areas | 🟡 Heavier | Images, SVGs, icons |
For text, always use text-shadow. It is the right tool. filter: drop-shadow() is better suited to images, SVGs, and PNG icons where you want the shadow to follow the visible shape of the element rather than its bounding box.
Using filter: drop-shadow() on text
If you want the shadow to follow the exact rendered shape of the text including any transparent areas, filter: drop-shadow() is the alternative. The syntax mirrors text-shadow but uses a function format.
/* filter: drop-shadow() syntax */ /* drop-shadow(offset-x offset-y blur-radius color) */ h1 { filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.4)); } /* No spread-radius, unlike box-shadow */ /* No inset keyword support */ /* Multiple drop-shadows require multiple filter functions */ h1 { filter: drop-shadow(1px 1px 0 #555) drop-shadow(2px 2px 0 #333); }
Text shadow in Tailwind CSS
Tailwind CSS v4.1 added native text-shadow utilities, so a plugin is no longer required to get text-shadow classes in a Tailwind project.
<!-- Preset sizes: 2xs, xs, sm, md, lg, xl --> <p class="text-shadow-lg">Hello</p> <!-- Adjust opacity with a slash modifier --> <p class="text-shadow-lg/30">Hello</p> <!-- Set a shadow color with Tailwind's color utilities --> <p class="text-shadow-lg text-shadow-indigo-500">Hello</p> <!-- Arbitrary value, for anything the presets don't cover --> <p class="text-shadow-[0_4px_8px_rgb(0_0_0_/_0.4)]">Hello</p> <!-- Remove a shadow at a breakpoint or state --> <p class="text-shadow-lg dark:text-shadow-none">Hello</p>
On Tailwind v3 and earlier there is no built-in utility, so you would add a community plugin such as tailwindcss-textshadow to your config, or fall back to an arbitrary-value class like [text-shadow:2px_2px_4px_rgba(0,0,0,0.4)], which works in any Tailwind version that supports arbitrary properties.
Performance considerations
A single text-shadow has almost no performance cost. Where things get expensive:
- Stacking many shadows on many elements at once
- Using very large blur radius values
- Animating
text-shadowdirectly with CSS transitions or keyframes
Do not animate text-shadow directly. It triggers repaint on every frame. If you need an animated glow or shadow, animate the opacity of a pseudo-element with a fixed shadow instead. Add will-change: opacity to that pseudo-element ahead of time so the browser promotes it to its own compositor layer before the animation starts, rather than during it. That combination stays smooth.
Common mistakes to avoid
- Heavy blur on body text: High blur reduces legibility. Keep blur low for text people need to read. Save large blur values for display headings or decorative elements.
- Pure black without transparency: Solid black shadows look harsh. Use
rgba(0, 0, 0, 0.3)style values instead. - Applying it everywhere: Text shadow works best when it is intentional. Using it on every element makes a page feel cluttered and visually noisy.
- Large offsets on small text: A 5px offset works on a 60px heading. On 14px body text it looks broken. Scale your shadow values relative to the font size.
- Only providing one length value:
text-shadowneeds at least offset-x and offset-y. A declaration liketext-shadow: 5px red;is invalid and gets ignored by the browser, not just rendered oddly. - Expecting a clean outline from a single shadow: one
text-shadowonly offsets in one direction, so it cannot fake a stroke around a letter by itself. For a real outline effect, stack four shadows (see the outline example above) or use-webkit-text-strokewhere it's supported.
Browser support
Full support across all modern browsers. Chrome, Firefox, Safari, Edge, Opera, and all major mobile browsers support text-shadow without vendor prefixes. You can use it in production today without any compatibility concerns.
Older tutorials sometimes reference -moz-text-shadow or -webkit-text-shadow. These prefixes were only ever needed in very old Firefox and Safari builds and have not been required for years. Write plain text-shadow and nothing else.
Tip: The color value can be written before or after the offset and blur values. Both are valid CSS. For consistency, pick one order and stick to it across your codebase. Most developers write color last.
FAQ
Can you add multiple shadows to text in CSS?
Yes. Separate each shadow definition with a comma and they will all apply to the same element. Each shadow is rendered independently and layered in the order they appear in your CSS.
Is blur-radius required?
No. You can write text-shadow: 3px 3px #000; and it is valid. Omitting blur-radius defaults it to zero, which gives a sharp hard-edged shadow with no softness.
Does text-shadow work on inline elements?
Yes. It works on any element that renders text, including span, a, strong, em, and all heading and paragraph elements.
Can text-shadow be animated?
CSS technically supports animating text-shadow with transitions and keyframes, but it is expensive. Each frame triggers a repaint. If you need motion on a shadow, animate the opacity of a pseudo-element with a fixed shadow instead.
What is the difference between text-shadow and filter: drop-shadow()?
text-shadow applies to the text characters only. filter: drop-shadow() applies to the entire rendered element including transparent areas. For text, use text-shadow. For images and SVGs, filter: drop-shadow() is more appropriate.
How do I create an inset or inner text shadow in CSS?
CSS text-shadow has no inset keyword. The closest approaches are layered opposing shadows, one light and one dark, to simulate depth, or SVG filters for a true inner shadow effect. See the full guide on CSS text shadow inset effects for code examples of each technique.
How do I create a text outline or stroke effect with text-shadow?
Stack four text-shadow values with zero blur, offset 1px in each diagonal direction, using the outline color. This traces a fake stroke around each character and works as a fallback anywhere -webkit-text-stroke isn't supported, or when the text needs to stay selectable.
Does Tailwind CSS support text-shadow?
Yes, natively as of Tailwind CSS v4.1, using utilities like text-shadow-sm through text-shadow-xl, plus opacity modifiers, color utilities, and arbitrary values. On earlier Tailwind versions you need a community plugin or an arbitrary-value class to get the same result.
Can text-shadow be added directly in an HTML file?
Yes. It works as an inline style attribute or inside a <style> block in the HTML file itself, exactly like it does in an external stylesheet. An external stylesheet is still the better long-term choice for anything beyond a quick test.
Build text shadows visually
Our CSS Text Shadow Generator lets you adjust offset, blur, and color in real time and copy the generated CSS directly into your project.
Open Text Shadow Generator →