Reveal
Overview
The Reveal component animates its content into view as it scrolls into the viewport, backed
by IntersectionObserver. It's a lightweight, CSS-driven wrapper for "fade/slide/zoom in on
scroll" effects on landing pages — no animation runtime dependency required.
Accessibility and progressive enhancement are built in, not opt-in:
- Automatically skips the animation (renders the final, visible state immediately) when the
user's OS requests
prefers-reduced-motion: reduce. - Renders the final, visible state during server-side rendering and on the very first client render, so content is never hidden from crawlers or users if JavaScript never runs.
- Falls back to the visible state immediately in environments without
IntersectionObserver.
Import
import { Reveal } from '@allxsmith/bestax-bulma';
Props
| Prop | Type | Default | Description |
|---|---|---|---|
animation | 'fade' | 'fade-up' | 'fade-down' | 'slide-left' | 'slide-right' | 'zoom' | 'flip' | 'fade-up' | Animation style applied when the element enters the viewport. |
delay | number | 0 | Delay in milliseconds before the animation starts. |
duration | number | 600 | Animation duration in milliseconds. |
threshold | number | 0.15 | Fraction (0-1) of the element that must be visible to trigger the reveal. Clamped to the 0-1 range; non-finite values fall back to 0.15. |
once | boolean | true | Animate only the first time the element enters the viewport. If false, it re-animates on every entry/exit. |
as | React.ElementType | 'div' | Element or component to render as. |
cascade | boolean | false | Stagger direct children with an incrementing delay instead of animating this element as a single block. |
cascadeInterval | number | 80 | Milliseconds added to each successive child's delay when cascade is set. |
children | React.ReactNode | — | Content to reveal. |
className | string | — | Additional CSS classes. |
| ... | All standard HTML and Bulma helper props | (See Helper Props) |
Usage
Basic Reveal
Fades a block up into view as it scrolls into the viewport.
function example() { return ( <Reveal animation="fade-up"> <Box> <Title size="4">Why bestax</Title> <Content>This box fades up into view once it's on screen.</Content> </Box> </Reveal> ); }
Rendering as a different element
Use as to render the wrapper as a different tag or component, e.g. Section.
function example() { return ( <Reveal animation="fade-up" as={Section}> <Title size="3">Why Grass Doctor</Title> <Content>Rendered as a `Section` instead of the default `div`.</Content> </Reveal> ); }
asWhen as is a plain intrinsic tag (e.g. as="section"), your className, style, Bulma
helper classes, and everything else (id, aria-*, data-*, event handlers) all land on that
single rendered element.
When as is a component (e.g. as={Section}), scroll detection needs a real DOM node it
can attach a ref to, which library components like Section/Card don't forward. Reveal
therefore wraps the component in an observed div: your className, style, and helper
classes go on that wrapper div, while the remaining props (id, aria-*, data-*, event
handlers) are forwarded to the inner component. A combined selector like #hero.highlight that
assumes both id and className sit on the same element won't match in that case.
Animation styles
animation accepts 'fade', 'fade-up', 'fade-down', 'slide-left', 'slide-right',
'zoom', and 'flip'.
function example() { return ( <Columns isMultiline> <Column size="one-third"> <Reveal animation="zoom"> <Box> <Title size="5">zoom</Title> </Box> </Reveal> </Column> <Column size="one-third"> <Reveal animation="flip"> <Box> <Title size="5">flip</Title> </Box> </Reveal> </Column> <Column size="one-third"> <Reveal animation="slide-left"> <Box> <Title size="5">slide-left</Title> </Box> </Reveal> </Column> </Columns> ); }
Staggered (cascade) children
Set cascade to stagger direct children of Reveal with an incrementing
transitionDelay (cascadeInterval milliseconds apart), rather than animating the wrapper as a
single block. Pass the individual items to stagger (e.g. Cards) directly as children, rather
than a single nested layout component.
function example() { return ( <Reveal animation="fade-up" cascade cascadeInterval={100} style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }} > {['Fast', 'Accessible', 'Themeable'].map(feature => ( <Card key={feature} style={{ flex: '1 1 160px' }}> <Card.Content> <Title size="5">{feature}</Title> </Card.Content> </Card> ))} </Reveal> ); }
Re-animating on every entry
By default, Reveal only animates the first time it enters the viewport (once). Set
once={false} to have it re-animate every time it scrolls in and out of view.
function example() { return ( <Reveal animation="fade" once={false}> <Notification color="info"> Scroll me out of view and back in to see this animate again. </Notification> </Reveal> ); }
Accessibility
Revealnever removes content from the accessibility tree — it only animatesopacityandtransform, so screen reader users always have access to the content regardless of scroll position.- When the user's OS is set to reduce motion (
prefers-reduced-motion: reduce),Revealskips the animation entirely and renders the final, visible state immediately. - During server-side rendering and the first client render (before hydration effects run),
Revealrenders the final, visible state — content is never hidden if JavaScript fails to load, ensuring crawlers and no-JS visitors always see the full page.