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?

Writing code in Framer now works better and a bit differently, but how come? It’s because they started loading 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. All of them 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 actually 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 really necessary. Most browsers can load those packages, in the form of ES modules, directly from a URL. Framer started to make use of 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"

(Granted, we’re still figuring out the URLs to the coolest packages, but the Framer team will probably publish some popular ones.)

Likewise, smart and code components you create are now easier to share between projects because you can import them from a https://framer.com/m/… URL (through team library or by copy/pasting).

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, when using useState(), useEffect(), or other React utilities, you’ll have to import them. 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 }
This component will be 150 by 150 points when you drag it onto the canvas.

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

Framer Motion is included in the Framer Library. So we could import its motion component and utilities like this:

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

You can now import those goodies directly 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 in code components.

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",
}

A Stack is just a CSS Flexbox, though. So you can easily recreate it with some CSS. Here’s a simple, vertical one that lets you select layers (component instances) on the canvas (and it supports flex-wrap 🤓).

import * as React from "react"
import { addPropertyControls, ControlType } from "framer"

export default function Vertical_Flexbox(props) {
    const { style, align, gap, children, fill, ...rest } = props

    const childCount = React.Children.toArray(children).length

    return (
        <div
            {...rest}
            style={{
                width: 200,
                height: 200,
                ...style,
                backgroundColor: fill,
                display: "flex",
                flexDirection: "column",
                justifyContent: "flex-start",
                alignItems: align,
                flexWrap: "wrap", // This line adds wrapping!
            }}
        >
            {React.Children.map(children, (child, i) =>
                React.cloneElement(child, {
                    style: {
                        ...child.props.style,
                        marginBottom: i < childCount - 1 ? gap : 0,
                        position: "relative",
                    },
                })
            )}
        </div>
    )
}

addPropertyControls(Vertical_Flexbox, {
    align: {
        type: ControlType.Enum,
        title: "Align",
        options: ["flex-start", "center", "flex-end"],
        optionTitles: ["Left", "Center", "Right"],
        displaySegmentedControl: true,
        defaultValue: "center",
    },
    gap: {
        type: ControlType.Number,
        title: "Gap",
        min: 0,
        defaultValue: 10,
    },
    children: {
        type: ControlType.Array,
        title: "Children",
        control: { type: ControlType.ComponentInstance },
    },
    fill: {
        type: ControlType.Color,
        title: "Fill",
        defaultValue: "#9ef",
    },
})

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, which will give 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 that 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 { width: "100%", height: "100%" } through style).

Importing smart components

You can now easily import Smart Components.

Remember, when we were still using Design Components, you could import them from a special canvas.tsx file:

import * as React from "react";
import { DesignComponent } from "./canvas";

export default function MyComponent(props) {
    return (
        <div { ...props }>
            <DesignComponent />
            <DesignComponent />
            <DesignComponent />
        </div>
    );
}
This will still work in legacy projects containing design components.

To import a Smart Component, you just select it in the editor’s Input Menu, which will insert the import code at the top of your code file:

import SmartComponent from "../canvasComponent/gk_xGVzhV"

export default function MyComponent(props) {
    return (
        <div { ...props }>
            <SmartComponent />
            <SmartComponent />
            <SmartComponent />
        </div>
    );
}

Importing another code component

To import an existing code component in the one you’re building, you first head to that component’s file and click its ‘Copy Import’ button. Here’s one called AnotherCodeComponent():

And then, back in the new component’s file, you can paste that import line and start using <AnotherCodeComponent>:

import { AnotherCodeComponent } from "https://framer.com/m/AnotherCodeComponent…"

export default function MyComponent(props) {
    return (
        <div {...props}>
            <AnotherCodeComponent />
            <AnotherCodeComponent />
            <AnotherCodeComponent />
        </div>
    )
}

This ‘Copy Import’ button gives you a full URL on the framer.com domain:

https://framer.com/m/AnotherCodeComponent-iuTc.js@ut4NhVenniZFVAnXbBsr

…which means that this code component can indeed be imported anywhere, also in other projects.

Versioning

The URL will refer to the component as it was the moment you clicked ‘Copy Import’. If you change it and click ‘Copy Import’ again, you’ll get a new URL (the string after @ defines the version).

And when you use the URL without the version string, like this:

https://framer.com/m/AnotherCodeComponent-iuTc.js

… you’ll always get the latest version of the component, just like when importing locally.

Local import

You can also import a code component’s file directly, as we did before. Just remember that you now must add the .tsx extension. Importing like this will (obviously) always give you the current version of the component.

import { AnotherCodeComponent } from "./AnotherCodeComponent.tsx"

export default function MyComponent(props) {
    return (
        <div {...props}>
            <AnotherCodeComponent />
            <AnotherCodeComponent />
            …

I left 👆 this example orange because there’s a catch when you import with a local file path: The component you’re importing can not have an export default:

export function AnotherCodeComponent(props) {
    return (
        <motion.div
            style={{
                width: 200,
                height: 200,
                ...props.style,
                backgroundColor: "#07F",
            }}
        >
            <div style={{ width: 100, height: 100, backgroundColor: "red" }} />
        </motion.div>
    )
}

(When the component has an export default, the component importing it will not appear in your project’s components list.)

Importing external components or libraries

All your smart and code components are now ES modules that can easily be imported everywhere. And you can do the same with other ES modules: when a component or library is downloadable in ES module format from an HTTPS path, you can use it.

Sharing a code component

Just like with smart components, you can now also add code components to your team’s library. This way, other users can find it in the Input Menu and place it directly on the canvas.

Click ‘Add to Library’ to add the code component to your team library.
A component already in it will have an ‘In Library’ button. Click it to unpublish it.

Export as default

You could, in theory, export several components from the same .tsx file, so Framer requires that you do a default export when adding a component to the library. (The editor will warn you when you don’t.) A file can have only one default export, so this way, you’ll always import the correct component in another file.

In Code Overrides

Higher-order components

Here’s an override like we used to write them. This kind of overrides 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.

Almost always, you’ll 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, it’ll lose its position, size, styling, etc., and default to a transparent blue square of 200 x 200 points.

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

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"

It’s now better 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 (x, scale, rotate).

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 MotionValue), which in turn changes its scale and rotate (MotionValues 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, then the frame defaults to a default transparent blue style without border radius. Like here:

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
                style={{ x: x, scale: scale, rotate: rotate }}
            />
        )
    }
}

(Because you overrode its props.style with your own style property.)

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 extra file, importing that file,…).

But now, because we’re using ES modules, there’s a version of CreateStore that can be imported 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 variables you need. 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 actually 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