Kinetic Typography

Glyph Portal Layout

A layout skeleton built from giant letters used as mask windows, each holding its own image or video that moves with parallax on scroll, with small type flowing around them.

This is a WebberUI Pro component

The install command below needs a licence to run; without one it returns 401.

How to install Pro components →See the plans →

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add "https://webberui.com/r/glyph-portal-layout.en.json?t=<install token>"

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

56
6
Active edited usageDefault props
<GlyphPortalLayout/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add "https://webberui.com/r/glyph-portal-layout.en.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/glyph-portal-layout.

Usage

GlyphPortalLayout provides the shared scroll progress and mask configuration; inside it you place any number of GlyphPortals (glyph windows) and GlyphProse blocks (the small type that flows around them). Each GlyphPortal uses one giant letter as its mask, and the image embedded in the letterform moves with parallax inside the glyph as you scroll.

import {
  GlyphPortal,
  GlyphPortalLayout,
  GlyphProse,
} from "@/components/ui/glyph-portal-layout";

<GlyphPortalLayout columns={2} intensity={64}>
  <GlyphProse span={2}>The giant letters are the layout skeleton.</GlyphProse>

  <GlyphPortal glyph="T" src="/media/dawn.jpg" alt="Dawn" depth={1.4} />
  <GlyphPortal glyph="Y" src="/media/flame.mp4" alt="Flame" depth={0.8} />
  <GlyphPortal glyph="P" src="/media/moss.jpg" alt="Moss" depth={1.2} />
  <GlyphPortal glyph="E" src="/media/night.jpg" alt="Night" depth={0.9} />

  <GlyphProse span={2}>Small type flows around them, forming a composition where the letterforms are the lead.</GlyphProse>
</GlyphPortalLayout>;

If the scrolling happens inside a nested overflow container, pass that container's ref to GlyphPortalLayout's container:

const scrollerRef = React.useRef<HTMLDivElement>(null);

<div ref={scrollerRef} className="h-[300px] overflow-y-auto">
  <GlyphPortalLayout container={scrollerRef}>{/* … */}</GlyphPortalLayout>
</div>;

When src has an extension like .mp4 or .webm, it is presented as a <video> automatically; you can also specify it explicitly with media="video".

GlyphPortalLayout Props

PropTypeDefaultDescription
childrenReact.ReactNode—Put GlyphPortals and any text content here
intensitynumber56Parallax travel strength (px): the maximum displacement of the image within the glyph
fontFamilystringa bold system font stackMask font stack (rasterized as a system font inside the CSS mask)
fontWeightnumber | string900Mask font weight
columnsnumber6Number of grid columns
containerRefObject<HTMLElement | null>—Nested scroll container ref; defaults to the window as the scroll container
classNamestring—Additional styles

GlyphPortal Props

PropTypeDefaultDescription
glyphstring—The character used as the window mask (usually a single letter, digit, or Han character)
srcstring—Image or video source URL
media"image" | "video"detected from the extensionMedia type
posterstring—Video poster image (when media is video)
altstring—Accessible alternative text; when omitted, this window is treated as decorative
mode"fill" | "knockout""fill"fill puts the image inside the letterform; knockout knocks the letterform out of the image
depthnumber1Parallax depth multiplier, used to create layering
direction1 | -11Parallax direction
aspectstring"3 / 4"Aspect ratio of a single cell (the glyph is undistorted when it matches the mask's ratio)
spannumber—Grid column span
classNamestring—Additional styles
childrenReact.ReactNode—Content layered over the glyph window that does not intercept the pointer

GlyphProse Props

PropTypeDefaultDescription
childrenReact.ReactNode—The small type flowing between the glyph skeleton
spannumber—Grid column span
classNamestring—Additional styles

How it works

  • The mask is brought in through CSS mask-image as an inline SVG data-URI, with the glyph rasterized in a system font, so no font files or global CSS need to be configured in your project.
  • Because the SVG inside the mask cannot see the page's @font-face, keep fontFamily at its default or point it at a widely available heavy font.
  • The mask uses mask-size: 100% 100% to fit a single cell; when aspect matches the mask's built-in ratio (3 / 4) the glyph is undistorted, and deliberately changing the ratio produces condensed or extended letterform effects.
  • Each GlyphPortal computes its scroll progress from its own position, and depth / direction desynchronize each glyph's parallax to create layering; the image overscans beyond its cell so the edges never show through at any displacement.

Accessibility

  • When the user has "reduce motion" enabled at the system level, the parallax displacement and video autoplay are disabled and the glyph windows keep a static layout.
  • A window with alt is announced to assistive technology through role="img" and aria-label; without alt, the whole window is marked aria-hidden and the image inside is hidden from assistive technology too.
  • Overlay content (children) is presented with pointer-events-none and does not intercept the interaction beneath it.