plans/issues/issue-2652.md
loadExternalIsValidProp / implicit @emotion/is-prop-valid loadingExecutor instructions: This plan is decision-gated. Do NOT modify any source until the maintainer has recorded a decision in the
plans/issues/README.mdrow for issue-2652. Run the drift check first.Drift check (run first):
gh api repos/motiondivision/motion/issues/2652 --jq .state→ expect"open". Confirm the excerpt below matchespackages/framer-motion/src/render/dom/utils/filter-props.ts.
styled(motion.div) users42bfbe3ed, 2026-06-11Filed 2024-05-06. filter-props.ts still ships a module-level
try { require("@emotion/is-prop-" + "valid") } catch {} whose own comment
says it should have been removed "in a 6.0.0" (motion is now on 12.x). The
implicit require:
@emotion/is-prop-valid into peerDependencies (optional) —
packages/framer-motion/package.json:102,107-109 — which still trips some
package managers' peer-resolution warnings;packages/framer-motion/src/render/dom/utils/filter-props.ts:9-14 — explicit
injection API already exists:
export function loadExternalIsValidProp(isValidProp?: IsValidProp) {
if (typeof isValidProp !== "function") return
shouldForward = (key: string) =>
key.startsWith("on") ? !isValidMotionProp(key) : isValidProp(key)
}
filter-props.ts:30-43 — the implicit loader (string-concat to dodge bundler
static resolution):
try {
const emotionPkg = "@emotion/is-prop-" + "valid"
loadExternalIsValidProp(require(emotionPkg).default)
} catch {
// fallback is the existing `isPropValid`.
}
Explicit injection is already wired through the public API:
packages/framer-motion/src/components/MotionConfig/index.tsx:40 —
isValidProp && loadExternalIsValidProp(isValidProp) (the documented
<MotionConfig isValidProp={isPropValid}> pattern).
Record ONE of these in the README row before any code change:
filter-props.ts:30-43, drop @emotion/is-prop-valid from
peerDependencies/peerDependenciesMeta, document that
styled(motion.div) users must pass
<MotionConfig isValidProp={isPropValid}>. Breaking for CJS users who
relied on auto-loading → major-version changelog entry.MotionConfig isValidProp escape hatch for ESM/Vite environments.loadExternalIsValidProp from the public API (it is currently internal) so
non-React/motion consumers can inject without MotionConfig.Add a Jest test in
packages/framer-motion/src/render/dom/__tests__/filter-props.test.tsx
(create if absent; model on neighbouring tests in src/render/dom/__tests__/)
asserting: (a) by default, non-motion arbitrary props are forwarded; (b) after
loadExternalIsValidProp(isPropValid), arbitrary non-DOM props are filtered.
This pins the explicit-injection behaviour before removing the implicit path.
Verify: npx jest --config packages/framer-motion/jest.config.json --testPathPattern="filter-props" → passes.
Delete the try { ... } catch {} block (filter-props.ts:30-43), keeping
loadExternalIsValidProp and the explanatory comment about Emotion/Styled
Components (rewritten to describe the explicit MotionConfig route). Remove
"@emotion/is-prop-valid" from peerDependencies and peerDependenciesMeta
in packages/framer-motion/package.json.
Verify: grep -rn "is-prop-" packages/framer-motion/src packages/framer-motion/package.json → only the loadExternalIsValidProp definition/imports remain.
yarn build from repo root, then
npx jest --config packages/framer-motion/jest.config.json (ignore the
known pre-existing SSR TextEncoder and use-velocity failures).
Add a CHANGELOG.md entry under Unreleased flagged as breaking ("Removed
implicit @emotion/is-prop-valid auto-loading; use
<MotionConfig isValidProp>"). Open a PR referencing #2652. Do not merge —
breaking changes ride the next major.
plans/issues/README.md rowrequire("@emotion/is-prop-valid") (concatenated or not) in src@emotion/is-prop-valid implicitly → report before working around.