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.
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.
<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
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | Put GlyphPortals and any text content here |
intensity | number | 56 | Parallax travel strength (px): the maximum displacement of the image within the glyph |
fontFamily | string | a bold system font stack | Mask font stack (rasterized as a system font inside the CSS mask) |
fontWeight | number | string | 900 | Mask font weight |
columns | number | 6 | Number of grid columns |
container | RefObject<HTMLElement | null> | — | Nested scroll container ref; defaults to the window as the scroll container |
className | string | — | Additional styles |
GlyphPortal Props
| Prop | Type | Default | Description |
|---|---|---|---|
glyph | string | — | The character used as the window mask (usually a single letter, digit, or Han character) |
src | string | — | Image or video source URL |
media | "image" | "video" | detected from the extension | Media type |
poster | string | — | Video poster image (when media is video) |
alt | string | — | 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 |
depth | number | 1 | Parallax depth multiplier, used to create layering |
direction | 1 | -1 | 1 | Parallax direction |
aspect | string | "3 / 4" | Aspect ratio of a single cell (the glyph is undistorted when it matches the mask's ratio) |
span | number | — | Grid column span |
className | string | — | Additional styles |
children | React.ReactNode | — | Content layered over the glyph window that does not intercept the pointer |
GlyphProse Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | The small type flowing between the glyph skeleton |
span | number | — | Grid column span |
className | string | — | Additional styles |
How it works
- The mask is brought in through CSS
mask-imageas 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, keepfontFamilyat its default or point it at a widely available heavy font. - The mask uses
mask-size: 100% 100%to fit a single cell; whenaspectmatches the mask's built-in ratio (3 / 4) the glyph is undistorted, and deliberately changing the ratio produces condensed or extended letterform effects. - Each
GlyphPortalcomputes its scroll progress from its own position, anddepth/directiondesynchronize 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
altis announced to assistive technology throughrole="img"andaria-label; withoutalt, the whole window is markedaria-hiddenand the image inside is hidden from assistive technology too. - Overlay content (
children) is presented withpointer-events-noneand does not intercept the interaction beneath it.
Type Specimen Stage
A variable-font showcase in the style of a foundry specimen page — a giant glyph, a weight ladder, and a size ramp — where dragging an axis slider makes the whole specimen tween and reflow in sync with Motion.
Alphabet Ledger
An alphabetical index scroll — a poster-scale initial letter is the sticky anchor of each section, and at the handover the new letter pushes the previous one out with a thin-to-bold weight baton animation.