Motion Navigation
Navigation animation patterns: tab indicator sliding via Motion.dev layoutId with spring stiffness:400/damping:30, hamburger menu expanding at 250ms cubic-bezier(0.16,1,0.…
$ prime install @community/pattern-motion-navigation Projection
Always in _index.xml · the agent never has to ask for this.
MotionNavigation [pattern] v1.0.0
Navigation animation patterns: tab indicator sliding via Motion.dev layoutId with spring stiffness:400/damping:30, hamburger menu expanding at 250ms cubic-bezier(0.16,1,0.3,1), and GSAP indicator fallback for non-React contexts.
Loaded when retrieval picks the atom as adjacent / supporting.
MotionNavigation [pattern] v1.0.0
Navigation animation patterns: tab indicator sliding via Motion.dev layoutId with spring stiffness:400/damping:30, hamburger menu expanding at 250ms cubic-bezier(0.16,1,0.3,1), and GSAP indicator fallback for non-React contexts.
Problem
Tab bars with no indicator animation lose spatial context — users can't see 'where they came from.' Hamburger menus that appear instantly feel disconnected from the toggle action. But over-animated navigation (>250ms) is used on every page interaction and quickly becomes annoying.
Solution
Two animation budgets: indicators ≤200ms (they fire on every tab tap); menu open/close ≤250ms. Use spring physics for the tab indicator (slight overshoot gives physical feel). Menu uses ease-out on open, ease-in on close.
Timing
- Tab Indicator: spring — stiffness: 400, damping: 30 (snappy, slight overshoot)
- Tab Indicator Max: ≤ 200ms perceived settle
- Menu Open: 250ms cubic-bezier(0.16, 1, 0.3, 1)
- Menu Close: 180ms ease-in
- Header Scroll: 200ms ease-out (show/hide on scroll direction change)
Code Framer Motion
// Tab indicator — Motion.dev layoutId (React)
import { motion } from "motion/react";
function TabBar({ tabs, active, setActive }) {
return (
<nav style={{ display: "flex", position: "relative" }}>
{tabs.map((tab, i) => (
<button key={tab} onClick={() => setActive(i)}
style={{ padding: "8px 20px", position: "relative" }}>
{tab}
{active === i && (
<motion.div
layoutId="tab-indicator"
style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: 2, background: "currentColor" }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>
)}
</button>
))}
</nav>
);
}
Code Gsap
// Tab indicator — GSAP (framework-agnostic)
import gsap from "gsap";
function moveIndicator(activeTab) {
gsap.to(".tab-indicator", {
x: activeTab.offsetLeft,
width: activeTab.offsetWidth,
duration: 0.25,
ease: "power2.out",
});
}
Styles
/* ── Hamburger menu overlay ──────────────────────────────────────────── */
.mobile-menu {
position: fixed; inset: 0;
transform-origin: top right;
animation: menu-open 250ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes menu-open {
from { opacity: 0; transform: scale(0.97) translateY(-8px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.mobile-menu.is-closing {
animation: menu-close 180ms ease-in both;
}
@keyframes menu-close {
to { opacity: 0; transform: scale(0.97) translateY(-8px); }
}
@media (prefers-reduced-motion: reduce) {
.mobile-menu, .mobile-menu.is-closing { animation: none; }
}
A11y
- Mobile menu must trap focus while open — identical to modal pattern.
- Indicator animation must not delay focus-visible feedback — visual indicator can lag slightly, focus ring must not.
- Tab ARIA roles: role='tablist', role='tab', role='tabpanel' — animation is cosmetic.
Loaded when retrieval picks the atom as a focal / direct hit.
MotionNavigation [pattern] v1.0.0
Navigation animation patterns: tab indicator sliding via Motion.dev layoutId with spring stiffness:400/damping:30, hamburger menu expanding at 250ms cubic-bezier(0.16,1,0.3,1), and GSAP indicator fallback for non-React contexts.
Problem
Tab bars with no indicator animation lose spatial context — users can't see 'where they came from.' Hamburger menus that appear instantly feel disconnected from the toggle action. But over-animated navigation (>250ms) is used on every page interaction and quickly becomes annoying.
Solution
Two animation budgets: indicators ≤200ms (they fire on every tab tap); menu open/close ≤250ms. Use spring physics for the tab indicator (slight overshoot gives physical feel). Menu uses ease-out on open, ease-in on close.
Timing
- Tab Indicator: spring — stiffness: 400, damping: 30 (snappy, slight overshoot)
- Tab Indicator Max: ≤ 200ms perceived settle
- Menu Open: 250ms cubic-bezier(0.16, 1, 0.3, 1)
- Menu Close: 180ms ease-in
- Header Scroll: 200ms ease-out (show/hide on scroll direction change)
Code Framer Motion
// Tab indicator — Motion.dev layoutId (React)
import { motion } from "motion/react";
function TabBar({ tabs, active, setActive }) {
return (
<nav style={{ display: "flex", position: "relative" }}>
{tabs.map((tab, i) => (
<button key={tab} onClick={() => setActive(i)}
style={{ padding: "8px 20px", position: "relative" }}>
{tab}
{active === i && (
<motion.div
layoutId="tab-indicator"
style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: 2, background: "currentColor" }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>
)}
</button>
))}
</nav>
);
}
Code Gsap
// Tab indicator — GSAP (framework-agnostic)
import gsap from "gsap";
function moveIndicator(activeTab) {
gsap.to(".tab-indicator", {
x: activeTab.offsetLeft,
width: activeTab.offsetWidth,
duration: 0.25,
ease: "power2.out",
});
}
Styles
/* ── Hamburger menu overlay ──────────────────────────────────────────── */
.mobile-menu {
position: fixed; inset: 0;
transform-origin: top right;
animation: menu-open 250ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes menu-open {
from { opacity: 0; transform: scale(0.97) translateY(-8px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.mobile-menu.is-closing {
animation: menu-close 180ms ease-in both;
}
@keyframes menu-close {
to { opacity: 0; transform: scale(0.97) translateY(-8px); }
}
@media (prefers-reduced-motion: reduce) {
.mobile-menu, .mobile-menu.is-closing { animation: none; }
}
A11y
- Mobile menu must trap focus while open — identical to modal pattern.
- Indicator animation must not delay focus-visible feedback — visual indicator can lag slightly, focus ring must not.
- Tab ARIA roles: role='tablist', role='tab', role='tabpanel' — animation is cosmetic.
Problem
Tab bars with no indicator animation lose spatial context — users can't see 'where they came from.' Hamburger menus that appear instantly feel disconnected from the toggle action. But over-animated navigation (>250ms) is used on every page interaction and quickly becomes annoying.
Solution
Two animation budgets: indicators ≤200ms (they fire on every tab tap); menu open/close ≤250ms. Use spring physics for the tab indicator (slight overshoot gives physical feel). Menu uses ease-out on open, ease-in on close.
Timing
- Tab Indicator: spring — stiffness: 400, damping: 30 (snappy, slight overshoot)
- Tab Indicator Max: ≤ 200ms perceived settle
- Menu Open: 250ms cubic-bezier(0.16, 1, 0.3, 1)
- Menu Close: 180ms ease-in
- Header Scroll: 200ms ease-out (show/hide on scroll direction change)
Code Framer Motion
// Tab indicator — Motion.dev layoutId (React)
import { motion } from "motion/react";
function TabBar({ tabs, active, setActive }) {
return (
<nav style={{ display: "flex", position: "relative" }}>
{tabs.map((tab, i) => (
<button key={tab} onClick={() => setActive(i)}
style={{ padding: "8px 20px", position: "relative" }}>
{tab}
{active === i && (
<motion.div
layoutId="tab-indicator"
style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: 2, background: "currentColor" }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>
)}
</button>
))}
</nav>
);
}
Code Gsap
// Tab indicator — GSAP (framework-agnostic)
import gsap from "gsap";
function moveIndicator(activeTab) {
gsap.to(".tab-indicator", {
x: activeTab.offsetLeft,
width: activeTab.offsetWidth,
duration: 0.25,
ease: "power2.out",
});
}
Styles
/* ── Hamburger menu overlay ──────────────────────────────────────────── */
.mobile-menu {
position: fixed; inset: 0;
transform-origin: top right;
animation: menu-open 250ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes menu-open {
from { opacity: 0; transform: scale(0.97) translateY(-8px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.mobile-menu.is-closing {
animation: menu-close 180ms ease-in both;
}
@keyframes menu-close {
to { opacity: 0; transform: scale(0.97) translateY(-8px); }
}
@media (prefers-reduced-motion: reduce) {
.mobile-menu, .mobile-menu.is-closing { animation: none; }
}
A11y
- Mobile menu must trap focus while open — identical to modal pattern.
- Indicator animation must not delay focus-visible feedback — visual indicator can lag slightly, focus ring must not.
- Tab ARIA roles: role='tablist', role='tab', role='tabpanel' — animation is cosmetic.
Source
prime-system/examples/frontend-design/primes/compiled/@community/pattern-motion-navigation/atom.yaml