Framer for Developers

An overview of the current best practices for writing code in Framer, introduced with the launch of Framer for Developers.

What’s new?

Since the launch of Framer for Developers, Framer now loads ES code modules directly in the browser. 🧐 I’ll try to explain.

If you used something like Create React App to build a website on your computer (intro here), you know that you can add different packages to your project with NPM, which saves those packages in a node_modules folder.

React is one of those packages, Framer Motion another one, and most of them also have dependencies: other JavaScript packages they use. They all get downloaded by NPM (or Yarn), and that node_modules folder can quickly contain hundreds of megabytes.

Then, when you’re ready to publish your website, a bundler (most often webpack) gathers only the code your website really needs and makes it smaller (minifies it). It gives you a streamlined version of your project in a folder ready to upload to a web server.

That’s also how Framer worked: When you added a package to your project, it got downloaded, included in the bundle (all happening on their servers), and then sent to your browser.

Now, all this bundling is not necessary. Most browsers can load those packages, in the form of ES modules, directly from a URL. Framer started to use that possibility and now runs ‘unbundled’. So when you now want to use, for instance, the three.js 3D library in your project, you can import it directly from a service like jspm:

import * as THREE from "https://ga.jspm.io/npm:three@0.129.0/build/three.js"

Likewise, canvas and code components you create are now easier to share between projects because you can import them from an URL like https://framer.com/m/….

The Framer team later added the possibility to do direct imports (more info), like you do when developing a JavaScript project on the desktop. So now you can import (for instance) three.js like this:

import * as THREE from "three"

The changes

I’m using this color coding for the example snippets.

❌ This will not work anymore.
👍 Still works, but there’s now a better way.
✅ This is The Way.

 

In Code Components

No need to import React

When you earlier always had to import "react" in your code components:

import * as React from "react"

export default function MyComponent() {
    return <h1>Hi there!</h1>
}

Now you can just omit it:

export default function MyComponent() {
    return <h1>Hi there!</h1>
}

Of course, you’ll have to import useState(), useEffect(), or other React utilities you use. You can pick and choose like this:

import { useState, useRef } from "react"
import { motion } from "framer-motion"

export default function MyComponent(props) {
    const [isHover, setHover] = useState(false)

    return (
        <motion.div
            …

… or grab all (*) of React like this:

import * as React from "react"
import { motion } from "framer-motion"

export default function MyComponent(props) {
    const [isHover, setHover] = React.useState(false)

    return (
        <motion.div
            …

Drop the <Frame>

The frames we draw on the canvas aren’t based anymore on Framer Library’s <Frame> component. At Framer, they started using regular HTML elements (e.g., <div>s or <motion.div>s) for their components, and we should do the same.

The Framer Library is still included, though, and you can use <Frame>s in new projects:

import { Frame } from "framer"

export default function Frame_based_component(props) {
    return (
        <Frame
            {...props}
            size={150}
            radius={25}
            backgroundColor="MediumSlateBlue"
            color="White"
            style={{ fontSize: 13, fontWeight: 600 }}
            animate={{ rotate: 360 }}
        >
            Frame
        </Frame>
    )
}

But when you drop this component on the canvas, you’ll see that you can’t even select it.

It’s 1 by 1 point big, so you can select it by dragging over its top-left corner.

That’s because it now gets a default ‘Auto’ size of one by one point. You can change the component’s sizing to ‘Fixed’ (in the properties panel) and adjust its Width and Height values.

But you can also solve this, apparently, by giving the component default width and height values and giving it a fixed size with the new layout options.

/**
 * @framerSupportedLayoutWidth fixed
 * @framerSupportedLayoutHeight fixed
 */
export default function Frame_based_component(props) {
    return (
        <Frame
            {...props}
            size={150}
            radius={25}
            backgroundColor="MediumSlateBlue"
            color="White"
            style={{ fontSize: 13, fontWeight: 600 }}
            animate={{ rotate: 360 }}
        >
            Frame
        </Frame>
    )
}

Frame_based_component.defaultProps = { width: 150, height: 150 }
When you drag it onto the canvas, this component will be 150 by 150 points.

But… it’s better to now use a <div> instead; or a <motion.div> when animating.

import { motion } from "framer-motion"

export default function Div_based_component(props) {
    return (
        <motion.div
            {...props}
            style={{
                width: 150,
                height: 150,
                borderRadius: 25,
                backgroundColor: "MediumSlateBlue",
                color: "White",
                fontSize: 13,
                fontWeight: 600,
                // Centering the text
                display: "flex",
                placeItems: "center",
                placeContent: "center",
            }}
            animate={{ rotate: 360 }}
        >
            Motion div
        </motion.div>
    )
}

Importing Framer Motion

We could import the Framer Motion bits we needed, like the motion component and utilities, from the Framer library.

import { motion, useMotionValue, useTransform } from "framer"

That’s not possible anymore; now you need to get them from the Framer Motion library:

import { motion, useMotionValue, useTransform } from "framer-motion"

But what about <Stack>?

<Stack>, the code version of the stack we have on the canvas, is another Framer Library component I often used.

You can still import and use it, but you’ll have the same problem as with a <Frame>: Your component will be tiny when you drop it on the canvas. But the same solution applies: give the component a default width and height, and fixed values for the supported layout dimensions:

import { Stack } from "framer"

/**
 * @framerSupportedLayoutWidth fixed
 * @framerSupportedLayoutHeight fixed
 */
export default function Divs_in_a_Stack(props) {
    return (
        <Stack
            {...props}
            direction="vertical"
            distribution="start"
            alignment="start"
        >
            <div style={divStyle}>It seems like…</div>
            <div style={divStyle}>we're…</div>
            <div style={divStyle}>stacked.</div>
        </Stack>
    )
}

Divs_in_a_Stack.defaultProps = { width: 380, height: 200 }

const divStyle: React.CSSProperties = {
    width: "380px",
    height: "60px",
    borderRadius: "10px",
    backgroundColor: "LightSeaGreen",
    color: "White",
    fontSize: 13,
    fontWeight: 600,
    display: "flex",
    placeItems: "center",
    placeContent: "center",
}
Three <div>s in a <Stack>

A Stack is just a CSS Flexbox, though. So you can easily recreate it with some CSS.

export default function Vertical_Flexbox(props) {
    const { style, ...rest } = props

    return (
        <div
            {...rest}
            style={{
                width: 380,
                height: 200,
                ...style,
                backgroundColor: "#fff",
                display: "flex",
                flexDirection: "column",
                justifyContent: "flex-start",
                alignItems: "flex-start",
                gap: 10,
                flexWrap: "wrap", // This line adds wrapping!
            }}
        >
            <div style={divStyle}>It seems like…</div>
            <div style={divStyle}>we’re…</div>
            <div style={divStyle}>flexboxed.</div>
        </div>
    )
}

const divStyle: React.CSSProperties = {
    width: "380px",
    height: "60px",
    borderRadius: "10px",
    backgroundColor: "IndianRed",
    color: "White",
    fontSize: 13,
    fontWeight: 600,
    display: "flex",
    placeItems: "center",
    placeContent: "center",
}
Three <div>s in a flexbox

I also made a <FlexStack> component that you can use as a replacement for <Stack>.

And <Page>? And <Scroll>?

The same is true for <Page> and <Scroll>: you can still import and use them. But I hope we’ll soon see code module versions of them appear in the code editor’s Import Menu.

import { Page, Scroll } from "framer"

export default function CC_19_Scroll_and_Page_combined(props) {
    return (
        <div
            style={{
                width: 400,
                height: 400,
                ...props.style,
                display: "flex",
                placeItems: "center",
                placeContent: "center",
            }}
        >
            <Page
                width={150}
                height={150}
                radius={30}
                center
                // Paging
                directionLock
            >
                …

Code snippet from 19. Scroll and Page combined. See also: 17. Page: Indicators and 18. Page: Effects.

By the way, you can also recreate a Scroll with a draggable motion.div. Check these examples:

Or, alternatively, you can set a div’s overflow property to scroll, giving you a native browser scroll (more info).

Pass some style

Now that we’re using style for the element’s CSS styling remember to pass the existing props.style to the div so that Auto Sizing works.

You could do it like this:

export default function MyComponent(props) {
    return (
        <motion.div
            style={{
                width: 150,
                height: 150,
                ...props.style,
                borderRadius: 25,
                backgroundColor: "#06F",
            }}
            animate={{ rotate: 180 }}
        />
    )
}

But it’s best to destructure it from the props so you can pass the remaining props (here: rest) to the div. Just in case you’d want to pass extra properties to this component with a code override or when using it inside another component.

export default function MyComponent(props) {
    const { style, ...rest } = props

    return (
        <motion.div
            {...rest}
            style={{
                width: 150,
                height: 150,
                ...style,
                borderRadius: 25,
                backgroundColor: "#06F",
            }}
            animate={{ rotate: 180 }}
        />
    )
}

By putting width and height before ‘...style’, this component will have a default size of 150 by 150, which Framer can override (by passing down values inside style).

In Code Overrides

Higher-order components

Here’s an override like we used to write them. This kind of override will still work.

export function Repeat(): Override {
    return {
        animate: { rotate: 360 },
        transition: { ease: "linear", duration: 2, repeat: Infinity },
    }
}
4. Repeat

The now favored higher-order component format is a bit more involved:

export function Repeat(Component): ComponentType {
    return (props) => {
        return (
            <Component
                {...props}
                animate={{ rotate: 360 }}
                transition={{ ease: "linear", duration: 2, repeat: Infinity }}
            />
        )
    }
}

But most of it is boilerplate that’ll be the same for every override.

A component that returns a component

This new type of override is a function that returns another function, so we have two return() statements. The outermost function accepts an existing Component as a parameter, and the innermost returns it as JSX (<Component>) with the props it had, plus the extra properties you’re adding.

You’ll almost always want your code inside the inner arrow () => function, which will run every time the layer is rendered in the Preview.

export function Repeat(Component): ComponentType {
    // This code here runs only once; when the layer first appears in the preview.
    return (props) => {
        // This code runs every time the layer (re)renders.
        return (
            <Component
                {...props}
                animate={{ rotate: 360 }}
                transition={{ ease: "linear", duration: 2, repeat: Infinity }}
            />
        )
    }
}

Pass along the props

Don’t forget to pass the existing props back to the component. Otherwise, the element you attach the override to will… disappear.

export function withHover(Component): ComponentType {
    return (props) => {
        return <Component {...props} whileHover={{ scale: 1.05 }} />
    }
}

The above ... is JavaScript’s spread syntax. It takes all the keys and values inside the props object and spreads them out. The result is the same as if you were adding all those properties manually:

        return (
            <Component
                className={props.className}
                style={props.style}
                name={props.name}
                // … etc
                whileHover={{ scale: 1.05 }}
            />
        )

When you don’t pass the props, the Frame will simply disappear. (Or you might get an error in the Preview when doing this with a component.)

Here’s what we have on the canvas (example from Code Overrides):

The Frames on the Canvas

The Frame on the right has this override:

export function withHoverNoprops(Component): ComponentType {
    return (props) => {
        return <Component whileHover={{ scale: 1.05 }} />
    }
}

… making it disappear in the Preview.

The Frames in the Preview

ComponentType instead of Override

Earlier, we imported Override from the Framer Library. It’s a bit of TypeScript that defines how an override function should look.

import { Override } from "framer"

export function Repeat(): Override {
    return {
        animate: { rotate: 360 },
        transition: { ease: "linear", duration: 2, repeat: Infinity },
    }
}

Now we use another object: ComponentType, imported from React.

import type { ComponentType } from "react"

export function Repeat(Component): ComponentType {
    return (props) => {
        return (
            <Component
                {...props}
                animate={{ rotate: 360 }}
                transition={{ ease: "linear", duration: 2, repeat: Infinity }}
            />
        )
    }
}

This ComponentType serves the same purpose: It helps the editor check your code and makes your override appear in Framer’s properties panel.

Importing Framer Motion

Here also: When we needed something from Framer Motion, we could grab it from the Framer Library:

import { Override, useMotionValue, useTransform } from "framer"

Now you have to get it from Framer Motion directly:

import type { ComponentType } from "react"
import { useMotionValue, useTransform } from "framer-motion"

Passing visual properties to style

Framer’s <Frame>s had many top-level convenience properties you could change directly. For example, you could tie MotionValues (like here x, scale, and rotate) directly to the corresponding top-level properties like x, scale, and rotate ( archived pages).

export function Drag_Transform(Component): ComponentType {
    return (props) => {
        const x = useMotionValue(0)
        const scale = useTransform(x, [-150, 150], [1.5, 0.5])
        const rotate = useTransform(x, [-150, 150], [-90, 90])

        return (
            <Component
                {...props}
                // Dragging
                drag="x"
                dragConstraints={{ left: 0, right: 0 }}
                dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
                // Transformation
                x={x}
                scale={scale}
                rotate={rotate}
            />
        )
    }
}

By the way, this is what this code does: Dragging the Frame changes its x (a Motion value), which in turn changes its scale and rotate (Motion values created with useTransform()).

13. Drag: Transform

However, the Frames you now draw on the canvas aren’t based on the legacy <Frame> anymore, so it’s better to treat them like motion elements and pass those visual properties to style:

export function Drag_Transform(Component): ComponentType {
    return (props) => {
        const { style, ...rest } = props

        const x = useMotionValue(0)
        const scale = useTransform(x, [-150, 150], [1.5, 0.5])
        const rotate = useTransform(x, [-150, 150], [-90, 90])

        return (
            <Component
                {...rest}
                // Dragging
                drag="x"
                dragConstraints={{ left: 0, right: 0 }}
                dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
                // Transformation
                style={{ ...style, x: x, scale: scale, rotate: rotate }}
            />
        )
    }
}

When defining style, you don’t want to override the existing styling, so it’s best to destructure the current style from the props and pass it back in again—like you would with a code component.

What happens when you don’t pass along the existing styling? Well, the Frame you attached the override to disappears in the Preview, just like when not passing its props (but strangely, a canvas component will not disappear).

Sharing data between overrides

Earlier, we often used Framer’s Data object to share data between a few overrides. There was also this better option, CreateStore, but it was a hassle to use it (finding the code, copying it to an additional file, importing that file,…).

But now, because we’re using ES modules, there’s a version of CreateStore that you can import with one line:

import type { ComponentType } from "react"
import { createStore } from "https://framer.com/m/framer/store.js@^1.0.0"

const useStore = createStore({ isHover: false })

export function Background(Component): ComponentType {
    return (props) => {
        const [store, setStore] = useStore()

        return (
            <Component
                {...props}
                …
Snippet from 25. Colors: Switching between states

CreateStore introduction

How you use it: Like with the Data object, you first create the store with the necessary variables. Here it’s just one: a Boolean called isHover:

import type { ComponentType } from "react"
import { createStore } from "https://framer.com/m/framer/store.js@^1.0.0"

const useStore = createStore({ isHover: false })

export function Background(Component): ComponentType {
    return (props) => {
        const [store, setStore] = useStore()

        return (
            <Component
                {...props}
                …

This useStore function is now a hook that you call in the overrides that need access to the data.

import type { ComponentType } from "react"
import { createStore } from "https://framer.com/m/framer/store.js@^1.0.0"

const useStore = createStore({ isHover: false })

export function Background(Component): ComponentType {
    return (props) => {
        const [store, setStore] = useStore()

        return (
            <Component
                {...props}
                animate={{
                    backgroundColor: store.isHover ? "#60f" : "#20a5f6",
                }}
                transition={{ duration: 0.5 }}
            />
        )
    }
}

export function Colors_Switching_between_states(Component): ComponentType {
    return (props) => {
        const [store, setStore] = useStore()

        return (
            <Component
                {...props}
                animate={{
                    scale: store.isHover ? 0.8 : 1,
                    rotate: store.isHover ? 90 : 0,
                }}
                onHoverStart={() => setStore({ isHover: true })}
                onHoverEnd={() => setStore({ isHover: false })}
            />
        )
    }
}

Inside each override, you can now read the existing values on store; and change them with setStore():

import type { ComponentType } from "react"
import { createStore } from "https://framer.com/m/framer/store.js@^1.0.0"

const useStore = createStore({ isHover: false })

export function Background(Component): ComponentType {
    return (props) => {
        const [store, setStore] = useStore()

        return (
            <Component
                {...props}
                animate={{
                    backgroundColor: store.isHover ? "#60f" : "#20a5f6",
                }}
                transition={{ duration: 0.5 }}
            />
        )
    }
}

export function Colors_Switching_between_states(Component): ComponentType {
    return (props) => {
        const [store, setStore] = useStore()

        return (
            <Component
                {...props}
                animate={{
                    scale: store.isHover ? 0.8 : 1,
                    rotate: store.isHover ? 90 : 0,
                }}
                onHoverStart={() => setStore({ isHover: true })}
                onHoverEnd={() => setStore({ isHover: false })}
            />
        )
    }
}

If you’re wondering what the above code does:

25. Colors: Switching between states

Don’t drop Data just yet.

But you can still use the Data object. You only need to add an observer function (importable from the Framer Library) so that Framer’s Preview knows you’re using it.

The older code from that same example will continue to work thanks to useObserveData():

import { Override, Data, useObserveData } from "framer"

const appState = Data({ isHover: false })

export function Background(): Override {
    useObserveData()

    return {
        animate: { backgroundColor: appState.isHover ? "#60f" : "#20a5f6" },
        transition: { duration: 0.5 },
    }
}

export function Colors_Switching_between_states(): Override {
    useObserveData()

    return {
        animate: {
            scale: appState.isHover ? 0.8 : 1,
            rotate: appState.isHover ? 90 : 0,
        },
        onHoverStart() {
            appState.isHover = true
        },
        onHoverEnd() {
            appState.isHover = false
        },
    }
}

(Add an useObserveData() call in every override that uses the data.)


One comment on “Framer for Developers”

Leave a Reply