Fixing Framer Motion TypeScript Definitions To Allow Custom CSS Variables in `style` Property
mazwrld opened this issue · comments
Issue: Framer Motion TypeScript Definitions Should Allow Custom CSS Variables in style
Property
Description
The current TypeScript definitions for Framer Motion do not allow for the use of custom CSS variables in the style
property of initial
and animate
objects. This limitation causes TypeScript errors when using custom CSS variables in these properties. The error message received is: "Object literal may only specify known properties, and ''--x'' does not exist in type 'string[] | AnimationControls | TargetAndTransition'."
Proposed Fix
Modify the TypeScript definitions to allow for custom CSS variables in the style
property by using a custom type definition for the style
property.
Steps to Reproduce
- Use custom CSS variables in the
style
property ofinitial
oranimate
objects in a Framer Motion component. - Run TypeScript checks or linting with ESLint.
- Observe TypeScript errors related to the use of custom CSS variables.
Example
import { motion, MotionProps } from 'framer-motion'
type CustomStyle = {
'--x': string;
};
type CustomMotionProps = MotionProps & {
initial: {
style: CustomStyle;
scale: number;
};
animate: {
style: CustomStyle;
};
};
const initialProps: CustomMotionProps = {
initial: {
style: {
'--x': '100%',
},
scale: 1,
},
animate: {
style: {
'--x': '-100%',
},
},
};
// Use `initialProps` in a Framer Motion component
Seems like a good solution, but it didn't work for me.
I avoided the typescript error just by adding a @ts-ignore
flag on top of both errors.
Like so:
const ShinyButton = () => {
return (
<motion.button
// @ts-ignore
initial={{ "--x": "100%", scale: 1 }}
// @ts-ignore
animate={{ "--x": "-100%" }}
whileTap={{ scale: 0.97 }}
transition={{
repeat: Infinity,
repeatType: "loop",
repeatDelay: 1,
type: "spring",
stiffness: 20,
damping: 15,
mass: 2,
scale: {
type: "spring",
stiffness: 10,
damping: 5,
mass: 0.1,
},
}}
className="px-6 py-2 rounded-md relative radial-gradient"
>
<span className="text-neutral-100 tracking-wide font-light h-full w-full block relative linear-mask">
Start now
</span>
<span className="block absolute inset-0 rounded-md p-px linear-overlay" />
</motion.button>
);
};
It will build with no errors. Hope it works!
Oh, I totally missed a bug because I didn't test the code in my repo. My bad!
I've fixed it now. In my case, using @ts-ignore would have worked, but my eslint rules are pretty strict, so I had to come up with a different solution.
type ExtendedMotionProps = HTMLMotionProps<'button'> & {
initial?: Record<string, unknown>
animate?: Record<string, unknown>
}
type ShinyButtonProps = ExtendedMotionProps & {
children: React.ReactNode
}
export default function ShinyButton({
children,
type = 'button', // Default to "button" type
...rest
}: ShinyButtonProps) {
return (
<motion.button
type={type}
initial={{ '--x': '100%', scale: 1 }}
animate={{ '--x': '-100%' }}
whileTap={{ scale: 0.88 }}
transition={{
repeat: Infinity,
repeatType: 'loop',
repeatDelay: 1,
type: 'spring',
stiffness: 20,
damping: 15,
mass: 2,
scale: {
type: 'spring',
stiffness: 10,
damping: 5,
mass: 0.1,
},
}}
className="radial-gradient relative w-40 rounded-md px-6 py-2"
{...rest}
>
<span className="linear-mask relative block h-full w-full tracking-wide">
{children}
</span>
<span className="linear-overlay absolute inset-0 block rounded-md p-px" />
</motion.button>
)
}
@estebanarriaga I used your code to fix the issue but didn't work. @mazwrld Can you share the full code for this component?
@sixfwa I'd request you to put up a solution for this in the comments of YouTube channel.
@Susmita-Dey here it is.
'use client' // for nextjs
import { motion, type HTMLMotionProps } from 'framer-motion'
type ExtendedMotionProps = HTMLMotionProps<'button'> & {
initial?: Record<string, unknown>
animate?: Record<string, unknown>
}
type ShinyButtonProps = ExtendedMotionProps & {
children: React.ReactNode
}
export default function ShinyButton({
children,
type = 'button', // Default to "button" type
...rest
}: ShinyButtonProps) {
return (
<motion.button
type={type}
initial={{ '--x': '100%', scale: 1 }}
animate={{ '--x': '-100%' }}
whileTap={{ scale: 0.88 }}
transition={{
repeat: Infinity,
repeatType: 'loop',
repeatDelay: 1,
type: 'spring',
stiffness: 20,
damping: 15,
mass: 2,
scale: {
type: 'spring',
stiffness: 10,
damping: 5,
mass: 0.1,
},
}}
className="radial-gradient relative w-40 rounded-md px-6 py-2"
{...rest}
>
<span className="linear-mask relative block h-full w-full tracking-wide">
{children}
</span>
<span className="linear-overlay absolute inset-0 block rounded-md p-px" />
</motion.button>
)
}
@mazwrld Thanks for sharing this. The button is being rendered now but there's issue with the button style and not showing up as shown in the video. Can you share your repo link?