Comic Book Halftone Headline Text

A loud sale banner in comic-book style. The word Boom is set in heavy slanted capitals with a thick black outline, a hard offset shadow and a yellow fill covered in red halftone dots. It pops in on a bouncy spring and then rocks gently. A second line in white with blue dots follows, above three boxed offers and a button, all on a blue dotted background with a slow sunburst. On phones the lettering scales down and the offers stack.

Code

// Adapted from Magic UI (https://magicui.design/docs/components/comic-text)
// Copyright (c) Magic UI. MIT License: https://we.inc/components/licenses
import type { CSSProperties } from "react";
import { motion, useReducedMotion } from "framer-motion";

const deals = [
  { tag: "Back issues", line: "Three for the price of two" },
  { tag: "Graphic novels", line: "A quarter off every shelf" },
  { tag: "Kids' corner", line: "Free sketch class at noon" },
];

/**
 * Comic-book lettering: a heavy slanted face with a thick black outline, a
 * hard offset shadow and a halftone dot fill. Everything is sized in em, so
 * set the size with a text class such as `text-[9rem]`.
 */
function ComicText({ children, fill = "#facc15", dots = "#ef4444", delay = 0, className = "" }: { children: string; fill?: string; dots?: string; delay?: number; className?: string }) {
  const reduce = useReducedMotion();
  const style: CSSProperties = {
    lineHeight: 1,
    fontFamily: "Impact, Haettenschweiler, 'Arial Narrow Bold', 'Arial Black', sans-serif",
    fontWeight: 900,
    letterSpacing: "0.02em",
    textTransform: "uppercase",
    WebkitTextStroke: "0.022em #000",
    filter: `drop-shadow(0.06em 0.06em 0 #000) drop-shadow(0.035em 0.035em 0 ${dots})`,
    backgroundColor: fill,
    backgroundImage: `radial-gradient(circle at 1px 1px, ${dots} 1.2px, transparent 0)`,
    backgroundSize: "8px 8px",
    WebkitBackgroundClip: "text",
    backgroundClip: "text",
    WebkitTextFillColor: "transparent",
  };
  return (
    <motion.span
      className={`inline-block select-none px-2 ${className}`}
      style={style}
      initial={reduce ? false : { opacity: 0, scale: 0.4, rotate: -12, skewX: -10 }}
      animate={{ opacity: 1, scale: 1, rotate: 0, skewX: -10 }}
      transition={{ type: "spring", stiffness: 380, damping: 13, delay }}
    >
      {children}
    </motion.span>
  );
}

export default function ComicBookHalftoneHeadlineText() {
  return (
    <section className="relative overflow-hidden bg-blue-600 px-5 py-16 sm:px-6 sm:py-20">
      <style>{`
        @keyframes comic-book-halftone-headline-text-wobble {
          0%, 100% { transform: rotate(-2deg) scale(1); }
          50% { transform: rotate(2deg) scale(1.04); }
        }
        @keyframes comic-book-halftone-headline-text-spin {
          to { transform: rotate(360deg); }
        }
        .comic-book-halftone-headline-text-wobble { animation: comic-book-halftone-headline-text-wobble 2.4s ease-in-out infinite; }
        .comic-book-halftone-headline-text-spin { animation: comic-book-halftone-headline-text-spin 60s linear infinite; }
        @media (prefers-reduced-motion: reduce) {
          .comic-book-halftone-headline-text-wobble,
          .comic-book-halftone-headline-text-spin { animation: none; }
        }
      `}</style>
      {/* Halftone dots and a slow sunburst behind everything. */}
      <span aria-hidden="true" className="pointer-events-none absolute inset-0 opacity-30 [background-image:radial-gradient(circle_at_2px_2px,#1e3a8a_2px,transparent_0)] [background-size:14px_14px]" />
      <span aria-hidden="true" className="comic-book-halftone-headline-text-spin pointer-events-none absolute left-1/2 top-1/2 -ml-[700px] -mt-[700px] h-[1400px] w-[1400px] opacity-20 [background-image:repeating-conic-gradient(#fde047_0deg_8deg,transparent_8deg_20deg)] [mask-image:radial-gradient(circle,black_10%,transparent_60%)]" />

      <div className="relative mx-auto max-w-4xl text-center">
        <p className="inline-block -rotate-2 border-4 border-black bg-white px-4 py-1.5 text-sm font-black uppercase tracking-wider text-black shadow-[4px_4px_0_#000]">Panel Nine Comics, this Saturday only</p>

        <h2 className="mt-8 flex flex-col items-center gap-3 sm:gap-5">
          <span className="sr-only">Boom! Half price day</span>
          <span aria-hidden="true" className="comic-book-halftone-headline-text-wobble block">
            <ComicText className="text-[5.5rem] sm:text-[9rem]">Boom!</ComicText>
          </span>
          <span aria-hidden="true" className="block">
            <ComicText fill="#ffffff" dots="#38bdf8" delay={0.25} className="text-[2.6rem] sm:text-[4.5rem]">
              Half price day
            </ComicText>
          </span>
        </h2>

        <ul className="mt-12 grid gap-4 text-left sm:grid-cols-3">
          {deals.map((d, i) => (
            <li key={d.tag} className={`border-4 border-black bg-yellow-300 p-4 shadow-[6px_6px_0_#000] ${i === 1 ? "sm:rotate-1" : "sm:-rotate-1"}`}>
              <p className="text-xs font-black uppercase tracking-wider text-red-700">{d.tag}</p>
              <p className="mt-1 text-lg font-extrabold leading-snug text-black">{d.line}</p>
            </li>
          ))}
        </ul>

        <a href="#" className="mt-10 inline-flex h-14 items-center border-4 border-black bg-red-600 px-8 text-lg font-black uppercase tracking-wide text-white shadow-[6px_6px_0_#000] transition-transform hover:-translate-y-1 focus:outline-none focus-visible:ring-4 focus-visible:ring-yellow-300 motion-reduce:transition-none">
          Save my spot in line
        </a>
      </div>
    </section>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/comic-book-halftone-headline-text.json

When to use it

How to customize it

Accessibility

Questions

How is the halftone dot fill made?

The text has a solid background colour plus a tiny repeating radial gradient that draws one dot per 8px tile. `background-clip: text` with a transparent text fill cuts that dotted background to the shape of the letters.

How does the outline work?

`-webkit-text-stroke` draws a black line centred on the edge of each letter. Despite the prefix it is supported in all current browsers. The hard shadow is two `drop-shadow` filters with no blur, one black and one in the dot colour.

Do I need a comic font?

No. The demo uses Impact and similar condensed system faces, which are heavy enough to carry the outline. A real comic face will look closer to print lettering if you already load one.

Why is the text marked aria-hidden?

The styled word is uppercase, skewed and split across lines, which can be read out oddly. A hidden copy of the full headline gives assistive technology a clean sentence instead.

Can I animate it on scroll instead of on load?

Yes. Replace `animate` on the `motion.span` with `whileInView` and add `viewport={{ once: true }}`.

Adapted from Comic Text in Magic UI, used under its open-source licence. Licences and credits

Related: Shimmering Chrome Gradient Headline, Text Scramble Decode Headline, Call-to-Action Banner on a Brand Colour.

More text effects or all free UI components. Start for free to have a full site built around it.

More in Text Effects

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap