Motion
Token-driven entrance, stagger, hover and marquee utilities that switch off under reduced motion.
Wraps: tailwindcss, @utility, @theme
Children of data-stagger enter one after another.
Reduced motion shows everything at once.
Use it
import { Card } from "@zedtech/zedkit-ui";
<Card className="animate-fade-up stagger-1">First</Card>
<Card className="animate-fade-up stagger-2">Second</Card>Hover feedback
<Card className="hover-lift">Lifts on hover (devices that hover)</Card>
<Button className="hover-press">Presses down on click</Button>Staggered children
Write the launch email
Review the product photos
Publish the sale banner
// Children enter one after another; set "pending" first to hide them
// until they scroll into view, then switch to "in".
<Stack data-stagger="in">
<Text>One</Text>
<Text>Two</Text>
</Stack>Kit not installed yet?
This ships on npm only; there is no copy-source brick for it.
Install
Terminal pnpm add @zedtech/zedkit-ui @fontsource-variable/inter @fontsource-variable/space-groteskLoad the styles
Tailwind does not scan node_modules, so the @source line points it at the kit's class names. The path is relative to this file.
src/app/globals.cssReplace the contents@import "tailwindcss"; @import "@zedtech/zedkit-ui/styles.css"; @source "../../node_modules/@zedtech/zedkit-ui/src/**/*.{ts,tsx}";Pick a look
Set data-identity="glass" on <html> so the finished app matches the site. Fresh app: replace src/app/layout.tsx. Existing app: merge, and keep ThemeProvider and other providers.
src/app/layout.tsxReplace the contentsimport "./globals.css"; import "./fonts"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en" data-identity="glass"> <body>{children}</body> </html> ); }Load the fonts
The Glass look uses Inter and Space Grotesk; these imports self-host them.
src/app/fonts.tsCreate the fileimport "@fontsource-variable/inter"; import "@fontsource-variable/space-grotesk";Import and render
src/app/page.tsxReplace the contentsimport { Card } from "@zedtech/zedkit-ui"; export default function Page() { return ( <main className="px-page-gutter py-section-sm"> <Card className="animate-fade-up stagger-1">First</Card> <Card className="animate-fade-up stagger-2">Second</Card> </main> ); }
Package version 2.0.1.
Accessibility
Every utility and attribute is switched off under prefers-reduced-motion: reduce, with content left visible and in place. Hover lift only applies on devices that hover, so it never sticks after a tap.
Troubleshooting
Nothing animates — check the OS reduced-motion setting first. Content stays hidden — `data-reveal="pending"` and `data-stagger="pending"` hide content until your code sets them to `"in"`. Stagger has no delay — `stagger-*` delays an animation class (`animate-fade-up stagger-2`), not a transition. Parallax travel follows `--zk-motion-intensity` (default 1, `0` = no shift) times `--zk-motion-mobile` (`0.5` under 40rem); override on a subtree (`.hero { --zk-motion-intensity: 0.3 }`), not as an identity token. Derived amplitude is `min(calc(var(--zk-reveal-distance) * 8), var(--zk-parallax-max-shift, 120px))` (x-axis cap `12vw`). Sticky offset is `--zk-pin-top` (`calc(var(--zk-header-height) + 1rem)`).
Tokens that drive motion
Every utility reads CSS custom properties with a fallback, so a page or a future identity can change how things move without touching a component.
| Token | Default | Used by |
|---|---|---|
--zk-reveal-duration | 300ms | animate-fade-up, animate-fade-in, animate-pop-in, data-reveal, data-stagger, data-animate |
--zk-reveal-distance | 0.75rem | upward travel of fade-up and reveal |
--zk-reveal-ease | cubic-bezier(0.22, 1, 0.36, 1) | entrance easing |
--zk-stagger-step | 70ms | stagger-*, data-reveal-delay, data-stagger children |
--zk-lift-duration | 200ms | hover-lift, hover-press |
--zk-lift-translate | 0 -2px | hover-lift offset |
--zk-press-translate | 0 1px | hover-press offset |
--zk-marquee-duration | 40s | marquee-track loop |
:root {
--zk-reveal-duration: 400ms; /* slower entrances across the app */
--zk-stagger-step: 90ms;
}Reveal on scroll
Prefer data-animate / <Animate> (see Reveal and react with motion on
any element): the kit observer writes data-animate-state. The older
data-reveal / data-stagger attributes still style pending → in if
you drive them yourself.
Write the launch email
Review the product photos
Publish the sale banner
import { Animate, Stack, Text } from "@zedtech/zedkit-ui";
<Animate on={{ appear: "fade-up" }} stagger>
<Stack>
<Text>One</Text>
<Text>Two</Text>
</Stack>
</Animate>Marquee
marquee-track scrolls its children in a loop. Render the content twice in
the track so the -50% translate wraps seamlessly, and keep the duplicate out
of the accessibility tree with aria-hidden.