Pure TailwindMIT licensed

Tailwind Gradient Text with Outline Stroke

Stroke-only, fill-only and stroke-plus-fill side by side, plus a hollow display word whose outline fills with gradient on hover. Tailwind has no stroke-text utility, so [-webkit-text-stroke:2px_…] and [paint-order:stroke_fill] arbitrary properties do the work — right in the class list.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="ttg-16 w-full min-h-svh grid place-items-center bg-ttg16-bg px-5 py-16 font-ttg16-sans text-ttg16-ink" aria-label="Gradient text with outline stroke demo">
  <div class="w-full max-w-3xl">
    <p class="font-ttg16-mono text-[11px] tracking-[0.16em] text-ttg16-mut uppercase">Stroke + gradient fill · hover the outline</p>
    <h1 class="mt-4 w-fit cursor-default bg-linear-to-r from-ttg16-a via-ttg16-b to-ttg16-c bg-[length:0%_100%] bg-no-repeat bg-clip-text font-ttg16-display text-[clamp(3.5rem,13vw,7.5rem)] leading-[0.95] tracking-[0.02em] text-transparent transition-[background-size] duration-700 ease-out [-webkit-text-stroke:2px_var(--color-ttg16-a)] hover:bg-[length:100%_100%] motion-reduce:transition-none">OUTLINE</h1>
    <h2 class="mt-3 w-fit bg-linear-to-r from-ttg16-a via-ttg16-b to-ttg16-c bg-clip-text text-[clamp(1.6rem,5vw,2.8rem)] font-bold tracking-[-0.02em] text-transparent [-webkit-text-stroke:1px_var(--color-ttg16-stroke)] [paint-order:stroke_fill]">Gradient fill + stroke</h2>
    <div class="mt-10 grid gap-4 sm:grid-cols-3">
      <figure class="grid gap-3 rounded-2xl border border-white/8 bg-white/[0.025] p-5">
        <span class="font-ttg16-display text-6xl text-transparent [-webkit-text-stroke:2px_var(--color-ttg16-a)]">Type</span>
        <figcaption class="font-ttg16-mono text-[11.5px] text-ttg16-mut">Stroke only<br><span class="text-ttg16-ink/80">text-transparent + stroke</span></figcaption>
      </figure>
      <figure class="grid gap-3 rounded-2xl border border-white/8 bg-white/[0.025] p-5">
        <span class="w-fit bg-linear-to-r from-ttg16-b to-ttg16-c bg-clip-text font-ttg16-display text-6xl text-transparent">Type</span>
        <figcaption class="font-ttg16-mono text-[11.5px] text-ttg16-mut">Fill only<br><span class="text-ttg16-ink/80">bg-clip-text</span></figcaption>
      </figure>
      <figure class="grid gap-3 rounded-2xl border border-white/8 bg-white/[0.025] p-5">
        <span class="w-fit bg-linear-to-r from-ttg16-a via-ttg16-b to-ttg16-c bg-clip-text font-ttg16-display text-6xl text-transparent [-webkit-text-stroke:3px_var(--color-ttg16-stroke)] [paint-order:stroke_fill]">Type</span>
        <figcaption class="font-ttg16-mono text-[11.5px] text-ttg16-mut">Stroke + fill<br><span class="text-ttg16-ink/80">[paint-order:stroke_fill]</span></figcaption>
      </figure>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-ttg16-bg: #04070b;
  --color-ttg16-bg: oklch(0.13 0.012 250);
  --color-ttg16-ink: #eef3f8;
  --color-ttg16-ink: oklch(0.96 0.008 245);
  --color-ttg16-mut: #98a3b3;
  --color-ttg16-mut: oklch(0.72 0.025 255);
  --color-ttg16-stroke: #ffffff59;
  --color-ttg16-stroke: oklch(1 0 0 / 0.35);
  --color-ttg16-a: #38bdf8;
  --color-ttg16-a: oklch(0.77 0.14 230);
  --color-ttg16-b: #8b8ff7;
  --color-ttg16-b: oklch(0.68 0.15 280);
  --color-ttg16-c: #ea7cf8;
  --color-ttg16-c: oklch(0.75 0.19 320);
  --font-ttg16-display: 'Archivo Black', system-ui, sans-serif;
  --font-ttg16-sans: 'Geist', system-ui, sans-serif;
  --font-ttg16-mono: 'Geist Mono', ui-monospace, monospace;
}

.ttg-16 {
  width: 100%;
  min-height: 100svh;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-ttg16-bg: #04070b;
  --color-ttg16-bg: oklch(0.13 0.012 250);
  --color-ttg16-ink: #eef3f8;
  --color-ttg16-ink: oklch(0.96 0.008 245);
  --color-ttg16-mut: #98a3b3;
  --color-ttg16-mut: oklch(0.72 0.025 255);
  --color-ttg16-stroke: #ffffff59;
  --color-ttg16-stroke: oklch(1 0 0 / 0.35);
  --color-ttg16-a: #38bdf8;
  --color-ttg16-a: oklch(0.77 0.14 230);
  --color-ttg16-b: #8b8ff7;
  --color-ttg16-b: oklch(0.68 0.15 280);
  --color-ttg16-c: #ea7cf8;
  --color-ttg16-c: oklch(0.75 0.19 320);
  --font-ttg16-display: 'Archivo Black', system-ui, sans-serif;
  --font-ttg16-sans: 'Geist', system-ui, sans-serif;
  --font-ttg16-mono: 'Geist Mono', ui-monospace, monospace;
}

.ttg-16 {
  width: 100%;
  min-height: 100svh;
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Geist:wght@400..600&family=Geist+Mono:wght@500&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • The original declared -webkit-text-stroke twice on one rule (the first was dead); the port has one arbitrary property per element.
  • UI upgrade: the hero word is now genuinely hollow and fills with gradient on hover via an animated background-size wipe inside the stroke.
  • UI upgrade: specimens are labelled <figure> tiles that print the exact utilities; Archivo Black gives strokes enough mass; muted labels rose from 2.6:1 to 7.4:1.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Gradient Text with Outline Stroke
Source: https://codefronts.com/motion/tailwind-css-text-gradient/tailwind-gradient-text-with-outline-stroke/

Stroke-only, fill-only and stroke-plus-fill side by side, plus a hollow display word whose outline fills with gradient on hover. Tailwind has no stroke-text utility, so [-webkit-text-stroke:2px_…] and [paint-order:stroke_fill] arbitrary properties do the work &mdash; right in the class list.
## HTML
```html
<section class="ttg-16 w-full min-h-svh grid place-items-center bg-ttg16-bg px-5 py-16 font-ttg16-sans text-ttg16-ink" aria-label="Gradient text with outline stroke demo">
  <div class="w-full max-w-3xl">
    <p class="font-ttg16-mono text-[11px] tracking-[0.16em] text-ttg16-mut uppercase">Stroke + gradient fill · hover the outline</p>
    <h1 class="mt-4 w-fit cursor-default bg-linear-to-r from-ttg16-a via-ttg16-b to-ttg16-c bg-[length:0%_100%] bg-no-repeat bg-clip-text font-ttg16-display text-[clamp(3.5rem,13vw,7.5rem)] leading-[0.95] tracking-[0.02em] text-transparent transition-[background-size] duration-700 ease-out [-webkit-text-stroke:2px_var(--color-ttg16-a)] hover:bg-[length:100%_100%] motion-reduce:transition-none">OUTLINE</h1>
    <h2 class="mt-3 w-fit bg-linear-to-r from-ttg16-a via-ttg16-b to-ttg16-c bg-clip-text text-[clamp(1.6rem,5vw,2.8rem)] font-bold tracking-[-0.02em] text-transparent [-webkit-text-stroke:1px_var(--color-ttg16-stroke)] [paint-order:stroke_fill]">Gradient fill + stroke</h2>
    <div class="mt-10 grid gap-4 sm:grid-cols-3">
      <figure class="grid gap-3 rounded-2xl border border-white/8 bg-white/[0.025] p-5">
        <span class="font-ttg16-display text-6xl text-transparent [-webkit-text-stroke:2px_var(--color-ttg16-a)]">Type</span>
        <figcaption class="font-ttg16-mono text-[11.5px] text-ttg16-mut">Stroke only<br><span class="text-ttg16-ink/80">text-transparent + stroke</span></figcaption>
      </figure>
      <figure class="grid gap-3 rounded-2xl border border-white/8 bg-white/[0.025] p-5">
        <span class="w-fit bg-linear-to-r from-ttg16-b to-ttg16-c bg-clip-text font-ttg16-display text-6xl text-transparent">Type</span>
        <figcaption class="font-ttg16-mono text-[11.5px] text-ttg16-mut">Fill only<br><span class="text-ttg16-ink/80">bg-clip-text</span></figcaption>
      </figure>
      <figure class="grid gap-3 rounded-2xl border border-white/8 bg-white/[0.025] p-5">
        <span class="w-fit bg-linear-to-r from-ttg16-a via-ttg16-b to-ttg16-c bg-clip-text font-ttg16-display text-6xl text-transparent [-webkit-text-stroke:3px_var(--color-ttg16-stroke)] [paint-order:stroke_fill]">Type</span>
        <figcaption class="font-ttg16-mono text-[11.5px] text-ttg16-mut">Stroke + fill<br><span class="text-ttg16-ink/80">[paint-order:stroke_fill]</span></figcaption>
      </figure>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-ttg16-bg: #04070b;
  --color-ttg16-bg: oklch(0.13 0.012 250);
  --color-ttg16-ink: #eef3f8;
  --color-ttg16-ink: oklch(0.96 0.008 245);
  --color-ttg16-mut: #98a3b3;
  --color-ttg16-mut: oklch(0.72 0.025 255);
  --color-ttg16-stroke: #ffffff59;
  --color-ttg16-stroke: oklch(1 0 0 / 0.35);
  --color-ttg16-a: #38bdf8;
  --color-ttg16-a: oklch(0.77 0.14 230);
  --color-ttg16-b: #8b8ff7;
  --color-ttg16-b: oklch(0.68 0.15 280);
  --color-ttg16-c: #ea7cf8;
  --color-ttg16-c: oklch(0.75 0.19 320);
  --font-ttg16-display: 'Archivo Black', system-ui, sans-serif;
  --font-ttg16-sans: 'Geist', system-ui, sans-serif;
  --font-ttg16-mono: 'Geist Mono', ui-monospace, monospace;
}

.ttg-16 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

Arbitrary properties fill the gaps. Any CSS declaration can be a class with square brackets; underscores become spaces:

<span class="bg-linear-to-r from-ttg16-a via-ttg16-b to-ttg16-c bg-clip-text text-transparent
  [-webkit-text-stroke:1.5px_rgb(255_255_255/0.35)] [paint-order:stroke_fill]">Type</span>

Paint order matters. By default the stroke paints over the fill and eats half its width into each glyph. paint-order: stroke fill draws the stroke first, so the gradient fill sits on top and only the outer half of the stroke shows.

The hollow word starts as stroke-only (gradient layer at 0% width) and the gradient wipes in on hover, inside the outline.

Make it yours

  • Display sizes above 6rem: use 3px strokes so the outline holds at distance.
  • Crisp white outline on light pages: [-webkit-text-stroke:1px_white] with a dark gradient fill.
  • Hard 3D shadow: add drop-shadow-[3px_3px_0_var(--color-ttg16-c)] to the stroked word.
  • Thicker hollow look: swap Archivo Black for a condensed heavy face.

Gotchas — read before shipping

  • -webkit-text-stroke is centred on the glyph edge; above 3px it closes counters in 'e' and 'a' unless you use paint-order.
  • Despite the prefix, -webkit-text-stroke works in Firefox and Safari — it's the standardised name.
  • Strokes on variable fonts can show overlap seams at contour joins; static heavy cuts are cleaner.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

paint-order on HTML text: Chrome 123+, Safari 11+, Firefox 60+; older Chrome paints the stroke over the fill.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…