Animate
One typed attribute and wrapper API for identity-token motion, with a tiny SSR-safe runtime.
Wraps: (none)
Use it
import { AnimateObserver, Card } from "@zedtech/zedkit-ui";
<AnimateObserver />
<Card data-animate="fade-up" data-animate-delay="100" data-animate-press="scale-down">
Invoice
</Card>Wrapper with several triggers
Hold to pulse. Scroll to shift.
import { Animate } from "@zedtech/zedkit-ui";
<Animate on={{ appear: "fade-up", press: "scale-down", longpress: "pulse", scroll: "parallax" }}>
Band
</Animate>Stagger children
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>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}"; @source not "../../node_modules/@zedtech/zedkit-ui/src/**/*.test.*";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 { AnimateObserver, Card } from "@zedtech/zedkit-ui"; export default function Page() { return ( <main className="px-page-gutter py-section-sm"> <AnimateObserver /> <Card data-animate="fade-up" data-animate-delay="100" data-animate-press="scale-down"> Invoice </Card> </main> ); }
Package version 2.0.1.
Props
Prop
Type
Show inherited HTML attributes
Standard attributes from the underlying element. See MDN.
Prop
Type
Accessibility
prefers-reduced-motion: reduce leaves content visible and in place. Hover is gated to hover-capable pointers. Focus uses :focus-visible. Press ignores disabled. Longpress is decorative, never the only action. Appear does not set aria-hidden.
Troubleshooting
Nothing appears to move — mount `<AnimateObserver />` once near the app root (or call `observeAnimate()`), then check reduced motion and `data-motion` on `<html>`. Content stays visible without JS by design. Scroll parallax needs `animation-timeline: view()` or the runtime fallback. In-app reduce flashes on load — render `<MotionScript />` in `<head>` (same pattern as `IdentityScript`) so `data-motion="reduce"` is set before paint; `useMotionPreference()` is `"reduce"` on the server.
Triggers and tokens
One element can carry several triggers. Identities retune the same tokens
(--zk-reveal-duration, --zk-reveal-distance, --zk-reveal-ease,
--zk-stagger-step, --zk-lift-*, --zk-press-scale, --zk-longpress-ms).
| Trigger | Attribute | When |
|---|---|---|
| appear | data-animate / data-animate-appear | Enters the viewport once |
| scroll | data-animate-scroll | Progress through the viewport (view() timeline, rAF fallback) |
| press | data-animate-press | :active (not disabled) |
| longpress | data-animate-longpress | Pointer held --zk-longpress-ms |
| hover | data-animate-hover | :hover on hover-capable pointers |
| focus | data-animate-focus | :focus-visible |
Mount <AnimateObserver /> once near the app root so appear, the scroll
fallback, and longpress run. Press, hover and focus are CSS-only.
Reduced motion leaves everything visible and still. The attribute grammar
and trigger semantics live in packages/ui/docs/animate-spec.md.