WebberUI

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

Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.

How to install Pro components →See the plans →

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

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
<GlyphPortalLayout />

Installation

npx shadcn@latest add "https://webberui.com/r/glyph-portal-layout.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.ReactNodePut 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
classNamestringAdditional styles

GlyphPortal Props

PropTypeDefaultDescription
glyphstringThe character used as the window mask (usually a single letter, digit, or Han character)
srcstringImage or video source URL
media"image" | "video"detected from the extensionMedia type
posterstringVideo poster image (when media is video)
altstringAccessible 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)
spannumberGrid column span
classNamestringAdditional styles
childrenReact.ReactNodeContent layered over the glyph window that does not intercept the pointer

GlyphProse Props

PropTypeDefaultDescription
childrenReact.ReactNodeThe small type flowing between the glyph skeleton
spannumberGrid column span
classNamestringAdditional 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.

On this page