Making things happen in Framer X

Steve Ruiz
11 min readJul 5, 2019

--

About two years ago, I joined the Framer Slack channel to ask a question about a prototype. I got a great answer, so I stuck around — and have been helping to answer questions from other users ever since. (If you ever need a hand, you know where to find me.)

One of our most common questions is some variation on the following:

  • How do I make an animation start after a user taps on a Frame?
  • How do I make two Frames talk to each other?
  • How do I make a Frame respond to a click?

The question always boils down to this:

How do I make a user interaction with one component produce a change in a different component?

In this article, I’ll try to answer that question as best I can. If you’re new to Framer and want to follow along, download a copy and try out the 14-day trial.

Here’s how to do it

Let’s start with the solution.

In the example below, tapping on a Frame connected to the Button override will change the background color of a Frame connected to the Container override.

Our change involves three steps:

  1. We define a state.
  2. On our first Frame’s override, we return a value that is conditional on the value of that state.
  3. On our second Frame’s override, we set an event that changes the state.

That’s it! When we tap on the Button, we’ll flip the value of appState.nightMode, which will cause our overrides to run again, and this time the Container’s background will be a different color.

If you came here to just unblock yourself on a prototype, then feel free to skip back to your project. And if you want to poke around in the code, you can download the example project here.

But if you’re wondering why it works this way, and why we can’t just change the component’s color directly, then read on: the rest of this article is for you.

Run this code, but later

Let’s step away from Framer X for a moment and think more abstractly about what we’re doing when we program computers. Most of the time, programming means issuing commands to a machine: the code we write is a list of commands that we want the computer to execute. It reads the commands, starting from the top, and executes each of them in the order.

→ Draw a box at 0, 0→ Move the box to 20, 20.

However, if we want to create interactive programs, then we need a way to run certain commands later on in response to something a user did.

→ When the user clicks on the screen, draw a box at the clicked point.→ When the user clicks on the screen and drags, move the box to the dragged point.

Listening for events

In the wide wide world of programming for the world wide web, we call this type of code an event listener. An event listener is made up of three parts: a target, a type, and a callback function.

  • The target defines which element will receive the event. Are we listening for events on the whole page or just a button? Any element will do.
  • The type defines which event we’ll be listening for. Is it a click? A hover? In the browser, there are lots of events to choose from.
  • The callback function (or “listener”) contains the code to run when the target receives one of those events. It is a function that the computer “calls” with information about the event, such as the place on the screen where a user clicked.

Here’s an example event listener:

document.body.addEventListener("click", (event) => {
console.log(`User clicked at ${event.pageX} ${event.pageY}.`)
});

Let’s break it down: we first define the target, document.body. Like every element of a website, it has a method, addEventListener, that we can use to add the listener. We call this method with the other two parts: the type, “click”, and the callback, (event) => {...}.

Demo time

Let’s try that code out ourselves:

  1. Open a new tab in your browser
  2. Open the console by pressing Command + Option + J if you’re on Chrome, Command + Option + C if you’re on Safari, or Command + Option + K if you’re on Firefox.
  3. In the window that opens, click the Console tab
  4. Copy the event listener above, paste it in the console and press Enter.
  5. Click anywhere on the website.

Notice that the text that gets logged to the console is different each time, based on the part of the screen that you’ve clicked? Let’s take another look back at our callback to see how that works:

(event) => {
console.log(`User clicked at ${event.pageX} ${event.pageY}.`)
}

Our callback function accepts one argument, event. Each time we click, the browser runs this function with an object containing information about the event. We catch that object as our callback's event argument, which lets us use that information (including the pageX and pageY) in our callback’s code.

Events are a very powerful pattern, and they’re key to understanding how things happen on the web. The example above is about as “low” as we can go in the browser: everything else we’ll be looking at, including in Framer X, eventually boils down to this type of code.

However, as we’ll eventually see, this pattern has its problems — and there are some good reasons why we’d want to avoid having to write this code by hand.

Events in Framer Classic

Now that we know how events work, let’s talk about doing things with events. For this section, I’ll use examples from Framer Classic. Compared to the nests of parentheses and curly braces that come with regular JavaScript, events in Framer Classic should look much more clear.

layerA = new Layer

layerA.onTap (event) ->
print "User clicked at #{event.x}, #{event.y}"

If all that looks familiar, it’s because Framer Classic is essentially a more user-friendly interface for making things in the browser. Layers themselves are “shells” around browser elements that acted as translators, turning human-friendly code into the more complex code that the browser can actually run.

While the syntax is different, the parts are all the same: we have our target (layerA), our type (via onTap) and our callback function that receives information about the event.

Making things happen (in Framer Classic)

Let’s bring back our original question, adapted now for Framer Classic.

How do I make a user interaction with one Layer produce a change in a different Layer?

First, let’s look at how we would set a Layer’s background color.

layerA = new Layer

layerA.backgroundColor = "tomato"

If we wanted to wait until after we’ve tapped the Layer before changing the background color, we could put that command inside of an event listener:

In this example, we’re creating a new Layer instance and storing that instance under the variable layerA. On the very next line, we're changing layerA’s background color by setting its backgroundColor property to a new value, “tomato”.

layerA = new Layer

layerA.onTap (event) ->
layerA.backgroundColor = "tomato"

And if we wanted to make tapping on a different Layer produce the change, we’d put the command inside of a listener set on that other Layer instead.

layerA = new Layer

layerB = new Layer
y: 232
layerB.onTap (event) ->
layerA.backgroundColor = "tomato"

Easy! Well, up to a point.

While this is a very straightforward way to make things happen, it gets complicated fast. Next, we’ll look at where this way of working with events breaks down and start thinking about why Framer X does it a bit differently.

Where it gets tricky

In our previous examples, we’ve so far been responding events by reaching over to the thing that we want to change and changing it directly. While that’s fine for simple interactions, it means that we always have to do everything by hand.

This gets tricky when working with any kind of information state. This state could be anything from the current temperature to the number of replies to a comment thread: any information that we’re “presenting” to our user; and that, if the information were to change, would require us to update a part of our user interface to reflect that change. In other words, we would need to ensure that the user interface stays in sync with the information state.

As an example, let’s say we have an app a simple state: a night mode that can either be on or off, and a Layer with a background color that should reflect the night mode.

We might write something like this:

Our code has three parts:

  1. First, we define a variable for whether the night mode is on or off.
  2. Next, we create a Layer. We set its background color to either black or silver depending on whether the night mode is on or off.
  3. When we tap on the Layer, we toggle our night mode on or off.

When our project loads, layerA would use “silver” as its background color because nightMode’s value is false. So far, so good, but here’s the important question:

When we tap layerA and flip nightMode to true, what will happen to layerA’s background color?

Take another look at the code and come up with your answer. … If you guessed that layerA would change its color from silver to black, then I’m sorry, that’s not how it works.

But wouldn’t that be nice?

Artisinal state relationships

The right answer is that nothing would happen — at least, nothing we could see. While somewhere in our computer’s memory the bits storing nightMode’s value would be flipped from false to true, the background color for layerA would never change because we’re never changing it.

The actual code we need looks something like this:

Here we’ve added a new function, switchNightMode. In that function, we do two things:

  • We update our state.
  • We update our presentation to match that state.

We have to do this because in Framer Classic (as in regular JavaScript) our code doesn’t define relationships between state and presentation, just commands that run when we tell them to. So the solution is to run more commands when the state changes in order to keep it in sync with our presentation.

This is easy enough with only one element (layerA) but you can imagine the complexity of a project with a more complex state and many elements, each needing to be manually switched in response to changes in state.

Reacting to State Changes

As digital products became more complex, teams started demanding a way of building interfaces that could react automatically to changes in an app’s information state. Enter React, a framework developed at Facebook for building those kinds of user interfaces.

React does something similar to what Framer Classic did: it functions as a translator, accepting human-readable code and turning it into the more complex code that a web browser can actually run.

And it’s incredibly popular because, unlike regular JavaScript — and unlike Framer Classic — the code we write in React is all about declaring relationships rather than commands.

In fact, because we can define our elements (or “components”) in terms of their relationship to some information state, and because those components will update automatically whenever that state changes, the only commands we usually need to write are the ones where we change the state.

That’s great for everyone, including designers. Funny enough, though Framer Classic let users write their prototypes in CoffeeScript, the app itself was built with React. Framer X is also built with React — but it lets you build with React, too.

React in Framer X

Here’s a Framer X code component that shows the React version of our previous Classic example:

As in the previous example, we’re doing three things:

  1. We create a state to track whether night mode is on or off, with an initial value of false.
  2. In our component’s children, we define a Frame. For its background color, we write a “conditional expression” that will return either black or silver, depending on the value of nightMode.
  3. On the same Frame, we set a tap event listener that, when tapped, will toggle night mode on or off.

That’s all we have to do — React will take care of the rest.

When we tap on the Frame, we’ll change our nightMode state. This will cause the component to “update”, meaning its code will run again. This time, the value of nightMode will be different, so the expression nightMode ? "black" : "silver"} will return a different value.

State in Overrides

Our last example showed the pattern for a code component, but what about Overrides? Here’s the same example as above, but set as an override instead.

This works fine but it has one limitation: the nightMode state only matters within the override. If we wanted to connect more than one Frame to our state, or if we wanted to change that state from an interaction with a different Frame, then we’d have to put it somewhere else where both Frames could have access to it.

A higher state

So far, we’ve been creating our state using a React hook, useState. Those only work inside of components (or override functions), so we can’t just move it out into the “body” of our code.

Instead, we’d have to create our state using Framer’s Data object.

The Data object works according to the same principle as any state in React, but was designed just for overrides. Whenever it changes, all of the project’s overrides will update, passing new values down to their connected components.

Sharing our higher state

Now that we’ve moved our state out of our Container override, we can bring that state into more than one override function. And we can make changes to the state from events set in those other overrides, too.

And this finally brings us to our answer from the start of this article.

  1. We’ve defined a state.
  2. On our first Frame’s override, we’re returning a value that is conditional on the value of that state.
  3. On our second Frame’s override, we’re setting an event that changes the state.

And let’s walk through how it all works:

  • When we tap on the Frame connected to the Button override, the event’s callback will run, changing our state.
  • When our state changes, our overrides will update.
  • When the Container override runs, the value it returns for background will either be “black” or “sivler”, depending on the current value of appState.nightMode.

Wrapping up

Ok, this was a long one. Let’s recap what we’ve learned.

  • On the web, we use “events” to run commands at some later point, in response to user interactions like clicks or taps.
  • We can use events to run commands that directly make changes to the different elements in our design.
  • Making these kinds of changes can be tricky, especially when we’re trying to keep our design in sync with some kind of information state.
  • React gives us a way to sync our designs with an information state.
  • We can still use events…
  • …but our events only need to change the information state and React will take care of the rest.

That’s quite a lot, but I hope it leaves you with a better understanding of how to make things happen Framer X — and the advantages of doing things the React way.

If you have questions (or if you’ve spotted any bugs in my code), feel free to hit me up on twitter, Spectrum or on the Framer Slack. If you liked this article and want to see more like it, direct a shaped charge at that clap button, retreat to a safe distance, and leave a comment below. ✌️

--

--