Text effects
Four finishes for display type: a headline that rises line by line as it arrives, a glow around the glyphs, hollow letters drawn with a stroke, and a band of light crossing a line once. All four are CSS. Only the reveal and the single-pass shimmer need a script, and only to be told that the element is on screen — that is Reveal, which the effects page already documents. Under prefers-reduced-motion: reduce every one of them is a still, legible headline.
These are built for text-3xl and up. At 1.5 rem a one-pixel stroke picks up the colour fringing of subpixel antialiasing and a glow smears into the counters of the letters. Body copy keeps the contrast of the theme and nothing else.
Reveal, line by line
Each line is a mask — iv-text-reveal__line with a span inside it — and the inner span starts one line lower until Reveal marks the heading with data-iv-inview. The author writes the line breaks: no script splits the text, so it stays selectable, translatable and readable exactly as served. The stagger is --iv-i multiplied by --iv-text-reveal-step, two plain custom properties with no timer behind them.
<section id="text-reveal" data-iv-component="reveal" class="iv-stack iv-u-gap-8">
<p class="iv-u-m-0 iv-u-max-w-measure">Sample copy for the fixture. Each line of the headline below is a mask; the span inside it starts one line lower and rises when <code>Reveal</code> marks the heading with <code>data-iv-inview</code>. The author writes the line breaks: no script splits the text, so it stays selectable, translatable and readable exactly as it was served. Without JavaScript, and under <code>prefers-reduced-motion: reduce</code>, every line is already up.</p>
<h2 class="iv-text-reveal iv-u-m-0" style="font-size: var(--iv-text-4xl); line-height: var(--iv-leading-tight); letter-spacing: var(--iv-tracking-tight)" data-iv-reveal>
<span class="iv-text-reveal__line"><span>The interface</span></span>
<span class="iv-text-reveal__line" style="--iv-i: 1"><span>starts here.</span></span>
</h2>
<div class="iv-stack iv-u-gap-4 iv-u-py-12" style="min-block-size: 90vh">
<h3 class="iv-u-m-0">Filler, so the next headline arrives on scroll</h3>
<p class="iv-u-m-0 iv-u-max-w-measure">The first heading is above the fold and reveals as soon as the observer reports it. The second one is below, and waits.</p>
<p class="iv-u-m-0 iv-u-max-w-measure">More filler. Sample copy for the fixture, with no claim of any kind.</p>
</div>
<h2 class="iv-text-reveal iv-u-m-0" style="font-size: var(--iv-text-4xl); line-height: var(--iv-leading-tight); letter-spacing: var(--iv-tracking-tight); --iv-text-reveal-step: 110ms" data-iv-reveal>
<span class="iv-text-reveal__line"><span>Four lines,</span></span>
<span class="iv-text-reveal__line" style="--iv-i: 1"><span>one after another,</span></span>
<span class="iv-text-reveal__line" style="--iv-i: 2"><span><span class="iv-gradient-text">staggered</span> by the index</span></span>
<span class="iv-text-reveal__line" style="--iv-i: 3"><span>the author served.</span></span>
</h2>
<p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-12">The stagger is <code>--iv-i</code> multiplied by <code>--iv-text-reveal-step</code>, both plain custom properties: there is no JavaScript timer behind them.</p>
</section>.iv-root {
--iv-i: 1;
--iv-text-reveal-step: 110ms;
}// data-iv-component="reveal"
{
reveal: true,
}Glow
Two layers of text-shadow: a tight one that thickens the strokes, a wide one that spills onto the surface behind. The colour is the halo token of the theme, so it follows light and dark. iv-text-glow--pulse breathes on a 2.4 second cycle. A halo is a finish, not a contrast device — the glyph keeps the contrast it had.
<section id="text-glow" class="iv-stack iv-u-gap-8">
<p class="iv-u-m-0 iv-u-max-w-measure">Sample copy for the fixture. <code>.iv-text-glow</code> is two layers of <code>text-shadow</code> around the glyphs: a tight one that thickens the strokes and a wide one that spills onto the surface behind. The colour is the halo token of the theme, so it changes with light and dark.</p>
<h2 class="iv-text-glow iv-u-m-0" style="font-size: var(--iv-text-4xl); line-height: var(--iv-leading-tight); letter-spacing: var(--iv-tracking-tight)">Voltage in the type</h2>
<h2 class="iv-text-glow iv-text-glow--pulse iv-u-m-0" style="font-size: var(--iv-text-4xl); line-height: var(--iv-leading-tight); letter-spacing: var(--iv-tracking-tight)">And the same line, breathing</h2>
<p class="iv-u-m-0 iv-u-max-w-measure">The pulse runs for 2.4 seconds and repeats; under <code>prefers-reduced-motion: reduce</code> it stops at the glow itself. In forced colours the shadow is dropped and the heading is plain system text.</p>
<div class="iv-card iv-texture-mesh">
<div class="iv-card__body iv-stack iv-u-gap-4 iv-u-py-10">
<h3 class="iv-text-glow iv-u-m-0" style="font-size: var(--iv-text-3xl); line-height: var(--iv-leading-tight); letter-spacing: var(--iv-tracking-tight); --iv-text-glow-color: var(--iv-glow-secondary); --iv-text-glow-size: 0.7em">Over a gradient surface</h3>
<p class="iv-u-m-0 iv-u-max-w-measure">Both <code>--iv-text-glow-color</code> and <code>--iv-text-glow-size</code> are public locals; here the halo is the secondary one and a little wider.</p>
</div>
</div>
<p class="iv-u-m-0 iv-u-max-w-measure iv-u-text-sm iv-u-text-muted">A halo is a finish, not a contrast device: the glyph itself keeps the contrast of the theme, and the glow only adds presence around it.</p>
</section>.iv-root {
--iv-text-glow-color: var(--iv-glow-secondary);
--iv-text-glow-size: 0.7em;
}Outline
iv-text-outline draws the letters hollow with the stroke the engine paints around each glyph, behind an @supports test: where the property is missing the heading simply keeps its fill. iv-text-outline--fill fills it back in while the link around it is hovered or focused. The stroke uses -webkit-text-fill-color: transparent rather than a transparent color, because both the stroke and the shimmer read currentColor, and its colour is the primary — the accent of the dark theme is a bright cyan and does not hold contrast as a hairline.
<section id="text-outline" class="iv-stack iv-u-gap-8">
<p class="iv-u-m-0 iv-u-max-w-measure">Sample copy for the fixture. <code>.iv-text-outline</code> draws the letters hollow with the stroke the engine paints around each glyph. Where the property is missing the heading keeps its normal fill: less dramatic, never lost. In forced colours it goes back to plain text as well.</p>
<h2 class="iv-text-outline iv-u-m-0" style="font-size: var(--iv-text-4xl); line-height: var(--iv-leading-tight); letter-spacing: var(--iv-tracking-tight)">Hollow headline</h2>
<h2 class="iv-text-outline iv-u-m-0" style="font-size: var(--iv-text-4xl); line-height: var(--iv-leading-tight); letter-spacing: var(--iv-tracking-tight); --iv-text-outline-width: 2px; --iv-text-outline-color: var(--iv-color-primary)">Thicker, in the primary</h2>
<h3 class="iv-u-m-0">Filled by the link around it</h3>
<p class="iv-u-m-0 iv-u-max-w-measure">Hover the line below, or reach it with the keyboard: the fill returns while the link that contains it is hovered or focused. The link points back at this fixture and does nothing else.</p>
<p class="iv-u-m-0"><a href="#text-outline" style="font-size: var(--iv-text-4xl); line-height: var(--iv-leading-tight); letter-spacing: var(--iv-tracking-tight); text-decoration-thickness: 2px; text-underline-offset: 0.12em"><span class="iv-text-outline iv-text-outline--fill">Point at me</span></a></p>
<p class="iv-u-m-0 iv-u-max-w-measure iv-u-text-sm iv-u-text-muted">An outlined heading is thinner than solid text: keep it large, keep the stroke colour at contrast — the accent of the light theme is a bright green and does not reach it — and never use it for body copy. Below a display size the hairline picks up the colour fringing of subpixel antialiasing.</p>
</section>.iv-root {
--iv-text-outline-width: 2px;
--iv-text-outline-color: var(--iv-color-primary);
}Shimmer
A gradient clipped to the glyphs, with only its position animated, so a band of light crosses the line and nothing repaints outside it. iv-text-shimmer--once waits for data-iv-inview, runs the band across a single time and stays put — a loop beside running text competes with the reading. Do not combine it with iv-gradient-text: both own the same background, so pick one.
<section id="text-shimmer" data-iv-component="reveal" class="iv-stack iv-u-gap-8">
<p class="iv-u-m-0 iv-u-max-w-measure">Sample copy for the fixture. <code>.iv-text-shimmer</code> clips a gradient to the glyphs and moves only its position, so a band of light crosses the line and nothing repaints outside it. Do not combine it with <code>.iv-gradient-text</code>: both own the same background, so pick one. The band is the primary of the theme, not the accent: it replaces the colour of the glyph while it passes, and it has to stay readable the whole way across.</p>
<h2 class="iv-text-shimmer iv-u-m-0" style="font-size: var(--iv-text-4xl); line-height: var(--iv-leading-tight); letter-spacing: var(--iv-tracking-tight)">A band of light crosses the line</h2>
<h3 class="iv-u-m-0">Once, when it arrives</h3>
<p class="iv-u-m-0 iv-u-max-w-measure">The heading below carries <code>--once</code> and <code>data-iv-reveal</code>: it waits, still, until <code>Reveal</code> marks it as in view, runs the band across a single time and stays put. A loop next to running text would compete with the reading.</p>
<div class="iv-u-py-12" style="min-block-size: 90vh">
<p class="iv-u-m-0 iv-u-max-w-measure">Filler, so the heading below starts out of view.</p>
</div>
<h2 class="iv-text-shimmer iv-text-shimmer--once iv-u-m-0" style="font-size: var(--iv-text-4xl); line-height: var(--iv-leading-tight); letter-spacing: var(--iv-tracking-tight)" data-iv-reveal>One pass, then still</h2>
<p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-12">Under <code>prefers-reduced-motion: reduce</code> the band is parked off the glyphs, so the line keeps its own colour and its contrast. In forced colours the gradient is dropped entirely.</p>
</section>// data-iv-component="reveal"
{
reveal: true,
}Keyboard and assistive technology
All four are headings and stay headings. There is nothing to focus, no role to add and no aria-hidden to hang on a decorative half, because nothing here duplicates the text: the reveal wraps the author's own line breaks, the glow and the shimmer paint the same glyphs, and the outline hollows them. Screen readers get the heading as written. In forced-colours mode the shadow, the gradient and the stroke are all dropped and the heading is plain system text.
Without JavaScript
Glow and outline never needed a script. The shimmer loops on its own; only its --once variant waits for Reveal, and without the script it settles visible. The reveal is the interesting case: the rule that hides a line is written under :root[data-iv-js], so a page whose script never loads shows every line up and readable. A headline can never be hidden by a reveal that did not arrive.
Tokens and locals
| Local | Default | What it moves |
|---|---|---|
--iv-text-reveal-distance | 110% | How far below the mask a line starts. |
--iv-text-reveal-duration | slow motion token × 3 | Length of one line's rise. |
--iv-text-reveal-step | 80ms | Delay per --iv-i between lines. |
--iv-text-glow-color | var(--iv-glow-primary) | Colour of the halo. |
--iv-text-glow-size | glow default | How far the halo spills. |
--iv-text-outline-width | 1px | Thickness of the stroke. |
--iv-text-outline-color | currentColor | Colour of the stroke. |
--iv-text-shimmer-color, --iv-text-shimmer-highlight | text colour, var(--iv-color-primary) | The line at rest, and the band that crosses it. |
--iv-text-shimmer-duration | 2.8s | One pass of the band. |
The shimmer keyframes run from 100 % to 0 %, not the other way round: outside that range the gradient leaves the box and the headline disappears with it.
Common mistakes
- Letting a script split the headline into words or characters. The line breaks are content decisions; write them.
- Outline or glow on body copy, or on anything below a display size.
- Stacking
iv-text-shimmeroniv-gradient-text. One background, one owner. - A shimmer that loops next to a paragraph somebody is trying to read. Use
--once. - Reaching for a glow to fix a contrast problem. Fix the colour.