Font Hook

Plain centered hook text with rapid font cycling.

Preview

Open editor

Usage

Font Hook keeps the scene intentionally simple: centered text on a flat background, with the font family swapping every few frames. Use the headline field for the main hook. The optional subtitle field renders as smaller plain text below it when provided.

The Style panel still applies. Background color, text color, and font family can be overridden, and the selected font is inserted into the rotation. There are no gradient layers, accent bars, shadows, or decorative effects.

Props

NameTypeDefault
headlinestring"Stop Scrolling"
subtitlestring""

Composition

ID
FontHook
Resolution
1080×1920
FPS
60
Duration
2.0s

Source

Copy or download the React source — drop it into your own Remotion project. The only runtime dependency is remotion.

"use client";

import { AbsoluteFill, useVideoConfig } from "remotion";
import { type ClipStyle, resolveClipStyle } from "../../clip-style";
import { useDesignFrame } from "../../use-design-frame";
import { useFontReady } from "../../use-font-ready";

export type FontHookProps = {
  headline: string;
  subtitle: string;
  clipStyle?: ClipStyle;
};

type FontStack = {
  family: string;
  weight: number;
};

const FALLBACK_STACK: FontStack = {
  family: "'Bebas Neue', 'Impact', 'Arial Black', sans-serif",
  weight: 400,
};

const DEFAULT_STACKS = [
  FALLBACK_STACK,
  {
    family: "'Anton', 'Arial Black', sans-serif",
    weight: 400,
  },
  {
    family: "'Archivo Black', 'Arial Black', sans-serif",
    weight: 400,
  },
  {
    family: "'Poppins', 'Trebuchet MS', sans-serif",
    weight: 900,
  },
  {
    family: "'Geist', system-ui, sans-serif",
    weight: 700,
  },
  {
    family: "'Geist Mono', ui-monospace, monospace",
    weight: 700,
  },
  {
    family: "Georgia, 'Times New Roman', serif",
    weight: 700,
  },
] as const satisfies readonly FontStack[];

const SEGMENT_FRAMES = 6;

export const FontHook: React.FC<FontHookProps> = ({
  headline = "Stop\nScrolling",
  subtitle = "",
  clipStyle,
}) => {
  const frame = useDesignFrame();
  const { width } = useVideoConfig();

  const s = resolveClipStyle(clipStyle, {
    background: "#ffffff",
    color: "#111827",
    fontFamily: FALLBACK_STACK.family,
    accent: "#111827",
  });

  useFontReady(s.fontFamily);
  useFontReady(FALLBACK_STACK.family);
  useFontReady(DEFAULT_STACKS[1].family);
  useFontReady(DEFAULT_STACKS[2].family);
  useFontReady(DEFAULT_STACKS[3].family);
  useFontReady(DEFAULT_STACKS[4].family);
  useFontReady(DEFAULT_STACKS[5].family);
  useFontReady(DEFAULT_STACKS[6].family);

  const baseKey = fontKey(s.fontFamily);
  const hasBase = DEFAULT_STACKS.some(
    (stack) => fontKey(stack.family) === baseKey,
  );
  const baseStack: FontStack = {
    family: s.fontFamily,
    weight: 700,
  };

  const fontStacks = hasBase ? DEFAULT_STACKS : [baseStack, ...DEFAULT_STACKS];
  const segment = Math.floor(frame / SEGMENT_FRAMES);
  const font = fontStacks[segment % fontStacks.length] ?? FALLBACK_STACK;

  const pad = width * 0.12;
  const headlineSize = Math.min(width * 0.18, 210);
  const subtitleSize = Math.min(width * 0.045, 52);

  return (
    <AbsoluteFill
      style={{
        background: s.background,
        color: s.color,
        fontFamily: s.fontFamily,
      }}
    >
      <AbsoluteFill
        style={{
          alignItems: "center",
          justifyContent: "center",
          textAlign: "center",
          padding: pad,
        }}
      >
        <div
          style={{
            fontSize: headlineSize,
            fontWeight: font.weight,
            fontFamily: font.family,
            letterSpacing: 0,
            lineHeight: 0.95,
            whiteSpace: "pre-line",
          }}
        >
          {headline}
        </div>

        {subtitle.trim() && (
          <div
            style={{
              marginTop: width * 0.033,
              fontSize: subtitleSize,
              fontWeight: 600,
              letterSpacing: 0,
              opacity: 0.7,
            }}
          >
            {subtitle}
          </div>
        )}
      </AbsoluteFill>
    </AbsoluteFill>
  );
};

function primaryFamily(fontFamily: string): string {
  const first = fontFamily.split(",")[0] ?? "";
  return first.replace(/["']/g, "").trim();
}

function fontKey(fontFamily: string): string {
  return primaryFamily(fontFamily).toLowerCase();
}
Save as FontHook/FontHook.tsx