{"id":30880069,"url":"https://github.com/cloud-shuttle/leptos-motion","last_synced_at":"2026-01-20T17:05:52.477Z","repository":{"id":312415788,"uuid":"1047466997","full_name":"cloud-shuttle/leptos-motion","owner":"cloud-shuttle","description":"🎬 Production-ready animation library for Leptos with complete Motion Studio, WebGL acceleration, and Framer Motion-inspired API. Built with Rust \u0026 WebAssembly for 60fps animations, 3D transforms, path morphing, and spring physics. v0.8.3 with comprehensive ADRs, pnpm support, and near-100% test coverage.","archived":false,"fork":false,"pushed_at":"2025-10-06T13:03:24.000Z","size":261544,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-08T16:26:18.725Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cloud-shuttle.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/roadmaps/IMPLEMENTATION_ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-08-30T13:41:01.000Z","updated_at":"2025-10-28T04:57:25.000Z","dependencies_parsed_at":"2025-08-30T14:14:43.204Z","dependency_job_id":"5286cf31-82a6-4675-9919-3000711ac683","html_url":"https://github.com/cloud-shuttle/leptos-motion","commit_stats":null,"previous_names":["cloud-shuttle/leptos-motion"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/cloud-shuttle/leptos-motion","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloud-shuttle%2Fleptos-motion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloud-shuttle%2Fleptos-motion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloud-shuttle%2Fleptos-motion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloud-shuttle%2Fleptos-motion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cloud-shuttle","download_url":"https://codeload.github.com/cloud-shuttle/leptos-motion/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloud-shuttle%2Fleptos-motion/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28607624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T16:10:39.856Z","status":"ssl_error","status_checked_at":"2026-01-20T16:10:39.493Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-08T06:11:51.116Z","updated_at":"2026-01-20T17:05:48.615Z","avatar_url":"https://github.com/cloud-shuttle.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Leptos Motion 🎬\n\n[![Crates.io](https://img.shields.io/crates/v/leptos-motion)](https://crates.io/crates/leptos-motion)\n[![Documentation](https://img.shields.io/docsrs/leptos-motion)](https://docs.rs/leptos-motion)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Rust](https://img.shields.io/badge/rust-1.70+-blue.svg)](https://www.rust-lang.org)\n\n**High-performance animation library for [Leptos](https://github.com/leptos-rs/leptos) with Framer Motion-inspired API**\n\nLeptos Motion brings smooth, performant animations to your Leptos applications with a familiar API that feels like home for React developers. Built with Rust and WebAssembly for maximum performance.\n\n\u003e **🚀 Stable Release Ready!** Version 0.3.0 is ready for production use.\n\n## 🎉 Stable Release Status\n\n**Version 0.3.0** is now available for production use!\n\n- ✅ **Solid Foundation**: Core animation engine, gestures, layout animations\n- ✅ **Comprehensive Testing**: 100+ tests passing with full coverage\n- ✅ **Type Safety**: Full Rust compile-time guarantees\n- ✅ **Simplified APIs**: Clean, user-friendly interfaces\n- ✅ **All Examples Working**: Advanced features, mobile app, dashboard, e-commerce\n- ✅ **API Compatibility**: Consistent and stable API across all components\n- ✅ **Production Ready**: Optimized for real-world applications\n\n\u003e **Note**: This is a stable release ready for production use.\n\n## ✨ Features\n\n- 🚀 **High Performance**: Built with Rust and WebAssembly for 60fps animations\n- 🎯 **Framer Motion Inspired**: Familiar API for React developers\n- 🎨 **Rich Animation Types**: Spring, tween, and custom easing functions\n- 🖱️ **Gesture Support**: Multi-touch, drag, hover, tap, and scroll animations\n- 📱 **Layout Animations**: FLIP-based smooth transitions when elements change position\n- 🎭 **Presence Animations**: Enter/exit animations with automatic cleanup\n- 🔧 **Type Safe**: Full Rust type safety and compile-time guarantees\n- 🌐 **Cross Platform**: Works in browsers and server-side rendering\n\n## 📦 Installation\n\nAdd to your `Cargo.toml`:\n\n```toml\n[dependencies]\nleptos-motion = \"0.3.0\"\n```\n\n## 🚀 Quick Start\n\n### Basic Animation\n\n```rust\nuse leptos::*;\nuse leptos_motion::*;\n\n#[component]\npub fn AnimatedButton() -\u003e impl IntoView {\n    view! {\n        \u003cMotionDiv\n            class=\"px-4 py-2 rounded-lg text-white font-medium bg-blue-600\".to_string()\n            initial=motion_target!(\n                \"scale\" =\u003e AnimationValue::Number(1.0)\n            )\n            while_hover=motion_target!(\n                \"scale\" =\u003e AnimationValue::Number(1.1)\n            )\n            transition=Transition {\n                duration: Some(0.2),\n                ease: Easing::EaseOut,\n                ..Default::default()\n            }\n        \u003e\n            \"Hover me!\"\n        \u003c/MotionDiv\u003e\n    }\n}\n```\n\n### Spring Animation\n\n```rust\nuse leptos::*;\nuse leptos_motion::*;\n\n#[component]\npub fn SpringBox() -\u003e impl IntoView {\n    let (is_open, set_is_open) = signal(false);\n    \n    view! {\n        \u003cdiv class=\"space-y-4\"\u003e\n            \u003cMotionDiv\n                class=\"bg-blue-500 rounded-lg shadow-lg\".to_string()\n                animate=motion_target!(\n                    \"width\" =\u003e AnimationValue::Pixels(if is_open.get() { 300.0 } else { 100.0 }),\n                    \"height\" =\u003e AnimationValue::Pixels(if is_open.get() { 200.0 } else { 100.0 })\n                )\n                transition=Transition {\n                    duration: Some(0.6),\n                    ease: Easing::Spring(SpringConfig::default()\n                        .stiffness(100.0)\n                        .damping(10.0)\n                    ),\n                    ..Default::default()\n                }\n            /\u003e\n            \u003cbutton\n                on:click=move |_| set_is_open.update(|x| *x = !*x)\n                class=\"px-4 py-2 bg-gray-800 text-white rounded-lg\"\n            \u003e\n                \"Toggle Size\"\n            \u003c/button\u003e\n        \u003c/div\u003e\n    }\n}\n```\n\n### Gesture Animations\n\n```rust\nuse leptos::*;\nuse leptos_motion::*;\n\n#[component]\npub fn DraggableCard() -\u003e impl IntoView {\n    view! {\n        \u003cMotionDiv\n            class=\"w-64 h-40 bg-gradient-to-br from-purple-500 to-pink-500 rounded-xl shadow-xl cursor-grab active:cursor-grabbing\".to_string()\n            drag=DragConfig::default()\n            while_drag=motion_target!(\n                \"scale\" =\u003e AnimationValue::Number(1.05)\n            )\n            drag_constraints=DragConstraints {\n                left: Some(-100.0),\n                right: Some(100.0),\n                top: Some(-100.0),\n                bottom: Some(100.0),\n            }\n        \u003e\n            \u003cdiv class=\"p-6 text-white\"\u003e\n                \u003ch3 class=\"text-xl font-bold\"\u003e\"Draggable Card\"\u003c/h3\u003e\n                \u003cp class=\"text-purple-100\"\u003e\"Drag me around!\"\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/MotionDiv\u003e\n    }\n}\n```\n\n### Layout Animations\n\n```rust\nuse leptos::*;\nuse leptos_motion::*;\n\n#[component]\npub fn AnimatedList() -\u003e impl IntoView {\n    let (items, set_items) = signal(vec![1, 2, 3, 4, 5]);\n    \n    let add_item = move |_| {\n        set_items.update(|items| {\n            let new_id = items.len() + 1;\n            items.push(new_id);\n        });\n    };\n    \n    let remove_item = move |id| {\n        set_items.update(|items| {\n            items.retain(|\u0026x| x != id);\n        });\n    };\n\n    view! {\n        \u003cdiv class=\"space-y-4\"\u003e\n            \u003cbutton\n                on:click=add_item\n                class=\"px-4 py-2 bg-green-600 text-white rounded-lg\"\n            \u003e\n                \"Add Item\"\n            \u003c/button\u003e\n            \n            \u003cul class=\"space-y-2\"\u003e\n                \u003cFor\n                    each=items\n                    key=|item| *item\n                    children=move |item| {\n                        let id = item;\n                        view! {\n                            \u003cMotionDiv\n                                class=\"p-3 bg-gray-100 rounded-lg flex justify-between items-center\".to_string()\n                                key=id.to_string()\n                                initial=motion_target!(\n                                    \"opacity\" =\u003e AnimationValue::Number(0.0),\n                                    \"x\" =\u003e AnimationValue::Pixels(-20.0)\n                                )\n                                animate=motion_target!(\n                                    \"opacity\" =\u003e AnimationValue::Number(1.0),\n                                    \"x\" =\u003e AnimationValue::Pixels(0.0)\n                                )\n                                exit=motion_target!(\n                                    \"opacity\" =\u003e AnimationValue::Number(0.0),\n                                    \"x\" =\u003e AnimationValue::Pixels(20.0)\n                                )\n                                transition=Transition {\n                                    duration: Some(0.3),\n                                    ease: Easing::EaseOut,\n                                    ..Default::default()\n                                }\n                            \u003e\n                                \u003cspan\u003e\"Item {id}\"\u003c/span\u003e\n                                \u003cbutton\n                                    on:click=move |_| remove_item(id)\n                                    class=\"px-2 py-1 bg-red-500 text-white rounded text-sm\"\n                                \u003e\n                                    \"Remove\"\n                                \u003c/button\u003e\n                            \u003c/MotionDiv\u003e\n                        }\n                    }\n                /\u003e\n            \u003c/ul\u003e\n        \u003c/div\u003e\n    }\n}\n```\n\n## 🎨 Animation Types\n\n### Spring Animations\nNatural, physics-based animations that feel organic and responsive:\n\n```rust\n\u003cMotionDiv\n    animate=motion_target!(\n        \"scale\" =\u003e AnimationValue::Number(1.2)\n    )\n    transition=Transition {\n        duration: Some(0.6),\n        ease: Easing::Spring(SpringConfig::default()\n            .stiffness(100.0)    // Higher = faster\n            .damping(10.0)        // Lower = more bouncy\n            .mass(1.0)            // Higher = more inertia\n        ),\n        ..Default::default()\n    }\n\u003e\n    \"Spring Animation\"\n\u003c/MotionDiv\u003e\n```\n\n### Tween Animations\nSmooth, controlled animations with custom easing:\n\n```rust\n\u003cMotionDiv\n    animate=motion_target!(\n        \"opacity\" =\u003e AnimationValue::Number(0.5)\n    )\n    transition=Transition {\n        duration: Some(0.5),\n        ease: Easing::EaseInOut,\n        ..Default::default()\n    }\n\u003e\n    \"Tween Animation\"\n\u003c/MotionDiv\u003e\n```\n\n### Custom Easing\nUse built-in easing functions or create custom ones:\n\n```rust\n\u003cMotionDiv\n    animate=motion_target!(\n        \"y\" =\u003e AnimationValue::Pixels(100.0)\n    )\n    transition=Transition {\n        duration: Some(0.8),\n        ease: Easing::EaseOut,\n        ..Default::default()\n    }\n\u003e\n    \"Custom Easing\"\n\u003c/MotionDiv\u003e\n```\n\n## 🖱️ Gesture Support\n\n### Drag Gestures\n```rust\n\u003cMotionDiv\n    drag=DragConfig::default()\n    drag_constraints=DragConstraints {\n        left: Some(-100.0),\n        right: Some(100.0),\n        top: Some(-100.0),\n        bottom: Some(100.0),\n    }\n    while_drag=motion_target!(\n        \"scale\" =\u003e AnimationValue::Number(1.05)\n    )\n\u003e\n    \"Draggable content\"\n\u003c/MotionDiv\u003e\n```\n\n### Hover Gestures\n```rust\n\u003cMotionDiv\n    while_hover=motion_target!(\n        \"scale\" =\u003e AnimationValue::Number(1.05)\n    )\n    transition=Transition {\n        duration: Some(0.2),\n        ease: Easing::EaseOut,\n        ..Default::default()\n    }\n\u003e\n    \"Hover me!\"\n\u003c/MotionDiv\u003e\n```\n\n### Tap Gestures\n```rust\n\u003cMotionDiv\n    while_tap=motion_target!(\n        \"scale\" =\u003e AnimationValue::Number(0.95)\n    )\n    transition=Transition {\n        duration: Some(0.1),\n        ease: Easing::EaseOut,\n        ..Default::default()\n    }\n\u003e\n    \"Tap me!\"\n\u003c/MotionDiv\u003e\n```\n\n## 📱 Layout Animations\n\nAutomatically animate layout changes with the `layout` prop:\n\n```rust\n\u003cMotionDiv\n    layout=true\n    class=\"grid grid-cols-3 gap-4\".to_string()\n\u003e\n    \u003cFor\n        each=items\n        key=|item| item.id\n        children=move |item| {\n            view! {\n                \u003cMotionDiv\n                    layout=true\n                    class=\"p-4 bg-white rounded-lg shadow\".to_string()\n                \u003e\n                    {item.content}\n                \u003c/MotionDiv\u003e\n            }\n        }\n    /\u003e\n\u003c/MotionDiv\u003e\n```\n\n## 🎭 Presence Animations\n\nHandle enter/exit animations automatically:\n\n```rust\n\u003cAnimatePresence\u003e\n    {move || if show_modal() {\n        Some(view! {\n            \u003cMotionDiv\n                initial=motion_target!(\n                    \"opacity\" =\u003e AnimationValue::Number(0.0),\n                    \"scale\" =\u003e AnimationValue::Number(0.8)\n                )\n                animate=motion_target!(\n                    \"opacity\" =\u003e AnimationValue::Number(1.0),\n                    \"scale\" =\u003e AnimationValue::Number(1.0)\n                )\n                exit=motion_target!(\n                    \"opacity\" =\u003e AnimationValue::Number(0.0),\n                    \"scale\" =\u003e AnimationValue::Number(0.8)\n                )\n                transition=Transition {\n                    duration: Some(0.3),\n                    ease: Easing::EaseOut,\n                    ..Default::default()\n                }\n                class=\"fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center\".to_string()\n            \u003e\n                \u003cdiv class=\"bg-white p-6 rounded-lg\"\u003e\n                    \"Modal content\"\n                \u003c/div\u003e\n            \u003c/MotionDiv\u003e\n        })\n    } else {\n        None\n    }}\n\u003c/AnimatePresence\u003e\n```\n\n## 🔧 Advanced Usage\n\n### Animation Presets\nUse built-in animation presets for common patterns:\n\n```rust\nuse leptos_motion_core::AnimationPresets;\n\n\u003cMotionDiv\n    initial=AnimationPresets::fade_in().initial\n    animate=AnimationPresets::fade_in().animate\n    transition=AnimationPresets::fade_in().transition\n    class=\"animated-element\".to_string()\n\u003e\n    \"Fade In Animation\"\n\u003c/MotionDiv\u003e\n```\n\n### Keyframe Animations\nCreate complex multi-step animations:\n\n```rust\nuse leptos_motion_core::animation::Keyframes;\n\nlet keyframes = Keyframes::new()\n    .add_keyframe(0.0, motion_target!(\"opacity\" =\u003e AnimationValue::Number(0.0)))\n    .add_keyframe(0.5, motion_target!(\"opacity\" =\u003e AnimationValue::Number(0.5)))\n    .add_keyframe(1.0, motion_target!(\"opacity\" =\u003e AnimationValue::Number(1.0)));\n\n\u003cMotionDiv\n    animate=keyframes.to_animation_target()\n    transition=Transition {\n        duration: Some(2.0),\n        ease: Easing::EaseInOut,\n        ..Default::default()\n    }\n    class=\"keyframe-demo\".to_string()\n\u003e\n    \"Keyframe Animation\"\n\u003c/MotionDiv\u003e\n```\n\n### Performance Optimization\n```rust\n// Use layout animations for smooth position changes\n\u003cMotionDiv\n    layout=true\n    class=\"performance-optimized\".to_string()\n\u003e\n    \"Layout Animation\"\n\u003c/MotionDiv\u003e\n\n// Use appropriate easing for performance\n\u003cMotionDiv\n    transition=Transition {\n        duration: Some(0.3),\n        ease: Easing::EaseOut, // Hardware accelerated\n        ..Default::default()\n    }\n    class=\"hardware-accelerated\".to_string()\n\u003e\n    \"Hardware Accelerated\"\n\u003c/MotionDiv\u003e\n```\n\n## 🚀 Performance Features\n\n- **WebAssembly**: Rust compiled to WASM for near-native performance\n- **GPU Acceleration**: Automatic hardware acceleration when available\n- **Frame Throttling**: Built-in 60fps limiting for smooth animations\n- **Memory Management**: Efficient memory usage with automatic cleanup\n- **Tree Shaking**: Only include the features you use\n\n## 🌐 Browser Support\n\n- **Modern Browsers**: Chrome 67+, Firefox 60+, Safari 11.1+, Edge 79+\n- **WebAssembly**: Full WASM support required\n- **CSS Grid/Flexbox**: For layout animations\n- **Touch Events**: For mobile gesture support\n\n## 📚 Documentation\n\n- [API Reference](https://docs.rs/leptos-motion)\n- [Examples](https://github.com/cloud-shuttle/leptos-motion/tree/main/examples)\n- [Migration Guide](https://github.com/cloud-shuttle/leptos-motion/blob/main/docs/migration/framer-motion.md)\n\n## 🤝 Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](https://github.com/cloud-shuttle/leptos-motion/blob/main/CONTRIBUTING.md) for details.\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](https://github.com/cloud-shuttle/leptos-motion/blob/main/LICENSE) file for details.\n\n## 🙏 Acknowledgments\n\n- Inspired by [Framer Motion](https://www.framer.com/motion/)\n- Built with [Leptos](https://github.com/leptos-rs/leptos)\n- Powered by Rust and WebAssembly\n\n---\n\n**Made with ❤️ by the Leptos Motion team**\n\n[GitHub](https://github.com/cloud-shuttle/leptos-motion) • [Issues](https://github.com/cloud-shuttle/leptos-motion/issues) • [Discussions](https://github.com/cloud-shuttle/leptos-motion/discussions)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloud-shuttle%2Fleptos-motion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcloud-shuttle%2Fleptos-motion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloud-shuttle%2Fleptos-motion/lists"}