Styling Perfect Underlines and Drop Caps with Modern CSS

Create cross-browser drop caps with the CSS initial-letter property and style precise underlines using text-decoration-thickness and text-underline-offset.

You have a drop cap that sits on a line-height grid you tuned by hand, and any font change breaks it. The fix is the CSS initial-letter property, which sizes and aligns the first letter to the specified number of lines using the font’s own metrics. You delete the float, the negative margins, and the magic numbers. This guide covers that property and the modern text-decoration family that replaces border-bottom underlines, with browser support notes you can act on today.

The Legacy Drop Cap: Float, Font-Size, and Line-Height Guesswork

Before initial-letter existed, a drop cap meant a element floated left. You set font-size to a multiple of the line height and tuned margins until the cap visually aligned. The classic recipe looked like this:

.drop-cap {
  float: left;
  font-size: 5.5em;
  line-height: 0.8;
  margin: 0.1em 0.15em 0 0;
}

That code depends on the font's ascender and descender metrics. A 5.5em size with a line-height of 0.8 might work for Georgia, then break for Inter. The margin value is a guess that survives one font and fails the next. Every change to the surrounding paragraph's type size or leading forces you to re-tune all three numbers. Worse, the float creates a block formatting context that can collide with preceding content if you forget to clear it. The technique is not wrong for a static page with one font. It is fragile, and the maintenance cost compounds across a design system.

The native replacement is the initial-letter property, part of the CSS Inline Layout Module Level 3. Apply it to the ::first-letter pseudo-element or an inline-level first child of a block container. Declare the number of lines the cap should span. The browser does the geometry.

Understanding the CSS drop caps initial-letter property

Syntax And The Sink Value

The initial-letter property takes two optional values: the drop size and an optional sink size. Write initial-letter: 3 for a three-line cap. Write initial-letter: 3 2 if you want the cap to span three lines but sink only two. The sink value controls how far the cap descends below the first baseline. When you omit the sink, it defaults to the drop size, producing the classic sunk cap.

p::first-letter {
  initial-letter: 3;
  font-size: inherit; /* do not set a separate size */
}

Browser Support And The Two-Tier Pattern

Safari has shipped initial-letter since version 9.0 in 2015. Chrome and Firefox have not shipped it as of the current data, which the MDN browser compat pages publish and revise on a rolling schedule. The Baseline status is Limited availability. Do not use it as the only implementation. Write the float version as the default, then wrap the initial-letter declaration in an @supports block that overrides it where supported.

p::first-letter {
  float: left;
  font-size: 5.5em;
  line-height: 0.8;
  margin: 0.1em 0.15em 0 0;
}

@supports (initial-letter: 3) {
  p::first-letter {
    float: none;
    font-size: inherit;
    line-height: normal;
    margin: 0;
    initial-letter: 3;
  }
}

That @supports query is the accepted fallback pattern. It cleans up the legacy properties only when the new one exists. Safari 9 and later get the modern alignment. Everyone else keeps the float approximation.

initial-letter drop cap browser support and the @supports fallback

The Uneven Support Landscape

The initial-letter drop cap browser support table is short and uneven. Safari shipped it in 9.0. Chrome and Firefox have not shipped it, according to the data that MDN and the Web Platform Baseline dashboard track. The feature remains Limited availability. You cannot rely on it for the general public. Always write the legacy float technique first, then layer initial-letter behind an @supports test. That is the standard progressive enhancement pattern for a property with uneven engine adoption.

Two Mistakes That Break The Fallback

First, applying initial-letter to an element without clearing floats above it can cause the drop cap to overlap preceding content. The float from an earlier element is still in the document flow. The new cap does not automatically clear it. Add a clear: both to the paragraph that contains the cap, or wrap the cap's paragraph in a container that establishes a new block formatting context. Second, setting initial-letter on an element whose type size is inherited from a parent with a different line spacing produces a misaligned cap height. The property uses the font's units, not the inherited leading. If the parent uses a line-height of 1.5 and the cap inherits that, the visual result looks off. Fix it by setting line-height: normal on the ::first-letter inside the @supports block, as the example above does.

The ::first-letter pseudo-element drop cap and its constraints

What The Pseudo-Element Matches

The ::first-letter pseudo-element drop cap is the selector you apply the property to. It matches the first formatted line's first letter, but only under specific conditions. The element must be a block container. The first letter must be preceded by no other content: no images, inline-block elements, or floats. The pseudo-element picks up punctuation that precedes the letter, like an opening quote, and includes it in the drop cap. To start the cap at the actual letter and leave the quote out, wrap the first word in a span and target that span instead.

Applicable Properties

The ::first-letter pseudo-element has its own set of applicable properties, a subset of all CSS. Font properties, color, background, margin, padding, border, and text-decoration work. Properties like float, position, and display do not apply directly to the pseudo-element. You can style the containing block. This is why the legacy float technique used a span inside the paragraph rather than the pseudo-element alone. With initial-letter, the pseudo-element is the natural target. The property was designed for it.

Text-decoration-thickness and Underline-offset CSS: The Modern Underline

Replacing The Border-Bottom Hack

text-decoration-thickness and text-underline-offset solve the same class of problem that drop caps did: native properties replacing hand-tuned hacks. The old underline was a border-bottom on a span. It sat on the element's box edge, not the text baseline. It broke when the line wrapped. Its vertical position was a guess. The text-decoration family gives you explicit control over the line's thickness and its offset from the text, using the font's own metrics where possible.

Thickness, Offset, And The From-Font Value

The thickness property accepts auto, from-font, a length, or a percentage. The from-font value uses the font's built-in underline thickness. It is the most consistent choice. The offset property controls how far the line sits below the baseline. It accepts auto, from-font, a length, or a percentage. Setting text-underline-offset: 0.2em moves the line down relative to the type size. That keeps the underline clear of descenders like the tail on 'g' or 'y'.

a {
  text-decoration-line: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.2em;
  text-decoration-color: #0066cc;
  text-decoration-style: solid;
}

Wide Availability And The Fallback Question

The two properties are Baseline widely available. Firefox shipped both in version 70 in 2019, Safari in 12.1 the same year, and Chrome in 87 for the offset and 89 for the thickness. Use them without a fallback for the modern browser population. The only reason to write a fallback is for older device-locked browsers. Those are users on iOS Safari versions that cannot update, or Android WebView instances in apps that do not ship updates. The size of that population is not reliably measured in a single public survey. For those users, the @supports pattern is the accepted route.

a {
  border-bottom: 1px solid #0066cc;
  text-decoration: none;
}

@supports (text-decoration-thickness: 2px) {
  a {
    border-bottom: none;
    text-decoration-line: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 0.2em;
  }
}

CSS decorative underlines with color, style, and skip-ink

Style And Color Choices

CSS decorative underlines go beyond the default solid black line. The text-decoration-style property accepts solid, double, dotted, dashed, and wavy. It has been widely available since 2017. The color property defaults to currentcolor, so the underline matches the text color unless you override it. The shorthand text-decoration takes line, style, color, and thickness in any order. A common mistake is using it to set only a color. That resets the line to none and the style to solid. Always use the shorthand with all four values, or set the longhands individually.

Skip-Ink And Descender Gaps

The text-decoration-skip-ink property controls whether the underline breaks where it crosses a descender. The default is auto. The browser skips the ink where a letter descends below the baseline. That is why a default underline appears to have a gap under a 'g' or 'y'. Setting text-decoration-skip-ink: none forces the line straight through the descender. It looks bad with most fonts. It can be a deliberate stylistic choice with a thick line and a high offset. The property has been widely available since 2020. Use it freely. The failure case with skip-ink is a thick weight and a small offset. The line collides with the descenders, and the auto skip creates an uneven visual. Increase the offset. Do not disable the skip.

Common Underline Mistakes and When to Skip the Decoration

Pair Thickness With Offset

Setting text-decoration-thickness without also setting text-underline-offset causes descender collisions when the thickness increases. A thick line at the default offset sits too close to the baseline. The letters' tails run into it. Pair thickness with an offset of at least 0.1em, or use from-font for both. Another mistake is using the shorthand to set only the color. That resets the line to none and the style to solid. For a red underline, write text-decoration: underline solid red 2px or set the longhands.

When To Leave The Underline Off

Skip the underline entirely when the text is already visually distinguished by weight, size, or background. Skip it when the link sits inside a sentence where the line competes with surrounding punctuation. The wavy style reads as a corruption indicator, not a link. Avoid it. For a line under a block element, use border-bottom, not text-decoration. The text-decoration family is for inline text and links only.

Raised Cap and Sunk Cap: Two Variants of the Drop Cap

How The Sink Value Shapes The Cap

initial-letter supports two visual variants beyond the classic sunk cap. A raised cap sits on the first baseline and extends upward. It does not descend below the line. The sunk cap, the default, drops below the baseline and fills the space of the first few lines. The difference is the sink value. For a raised cap, set the sink to 1. The cap occupies the first line and rises above it. For a sunk cap, the sink equals the drop size. A three-line cap sinks three lines.

p.raised::first-letter {
  initial-letter: 3 1; /* spans 3 lines, sinks 1 */
}

p.sunk::first-letter {
  initial-letter: 3 3; /* spans 3, sinks 3 */
}

Choosing The Right Variant

The raised cap suits editorial layouts where the first paragraph is set in a larger size. The cap acts as a visual anchor without pulling the following lines down. The sunk cap is the classic drop cap found in novels. Choose based on the paragraph's line count and the surrounding whitespace. Use a raised cap for a short paragraph of one or two lines. Use a sunk cap for a longer paragraph of four lines or more.

Font Metrics, Line-Height, and Margin: Why initial-letter Beats the Float

Direct Access To Font Metrics

The font-size, line-height, and margin values you tuned for the float version disappear with initial-letter. The property reads the font's ascender, descender, and cap height directly. It computes the size and baseline alignment from those metrics. A cap set to 3 in Georgia and a cap set to 3 in Inter both align to the same grid, even though the fonts have different vertical proportions. The margin hack compensated for the fact that font-size does not equal cap height. initial-letter removes the need to know that ratio.

Resilience Across A Design System

Change the paragraph's font-family, type size, or leading without touching the drop cap. The property recomputes relative to the new values. This is the single biggest advantage over the float technique. The float version required a fixed margin that pushed the cap away from the following text. initial-letter uses the font's side bearings, which are part of the glyph metrics. The spacing is not identical to the float. It is consistent across fonts, and that matters more for a design system.

The Interop and Baseline Status of These Typography Features

Widely Available Underline Properties

The Interop project is the annual cross-browser initiative where vendors agree to fix the same conformance gaps. text-decoration-thickness and text-underline-offset are part of that. Both are now widely available across Blink, WebKit, and Gecko. The Baseline status for these is widely available. Use them without a fallback for the vast majority of users.

The initial-letter Gap

The initial-letter property is the outlier. It is limited to Safari. Chrome and Firefox have not implemented it. The browser engine support is categorical: Blink and Gecko lack it, WebKit has it. The @supports rule is the only reliable way to bridge that gap. It tests the property:value pair directly. The browser applies the block only if it understands the syntax. Do not rely on a caniuse percentage for initial-letter. The real gap is users on older device-locked browsers, plus the entire Chrome and Firefox population that does not support the feature at all. The @supports fallback is not a nice-to-have. It is the required implementation.

FAQ: Drop Caps and Underlines in Practice

Does initial-letter work in Chrome or Firefox?

No. As of the latest data, Chrome and Firefox have not shipped it. Safari 9 and later support it. Write the float technique first and override with @supports. The same applies to any Chromium-based browser like Edge or Opera.

How do I make an underline thicker without breaking the descenders?

Set text-decoration-thickness to a value like 2px or 0.05em. Then set text-underline-offset to at least 0.2em. The offset moves the line below the descenders. Test with a word containing 'g' and 'y'.

What is the difference between a raised cap and a sunk cap?

A sunk cap, the default, descends below the first baseline and spans several lines. A raised cap sits on the baseline and rises above it. Set the sink value to 1 for a raised cap. Both use the same initial-letter property.

Can I use the text-decoration shorthand to set just a color?

No. The shorthand resets line, style, and thickness to their initial values. Use the longhand text-decoration-color, or write the full shorthand with all four parts. A shorthand that only sets color will remove the underline.

What Not to Use These Properties For

When To Skip The Drop Cap

initial-letter is not a tool for every paragraph. Use it for the opening paragraph of an article, a chapter, or a major section. Skip it for body text that follows a subheading, for captions, and for any paragraph shorter than three lines. A sunk cap on a two-line paragraph looks like a mistake. The raised cap is the only variant that works for short introductory lines, and only if the surrounding whitespace is generous.

Keep Text-Decoration On Inline Text

The text-decoration properties are not for buttons, cards, or any block-level element. border-bottom remains the right tool for a full-width line under a header or a card's footer. The text-decoration family applies to inline text and links. Using it on a block element produces a line that spans only the inline content. That is almost never what you want. For a line under a block, use border, not text-decoration.

The @supports fallback for initial-letter must also reset line-height to normal and clear floats above the paragraph. The property inherits the parent’s leading, and a float from a preceding element will overlap the cap.