Animation
Reveal animations, aos / animates-on-scroll, animates-once, animate-disable and child-reveal control.
On this page
Systhema's animation utilities fade content in as it appears: seven fade animations, the aos class that holds an element until it scrolls into view, and the classes that decide which elements in a tree reveal on their own.
Quick referenceLink to this section
| Class | Styles |
|---|---|
animate-fadein | opacity: 0;animation: fadeIn 1s cubic-bezier(0.33, 1, 0.68, 1) forwards; |
animate-fadeinzoomout | opacity: 0;animation: fadeInZoomOut 1s cubic-bezier(0.33, 1, 0.68, 1) forwards; |
animate-fadeinup | opacity: 0;animation: fadeInUp 1s cubic-bezier(0.33, 1, 0.68, 1) forwards; |
animate-fadeindown | opacity: 0;animation: fadeInDown 1s cubic-bezier(0.33, 1, 0.68, 1) forwards; |
animate-fadeinleft | opacity: 0;animation: fadeInLeft 1s cubic-bezier(0.33, 1, 0.68, 1) forwards; |
animate-fadeinright | opacity: 0;animation: fadeInRight 1s cubic-bezier(0.33, 1, 0.68, 1) forwards; |
animate-fadeout | opacity: 0;animation: fadeOut 1s cubic-bezier(0.33, 1, 0.68, 1) forwards; |
animates-once | animation-iteration-count: 1; |
animate-disable | animation: none !important; |
animates-on-scroll | &:not(.animated) { animation: none !important; }&:not(.animated):not(.animate-disable) { opacity: 0 !important; animation: none !important; }@media (prefers-reduced-motion: reduce) { & { opacity: 1 !important; animation: none !important; transform: none !important; } }@media (prefers-reduced-motion: reduce) { &:not(.animated):not(.animate-disable) { opacity: 1 !important; animation: none !important; transform: none !important; } } |
aos | &:not(.animated) { animation: none !important; }&:not(.animated):not(.animate-disable) { opacity: 0 !important; animation: none !important; }@media (prefers-reduced-motion: reduce) { & { opacity: 1 !important; animation: none !important; transform: none !important; } }@media (prefers-reduced-motion: reduce) { &:not(.animated):not(.animate-disable) { opacity: 1 !important; animation: none !important; transform: none !important; } } |
aos-disable-children | & .aos:is(.animated), .aos-disable-children .aos:not(:is(.animated)), .aos-disable-children .animates-on-scroll:is(.animated), .aos-disable-children .animates-on-scroll:not(:is(.animated)) { opacity: 1 !important; animation: none !important; } |
Basic usageLink to this section
An animate-fade* class plays its animation once, as soon as the element renders: one second, ease-out-cubic, and it stays on its last frame. Press Replay to run them again.
import { useState } from 'react'
import { Button } from '@systhemaui/next'
export default function Demo() {
const [run, setRun] = useState(0)
return (
<div className="flex w-full flex-col items-center gap-6">
<div key={run} className="grid w-full grid-cols-2 gap-3 md:grid-cols-4">
<Tile label="fadein" className="animate-fadein" />
<Tile label="fadeinzoomout" className="animate-fadeinzoomout" />
<Tile label="fadeinup" className="animate-fadeinup" />
<Tile label="fadeindown" className="animate-fadeindown" />
<Tile label="fadeinleft" className="animate-fadeinleft" />
<Tile label="fadeinright" className="animate-fadeinright" />
<Tile label="fadeout" className="animate-fadeout" />
</div>
<Button.button variant="secondary" onClick={() => setRun(run + 1)}>
Replay
</Button.button>
</div>
)
}
function Tile({ label, className }: { label: string; className: string }) {
return (
<figure className="flex flex-col gap-2">
<div className="overflow-hidden rounded-lg border border-dashed border-(--color-foundations-surface-border)">
<div className={`h-16 bg-(--color-foundations-primary-bg) ${className}`} />
</div>
<figcaption className="text-small color-label">{label}</figcaption>
</figure>
)
}<h2 class="animate-fadeinup">Our services</h2>animate-fadeinleft and animate-fadeinright start a full element width to the side, so give their container overflow: hidden (or overflow-x-clip on the section) if the slide must not show outside it.
Reveal on scrollLink to this section
On its own, a fade plays at render time, which is usually above or below the fold where nobody sees it. Add aos (or the long form animates-on-scroll) and the element stays hidden and still until AosListener adds the animated class as its top edge enters the viewport. Then the fade runs.
import { useState } from 'react'
import { Button } from '@systhemaui/next'
export default function Demo() {
const [run, setRun] = useState(0)
return (
<div className="flex w-full flex-col items-center gap-6">
<div key={run} className="flex w-full flex-col gap-4">
<div className="aos animate-fadeinup rounded-lg bg-(--color-foundations-surface-bg) p-6">
<p className="text-h4 color-heading">Reveals as it scrolls in</p>
<p className="color-body">aos animate-fadeinup</p>
</div>
<div className="aos animate-fadeinup animates-once rounded-lg bg-(--color-foundations-surface-bg) p-6">
<p className="text-h4 color-heading">Reveals once</p>
<p className="color-body">aos animate-fadeinup animates-once</p>
</div>
<div className="aos-disable-children">
<div className="aos animate-fadeinup rounded-lg bg-(--color-foundations-surface-bg) p-6">
<p className="text-h4 color-heading">Shown at once</p>
<p className="color-body">inside aos-disable-children</p>
</div>
</div>
</div>
<Button.button variant="secondary" onClick={() => setRun(run + 1)}>
Replay
</Button.button>
</div>
)
}Scroll this page so the preview leaves the screen and comes back: the first card plays again, the second stays revealed, and the third, inside aos-disable-children, never animates.
aosre-arms an element once it has scrolled well below the fold again, so the reveal replays every time the visitor scrolls back down to it.animates-oncekeeps the element revealed after the first time.animate-disablecancels the animation of one element and keeps the listener from revealing it. It does not undo a fade class's startingopacity: 0, so an element with both stays hidden: to show it without motion, drop the fade class or wrap it inaos-disable-children.- Without the listener, an
aoselement never getsanimatedand stays invisible, so renderAosListeneronce in your root layout. HTML projects get the same engine from the vanilla JS bundle.
The React components add their animation classes from packages.react.animationClasses in systhema.config (for example 'aos animate-fadeinup'), and each takes disableAnimation to opt out. See systhema.config reference.
Child revealsLink to this section
When a container and its children both reveal, the result looks busy and the children may never be seen settling. So an animating stack, card, hero, column, row, footer, accordion, quote, figure or media overlay cancels every .aos / .animates-on-scroll reveal anywhere below it: those descendants get opacity: 1 and no animation, and the container reveals as one unit. The column and row rules apply only while that container animates.
You control this from both sides:
aos-disable-childrendoes the same for any element of yours: everything withaosinside it is shown immediately.aos-allow-childrenon a.stacklets its children keep their own reveals, and<Stack allowChildAnimation>adds it for you. It has no effect outside a.stack, and it does not overrideaos-disable-childrenoranimate-disable.
For every other component, turn the container's animation off (disableAnimation) to let child reveals run.
<ul class="aos-disable-children">
<li class="aos animate-fadeinup">Shown at once, no animation</li>
</ul>End stateLink to this section
The fades run forwards, so their last frame stays applied for the life of the element. That frame ends at transform: none, not at an identity such as translate3d(0, 0, 0) or scale(1): any other transform would turn the element into a containing block for its position: absolute descendants, and an ancestor's overflow: hidden would stop clipping them once the element has revealed. Follow the same rule in your own reveal keyframes.
Reduced motionLink to this section
Under prefers-reduced-motion: reduce, every fade and every aos element snaps to its end state: fully visible, no animation, no transform. This is pure CSS, so it holds whether or not the listener runs, and content never stays at opacity: 0. Turn on reduced motion in your operating system and the previews above show their final frame immediately. See Accessibility utilities.
Responsive and state variantsLink to this section
The animation classes take variants like any utility: animate-fadein md:animate-fadeinleft fades in place on phones and slides in from the side from md up.
CustomizingLink to this section
Delay, duration, easing and stagger are on Animation timing. The fade keyframes themselves are part of Systhema's Tailwind config; define your own @keyframes and an --animate-* theme variable for a new entrance, and add the class next to aos to make it scroll-triggered.