Skip to content

Reveal

Wrap a section, a card or a row and it fades — and rises, or grows — into place the first time the reader gets to it. Content that is already on screen when the page loads plays the same entrance straight away, so the component reads the same whether it is the hero or the fourth section down. It plays once; repeat is there for the cases where it should not.

reveal/basic.tsx
One less thing to wire up
Wrap anything. It fades and rises into place when the reader gets to it, once, and then stays out of the way.

It is a wrapper and nothing else: no scroll listener, no animation library, no bundle. One IntersectionObserver per element decides when, and the entrance itself is two states and one CSS transition.

Import

import { Reveal } from "@forte-ui/react";

Content is never gated behind the entrance

Nothing is hidden until the component has mounted in a browser and an observer is watching. A page whose JavaScript fails, or has not arrived yet, shows the content rather than an empty column; a crawler reads the markup it was served; a screen reader, which never depended on any of this, reads it either way. Printing is covered too — paper does not scroll, so anything still waiting for its entrance prints at rest rather than as a blank page.

Examples

Effects

Seven entrances. fade moves nothing; the four directional ones fade while travelling --forte-reveal-travel; scale grows into place without fading, which is the one to reach for over text that should stay readable the whole way in; fade-scale does both.

fade-start and fade-end are named for the reading direction rather than for a side — fade-start arrives from the left of this page and from the right of an Arabic one. Flip the frame to RTL and watch the pair swap.

reveal/effects.tsx
fade
fade-up
fade-down
fade-start
fade-end
scale
fade-scale

Staggering a list

There is no group component and no orchestration: delay is a per-element wait, so a mapped list staggers with delay={i * 70}. Each row still starts its own clock when it reaches the screen, which is what keeps a long list from dealing out its last rows to an empty viewport.

reveal/stagger.tsx
  1. Install

    pnpm add @forte-ui/react

  2. Import the styles

    Once, in the root layout

  3. Pick a seed

    Every color derives from it

  4. Ship

    No runtime theming layer

The rows above are <li>s, not wrappers around <li>s. render replaces the element the component draws, so the entrance is applied to the list item itself — the same prop every Base UI-backed component in the library takes, and the answer wherever a <div> would be invalid or would break a grid:

<Reveal render={<li />}>…</Reveal>
<Reveal render={<section />}>…</Reveal>
<Reveal render={<Card.Root />}>…</Reveal>

Playing again

Off by default, because the honest reading of a second entrance is "this is new", and content the reader has already scrolled past is not. Turn repeat on for a demo of the effect itself, or for a panel whose contents genuinely change while it is away.

reveal/repeat.tsx
Scroll this panel
Each row enters as it arrives
and resets once it is fully out
so scrolling back plays it again
which is what repeat is for
a demo of the effect itself
or a panel whose contents change
while nobody is looking at it

The reset is a cut rather than a reverse, and it happens only once the content is entirely off screen, where there is nothing to watch. It is also skipped while focus is inside, so a keyboard user is never left standing on something that has faded out underneath them.

How much has to be on screen

amount is the fraction of the content that has to be visible before it counts, from 0 — any sliver, the default — to 1, all of it. The default starts the entrance as the top edge crosses into view, which is what makes it read as the content arriving with the scroll.

<Reveal amount={0.6}>…</Reveal>

Anything too big to fit on screen is exempt and plays as soon as it appears: a section two screens tall can never be 60% visible, and a component that waited for it would leave that section invisible on a phone and look fine on the desk it was written at.

Theming

reveal/theming.tsx
travel 3rem
240ms, no spring
scale from 0.75

Reduced motion

Nothing travels and nothing scales — the geometry tokens behind the knobs are already 0px and 1 there, and the two gates in the component's CSS carry the same collapse over to a distance or a size you set by hand. What is left is the fade, on a duration the token has already shortened: reduced, not removed, so the content still reads as arriving rather than as having always been there.

API

Props for Reveal
PropTypeDefaultDescription
amountnumber0How much of the content has to be on screen before it counts, from 0 — any sliver of it — to 1, all of it. The default starts the entrance as the top edge crosses into view, which is what makes it read as the content arriving with the scroll rather than as something that happened before the reader got there. Anything too big to fit on screen is exempt and starts as soon as it appears, since it could never satisfy the fraction.
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.
delaynumber0Milliseconds to wait after the content is on screen before it starts. Sets `--forte-reveal-delay`, and exists as a prop because the reason to reach for it is nearly always a stagger: `delay={i * 60}` over a mapped list. Keep the total under a few hundred milliseconds — a list whose last row arrives a second late reads as a page still loading.
effectRevealEffectfade-upWhich entrance plays. - `fade` — opacity alone. The only one that moves nothing, so the only one that cannot widen a page (see the note on the inline effects). - `fade-up` / `fade-down` — fades while rising from below, or settling from above. - `fade-start` / `fade-end` — the same along the inline axis, so `fade-start` arrives from the left in LTR and from the right in RTL. Named for the reading direction rather than for a side because that is what makes them survive `dir="rtl"` unchanged. - `scale` — grows into place with no fade, for something that should stay legible the whole way in. - `fade-scale` — both: the "pop" used for a card or a modal-like panel. The distance and the starting size are knobs, not values baked into the effect — `--forte-reveal-travel` and `--forte-reveal-scale`.
renderRenderProp<Record<string, unknown>>Replaces the rendered `<div>` with another element or component — `render={<li />}` inside a list, `render={<section />}` for a landmark, `render={<Card.Root />}` to animate the card itself rather than a wrapper around it. The entrance is applied to whatever element comes back, so nothing about the layout changes.
repeatbooleanfalsePlay the entrance again every time the content comes back on screen, instead of once and never again. Off by default, because the honest reading of a second entrance is "this content is new", and content the reader has already scrolled past is not. Turn it on for a demo of the effect itself, or for a panel whose contents genuinely change while it is away. The reset is a cut, not a reverse: it happens once the content is entirely off screen, where there is nothing to see. It is also skipped while focus is inside, so a keyboard user cannot be left on an element that has faded out underneath them.

Theming

Theming tokens for Reveal
PropertyControlsDefault
--forte-reveal-travelHow far the content travels before it settles, for the four directional effects.var(--forte-travel-lg)
--forte-reveal-scaleThe size the content starts at, for scale and fade-scale. Below 1 it grows into place; above 1 it settles down into it.var(--forte-scale-enter)
--forte-reveal-durationHow long the entrance takes.var(--forte-duration-spring-gentle)
--forte-reveal-easeThe curve it runs on.var(--forte-ease-spring-gentle)
--forte-reveal-delayHow long to wait once the content is on screen. Set by the delay prop, which is how a mapped list staggers.0s