Designing a Switch in Framer X, Part III
A crash course in code components with the new Framer X API.
Introduction
Welcome to the third and final part of the Designing a Switch in Framer, a short tutorial series where we’ll be creating and using a code component in Framer X.
In Part I, we:
- Set up our Framer X workspace.
- Created a code component for our Switch
- Defined our component’s state logic
- Presented our component’s state using animations
In Part II, we:
- Added props to our Switch, so that we can set its state from outside of the component
- Added property controls for our Switch, so that we can set our component’s props through Framer’s user interface
In Part III, we’ll:
- Give our Switch an event handler prop, so that we can share its state with the rest of our project
- Use overrides to change a second component when our switchy Switch switches
If you’re new to Framer X (or to React), then this tutorial is a great introduction to working with code. I’d recommend completing Part I and Part II before working through this one.
Getting Started
We’ll be making this project with Framer X. If you don’t have it already, download a copy and enjoy the 14-Day trial.
If you’d like to pick up where we left off, click here to download the finished project from Part II of this tutorial.
Sharing state with an event handler
In the last article, we learned about how to get information into our component from the outside world through props. However, while our switch can now receive information, it still has no way of sharing information with other components. In other words, our switch can be switched, but it can’t switch anything else!
In order to give other parts of our project a way to respond when the user flips our switch, we’ll need to give our component an event handler prop.
Event handlers are callback functions that a component will call when an event occurs. Components will usually call the event handlers with arguments containing information about the event that has occurred. In Framer X, Frames and other components use events as a way to respond to user interactions.
Adding our event handler
We’ll call our event handler onValueChange
. Let’s start by adding onValueChange
to our component’s default props.
- With our project open in Framer X, open up the code tab by selecting the code icon on the tab bar or by pressing
Command + 4
. - In the list of files, select our component’s file,
Switch.tsx
. - In our component’s file, find our component’s
defaultProps
definition. It’ll be down below our component. - In our component’s
defaultProps
, add a new property namedonValueChange
. Set it to a function that accepts a boolean argument calledisOn
and returnsnull
.
import * as React from "react"
import { Frame } from "framer"export function Switch(props) {
...
}Switch.defaultProps = {
isOn: false
onValueChange: (isOn: boolean) => null
}
Event handlers are usually named by putting
on
before the name of the event. For example, a “click” event handler will usually be namedonClick
, a “crash” event handler will usually be namedonCrash
, and so on. However, beyond making our components easier to use, the names we use have no effect on how the function works.
Calling the event handler
Next, let’s call props.onValueChange
each time our switch flips.
- Back in our component, find the
flipSwitch
function. - Above our
setState
call, insert a line that callsprops.onValueChange
with the new value ofstate.isOn
.
const flipSwitch = () => {
props.onValueChange(!state.isOn) setState({
isOn: !state.isOn,
})
}
Take another look at this function. Notice that we’re writing !state.isOn
twice? Repetition is usually a sign that our code is getting “messy”. Cleaning up messy code (or refactoring) is part of learning to code!
- Can you refactor our
flipSwitch
function so that we only write!state.isOn
once?
Testing our event handler
Now that we’ve added our event handler, let’s make sure that it’s doing what we want.
- Back in our
defaultProps
, change the defaultonValueChange
function to log a message to the console as shown below. - Next, open the Preview.
- Open the console by selecting Show Console from the preview’s menu or by pressing
Command + I
.
Switch.defaultProps = {
isOn: false
onValueChange: (isOn: boolean) => console.log(isOn)
}
The console is a great way to check whether your project is doing what you expect it to do. You can
console.log
anything, giving you just enough feedback to solve your bugs.
If all’s going well, we’ll see a readout of the argument (!state.isOn
) that we’re using when we call props.onValueChange
from our flipSwitch
function.
We’ll be using the console again in the next steps, so let’s clean it up by removing our call to console.log
.
- Go back to our component’s
defaultProps
. - Turn our
onValueChange
function back to(isOn: boolean) => null
.
Finishing touches
Believe it or not, we’re done with our component! Or at least, we won’t be making any more changes to it in this tutorial is concerned. If you’d like to keep working on the switch, here are some challenges:
- Can you create a prop that controls the color of the Switch’s container frame when the switch is on? Can you create a property control for this prop?
- Can you add a disabled state to the switch?
- Can you change the size of the component’s Frames so they respond to the component’s height and width on the canvas?
While our switch is done, this tutorial isn’t! Before we wrap things up, let’s actually use our switch to control something in our project.
Overrides
In Framer X, overrides are a way to connect parts of our design together.
On the most basic level, overrides allow us to merge new props into the existing props of our design’s components. They give us a way to change (or override) properties that we’ve set on the canvas, like a Frame’s height
and width
, but they also let us set props that can’t be set from the canvas, such as onTap
. Overrides only take effect in the Preview.
In order to really make overrides useful, we need to use them together with the Data object. The Data object is similar to our component’s state
but designed to work with overrides: whenever we change its values, every override will run again, each merging new props to their connected components.
Don’t worry if you don’t get it yet! As usual in Framer X, we’ll learn by doing.
Creating an overrides file
Let’s create a new file where we’ll write our overrides.
- Return to the canvas.
- Click on our Switch instance to select it.
- On the properties panel, click on the section labeled Override.
- In the Files dropdown, select New File.
- Now click the Edit Code button.
This will open up a new overrides file named App.tsx
.
Like our component’s file (
Switch.tsx
), an overrides file is a.tsx
file that exports different functions to the rest of our project.
Creating our data state
Let’s start by creating a Data object to hold our prototype’s state. In this example, we’ll be using our switch to control our design’s “theme”, which can be either day or night.
- At the top of our
App.tsx
file, addData
to the list of imports from the"framer"
library. - Delete the rest of the code in the file.
- Create a new Data object as shown below.
import { Override, Data } from "framer"const data = Data({
theme: "day"
})
Overriding our device Frame
Next, let’s create an override for our device Frame. Our device Frame is the very first Frame we made in this tutorial: it sits on our canvas and contains our Switch instance.
- In our overrides file, delete the default override.
- In its place, create a new override named
DeviceFrame
as shown below.
In order for Framer to recognize our overrides, we need to give the function the
Override
type andexport
it from the file.
import { Override, Data } from "framer"const data = Data({
theme: "day"
})export function DeviceFrame(props): Override {
return {
backgroundColor: "#222222"
}
}
This is the most “basic” override: a function
that gets called with the props
of its connected Frame, and that returns an object of new props. When we preview a component that is connected to an override, Framer will call the override function, get back the new props, and then merge those props into the connected component’s original props. Framer will then pass the result back to the component as props.
To see it working, let’s connect the override to our device Frame.
- Back on the canvas, select the device Frame.
- On the properties panel, select our new override from the Override dropdown.
- Open the Preview to see the results.
Interesting! Our device Frame’s background is white on the canvas but — thanks to our override — an elegant black in our Preview.
As nice as this is, it doesn’t match our theme! According to our data
state, our theme is currently "day"
. We only want our device Frame to be black when our theme is “night”
.
Making our override conditional
Let’s go back and change our override so that the background
prop it returns is either black or white, depending on the value of data.theme
.
- Go back to the code tab.
- If it isn’t already selected, select our overrides file (
App.tsx
) in the list of our project’s code files. - In our
DeviceFrame
override, remove thebackground
property from the override’s set of returned props. - In its place, add a set of
variants
forday
andnight
as shown below. - Add an
initial
andanimate
property and point both properties to the value ofdata.theme
.
import { Override, Data } from "framer"const data = Data({
theme: "day",
})export function DeviceFrame(props): Override {
return {
variants: {
day: {
backgroundColor: "#FFFFFF"
},
night: {
backgroundColor: "#222222"
}
},
initial: data.theme,
animate: data.theme,
}
}
This code should look familiar: it’s almost identical to the code we used to define our component’s variants in Part I of this tutorial.
Open the Preview again and check out the results. If your code looks like mine, then the device Frame should be back to having a white background.
To see if it’s all working, try changing the value of data.theme
from "day"
to "night"
. Did your device change also change its background color from white to black?
Overriding our Switch
Now that our device Frame’s background color is connected to our data state, let’s move on to our Switch instance. We want our switch to be on when theme.mode
is "night"
and off when theme.mode
is “day”
.
You’ve already learned enough to write this one yourself. I’ll still include the steps below, but give it a shot on your own first. Good luck!
- In our overrides file, create a new override and name it
ThemeSwitch
. If that name doesn’t appear, feel free to use another— the names of our overrides don’t matter, so long as they’re unique. - In the props that our override returns, set the
isOn
prop with an expression that will evaluate totrue
ifdata.theme
is"night"
, or else tofalse
. - Back on the canvas, connect our Switch instance to the new override.
export function ThemeSwitch(props): Override {
return {
isOn: data.theme === "night",
}
}
Now open the Preview again and let’s see what’s changed.
Changing our data state
We’re almost done! All that’s left is to make flipping our Switch between on and off also flip our theme between day and night. To get this done, we’ll use our component’s onValueChange
event handler.
- In our
ThemeSwitch
override, add theonValueChange
property to the props that this override returns. - Set this property to a callback function that accepts
isOn
as an argument. - Inside of this function, use the value of the
isOn
argument to setdata.theme
to either"day"
or“night”
.
export function ThemeSwitch(props): Override {
return {
isOn: data.theme === "night",
onValueChange: isOn => {
data.theme = isOn ? "night" : "day"
},
}
}
And when we preview…
Our little Switch is finally doing some work!
Our finished component
Let’s take another look at our component.
As before, we’ll walk through each part that we’ve added. Anything not covered below is code that we’ve already covered in Part I or Part II.
Line 18
: In our flipSwitch
function, we’ve added a line that calls props.onValueChange
. We’re calling this function with the value of state.isOn
.
Line 63
: We’ve also onValueChange
to our component’s defaultProps
. The default value is a function that does nothing.
Our finished overrides
Let’s also walk through our overrides file.
Line 1
: On the first line of our overrides file, we’ve imported the Override
type alias and the Data
function from the "framer"
library.
Lines 3 - 5
: Here we’re creating a Data object and assigning it to the variable data
. Our data object is keeping track of a single property theme
that has the initial value "day"
.
Lines 7 - 20
: This is our override for our device Frame. It returns a set of variants
, day
and night
, and uses data.theme
to determine both which variant to use when the connected component first mounts (its initial
variant) and which variant the component should animate
to when the component re-renders.
Lines 22 - 29
: Finally, we’ve created an override for our Switch. It overrides the connected component’s isOn
with a value that is conditional on data.theme
, returning true
when data.theme
is “night”
and false
when data.theme
is “day”
. It also overrides the onValueChange
prop with a callback function that sets the value of data.theme
to “night”
if the switch was turned on or “day”
if it was turned off.
And as a final note, let’s review how our prototype works:
- Flipping our switch will change the value of
data.theme
. - Because
data
is a special Data object, this change will cause each of our overrides to run again. - When they run, any expressions that point to
data.theme
will be solved with that property’s current value, returning different props depending on whether that value is“day”
or“night”
.
To download the finished project, click here.
Hey, you’ve made it to the end. Congratulations! Whether you’re new to code or just new to Framer X, I hope you’ve enjoyed taking some first steps toward designing interactive components and prototypes.
If you liked this tutorial, please help spread the word by smashing that clap button! If you have questions, add them below or tweet me at @steveruizok. We can also connect on Framer’s Slack and Spectrum communities.
Keep an eye out for more tutorials and walkthroughs coming soon!