Designing a Switch in Framer, Part I
A crash course in code components with the new Framer X API.
Introduction
Welcome to the first 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’ll:
- Set up our Framer X workspace.
- Create a code component for our Switch
- Define our component’s state logic
- Present our component’s state using animations
If you’re new to Framer X (or to React), then this tutorial is a great introduction to working with code.
- We’ll take it slow, explaining each step as we go.
- At the end of the article, we’ll walk back through each part of the finished component code.
If you’d prefer to explore for yourself, you can download the finished project and hack away. You can also jump directly to Part II and Part III.
What are we making?
In this article, we’ll be designing a Switch, a user interface element used to turn some property on or off.
Chances are you’re familiar with the Switch pattern already — it is one of the most common elements of a mobile interface. While the component’s mechanics are pretty simple, a Switch is still a tricky element to design.
A well-designed switch should communicate:
- that the switch has two mutually exclusive states, on or off
- which state the switch is currently in
- that the user can interact with the switch
- that a change has occurred to the switch’s state
As we put together our component, the code we write will fall into two categories: controlling the logic behind the Switch’s state — whether the switch should be on or off — and controlling the presentation of that state to the user.
Setting up
Let’s get started in Framer X. If you don’t have it already, download a copy and enjoy the 14-Day trial.
- Open up the Framer X app.
- Update to the latest version, if prompted.
- Start a new project.
Creating a device Frame.
The first thing we’ll do is create a Frame where our component will live.
- Select Tool > Frame in the Menu (or press
F
on your keyboard) - Select a Device from the panel that appears on the right side of the screen.
Selecting a device will automatically create a Frame on the canvas set size of that device’s screen. Later, when we preview our Frame, we’ll see our design as if it were displayed on that device’s screen.
Creating our code component
Next, we’ll actually create our code component.
- From the left sidebar, click the code tab.
- At the top of the empty files list, click the New button.
- Enter a name for our new component.
- Click New Code Component to create the component.
Creating a Switch instance.
Once you’ve created a component, you’ll be taken to that component’s code. However, before we jump into the code, let’s create an instance of our component on the canvas.
Code components work like design components — or symbols if you’re coming from Sketch. While we only define our component once, we can create as many instances of it as we want.
- From the left sidebar, click the Components tab (the four boxes). Our new component should be sitting at the top of the components list.
- To create an instance, click the component and drag it onto the Frame that we’ve just made.
Previewing our Frame
We’ll want to see our component as we work on it, so let’s also set up our Preview window.
- Click on the Device Frame
- Open the Preview by clicking the play button (or right-facing triangle) in the top-right corner. You can also press
Command + P
on your keyboard. - Click the lock icon at the top of the Preview window to lock the preview to this Frame.
- Position your windows so that we can see our Preview window and Framer’s main window at the same time.
Cleaning out the boilerplate
Now that our windows are set up, let’s go back to our component’s code.
- On the canvas, select our component instance (the big blue rectangle).
- Click the Edit Code button on the properties panel.
We want to start almost from scratch, so clear out the component’s existing code until it looks like this:
import * as React from "react"
import { Frame } from "framer"
export function Switch() {
return (
<Frame size="100%"/>
)
}
We’re all set up! We can finally — but cozily!— start writing our component.
Our component’s state
Like all React components, our Switch will respond to two sources of data: props and state.
Props are data that comes from the outside world. If a component is a function — and it technically is — then props is an argument passed to that function. If a component is a pizza, then props are the pizza order — toppings, crust, etc.
State is data that the component manages internally. To reuse our pizza example, we could think of the pizza’s temperature as part of its state, changing over time without any new external input.
We’ll work with props in Part II of this series. For now, let’s focus on the component’s state.
Creating the component’s state
Our component’s state is data that the component manages itself. In our case, we’ll want our component’s state to keep track of whether the Switch is on or off.
- At the top of our component, create a
useState
hook. - Define the hook’s initial state as an object with a single property,
isOn
, set asfalse
. - Destructure out the
state
variable and thesetState
function, as shown below.
For this project, we’ll use a boolean property,
isOn
, to track whether the switch is “on” or “off”. We can think of this property as a statement — “The Switch is on” — which will either betrue
if the Switch is on orfalse
if it isn’t.
export function Switch(props) {
const [state, setState] = React.useState({
isOn: false,
})
return (
<Frame size="100%"/>
)
}
Flipping the Switch
Our Switch should know how to flip, so let’s add that routine.
- Below our
useState
hook, create a function namedflipSwitch
. - In this function, use our hook’s
setState
function to set a new state. - In the new state, define the
isOn
property as “not” the currentisOn
value, as shown below.
The
!
operator will return the opposite of whatever follows it. You’ll see this a lot in JavaScript: for example, if===
is “equals”,!===
is “not equals”.
export function Switch(props) {
const [state, setState] = React.useState({
isOn: false,
})
const flipSwitch = () => {
setState({
isOn: !state.isOn,
})
}
return (
<Frame size="100%"/>
)
}
Setting the tap event
Next, let’s tell our Frame to run flipSwitch
when the user taps on the Frame.
- In the JSX that our component returns, give the Frame an
onTap
prop. - Point this
onTap
prop to ourflipSwitch
function.
The
onTap
prop is one of several events that the Frame component supports. We define event props by passing the Frame a callback function. Later, if the Frame detects the event (in our case, a tap), it will call the function with information about what happened.
export function Switch(props) {
const [state, setState] = React.useState({
isOn: false,
})
const flipSwitch = () => {
setState({
isOn: !state.isOn,
})
}
return (
<Frame size="100%" onTap={flipSwitch}/>
)
}
Creating a lo-fi presentation
In order to that we can see that everything is working, let’s throw in a quick and dirty way to see the value of our state.
- Break the self-closing
<Frame/>
into “opening” and “closing” tags,<Frame>
and</Frame>
. - Between these tags, add an expression that turns our
state.isOn
value from a boolean (false
) into a string (“false”
), so that we can display it as text.
In JSX, elements that don’t have any children will show up as self-closing, like
<Frame height="200"/>
. Elements that do have children will need both an opening and closing tag, like<Frame height="200">
and</Frame>
, with the children in between. Note that we only set props on the element’s opening tag.
export function Switch(props) {
const [state, setState] = React.useState({
isOn: false,
})
const flipSwitch = () => {
setState({
isOn: !state.isOn,
})
}
return (
<Frame size="100%" onTap={flipSwitch}>
{ state.isOn.toString() }
</Frame>
)
}
Our state is working! If we click on our component, we can see the state.isOn
switching between true and false.
Presenting our State
Now that we have our state worked out, let’s move on to presenting this state to our user. Our requirements call for much more than the blue box that we currently have.
Styling our container Frame
Our component will have two Frames — a round “knob” and a “container” in which the knob will move. We’ll use our current Frame as the container.
- In our JSX, set the container Frame’s
height
andwidth
props with a more appropriate size. - To center the container Frame, add the
center
prop. - Round the corners by using the
radius
prop. To get the pill shape we want, set this prop to half of the container Frame’sheight
.
return (
<Frame
height={50}
width={80}
radius={25}
center={true}
onTap={flipSwitch}
>
{state.isOn.toString()}
</Frame>
)
Creating our knob Frame
Next up, let’s replace our placeholder state text with a second Frame for the component’s knob.
- Replace the
{state.isOn.toString()}
line with a new Frame instance. - Set this Frame’s
height
to just less than the height of the container. - Set the
top
andleft
props to give this Frame a margin. - Set the
radius
prop to"100%"
, which will make this Frame into a circle. - Set the
background
prop to a different color (white in my examples), so that we can see it better.
Framer X uses the same color definitions as HTML and CSS. In the example below, we’re using a hex code to define our color. Learn more about different ways of representing color in your code.
return (
<Frame
height={50}
width={80}
center
radius={25}
onTap={flipSwitch}
>
<Frame
size={46}
top={2}
left={2}
radius={"100%"}
background="#FFFFFF"
/>
</Frame>
)
If all’s gone well, we should have something looking like this:
Creating our variants
In the last step, we removed the line {state.isOn.toString()}
. Up to now, this has been our only way of knowing whether our Switch was “on” or “off”, so removing it has left us in the dark. If we click on the Switch now, nothing happens — at least, nothing we can see.
In order to represent this state visually again, we’ll create two different variants for our knob and container.
A variant is a group of properties that define a visual state for a Frames. A Frame can only be in one variant at a time, but it can switch between variants at different times.
Defining our container’s variants
Let’s start by defining the variants on our component’s container Frame. We want the container to be grey when the Switch is off and blue when the switch is on. We’ll use two variants, each of which will define one of these visual states.
- Set the
variants
prop of the container’s Frame to an object with two object properties,on
andoff
. - Set the
off
variant to a greybackground
. - Set the
on
variant to a bluebackground
.
return (
<Frame
height={50}
width={80}
center
radius={25}
onTap={flipSwitch}
variants={{
off: { background: "#BBBBBB" },
on: { background: "#0070DF" },
}}
>
<Frame
size={46}
top={2}
left={2}
radius={"100%"}
background="#FFFFFF"
/>
</Frame>
)
Setting our knob variants
Next, create a set of variants for the knob. We want the knob to be at the left side when the Switch is off, and the right side when the switch is on.
- Set the
variants
prop of the knob’s Frame to an object with two object properties,on
andoff
. - Set the
off
variant to anx
of0
. - Set the
on
variant to anx
of30
.
If you’re feeling math-y, we’re calculating
30
by taking the container’swidth
, subtracting the knob’swidth
, then subtracting twice the knob’s horizontal margin, or itsleft
prop.
return (
<Frame
height={50}
width={80}
center
radius={25}
onTap={flipSwitch}
variants={{
off: { background: "#BBBBBB" },
on: { background: "#0070DF" },
}}
>
<Frame
size={46}
top={2}
left={2}
radius={"100%"}
background="#FFFFFF"
variants={{
off: { x: 0 },
on: { x: 30 },
}}
/>
</Frame>
)
Setting our initial variant
To start with, let’s use state.isOn
to determine the component’s initial variant.
- Set the container Frame’s
initial
prop to an expression that will crunch to either"on"
or"off"
, depending on the value ofstate.isOn
.
In the example below, we’re using JavaScript’s ternary operator. You’ll see the ternary operator a lot in JSX code: an expression
a ? x : y
will evaluate tox
ifa
is true, or elsey
ifa
is false.
return (
<Frame
height={50}
width={80}
center
radius={25}
onTap={flipSwitch}
variants={{
off: { background: "#BBBBBB" },
on: { background: "#0070DF" },
}}
initial={state.isOn ? "on" : "off"}
>
<Frame
size={46}
top={2}
left={2}
radius={"100%"}
background="#FFFFFF"
variants={{
off: { x: 0 },
on: { x: 30 },
}}
/>
</Frame>
)
If all’s gone right, we should be able to test by changing our component’s initial state. Back in our component’s useState
hook, change { isOn: false }
to { isOn: true }
and see what happens.
Notice that our knob Frame is also switching its variant to either
on
oroff
, just like the container Frame, even though we haven’t set itsinitial
prop. Unless specifically given a variant of their own, children will “inherit” their parent’s current variant name. Learn more.
Animating to the right Variant
Once we have our initial variant set, we’ll use a different prop, animate
, to define which variant the component should use when the user flips the switch.
- Set the container Frame’s
animate
prop to an expression that will crunch to either"on"
or"off"
, depending on the value ofstate.isOn
.
The
initial
prop defines which variant the Frame should use for its first render. Theanimate
prop defines which variant the Frame should use for every other render. (Remember that a component will render once when it first loads and then re-render each time its state or props change).
return (
<Frame
height={50}
width={80}
center
radius={25}
onTap={flipSwitch}
variants={{
off: { background: "#BBBBBB" },
on: { background: "#0070DF" },
}}
initial={state.isOn ? "on" : "off"}
animate={state.isOn ? "on" : "off"}
>
<Frame
size={46}
top={2}
left={2}
radius={"100%"}
background="#FFFFFF"
variants={{
off: { x: 0 },
on: { x: 30 },
}}
/>
</Frame>
)
Great! Our component now knows two things:
- which variant to show when the component first loads
- how to animate between variants when the component changes its state
Tweaking the transition
Before we’re done, we should dial in that animation — a springy knob doesn’t just really feel right. We can adjust every aspect of a Frame’s animation by using a transition.
- Set the container Frame’s
transition
prop to an object. - Add a
type
property with the value"tween"
. - Add a
duration
property with the value0.2
, or one-fifth of a second. - Copy this
transition
prop to the knob Frame, too.
There are two main types of transitions, tween and spring, each of which is defined by a different set of properties.
return (
<Frame
height={50}
width={80}
center
radius={25}
onTap={flipSwitch}
variants={{
off: { background: "#BBBBBB" },
on: { background: "#0070DF" },
}}
initial={props.isOn ? "on" : "off"}
animate={state.isOn ? "on" : "off"}
transition={{
type: "tween",
duration: 0.2,
}}
>
<Frame
size={46}
top={2}
left={2}
radius={"100%"}
background="#FFFFFF"
variants={{
off: { x: 0 },
on: { x: 30 },
}}
transition={{
type: "tween",
duration: 0.2,
}}
/>
</Frame>
)
That feels better. Feel free to keep tweaking the design and its animation until it matches your own tastes, but I’m calling this one done!
Our finished component
Let’s take another look at our component. We’ll read it over and then go through point by point of how it works. If you’re lost about what any of the code is doing, here’s your chance to catch up.
Lines 1 – 2
: At the top of the file, we import the entire React library, as well as all of the elements we need from the “framer”
library.
Lines 4 – 50
: Here’s where we create our component itself. The component is a function that returns JSX.
Lines 5 – 7
: We use React’s useState
hook to define our component’s initial state. It gives us back our state
and a function useState
that we can use to update that state. We use state.isOn
to track whether the Switch is on or off.
Lines 9 – 13
: Here we create a function flipSwitch
to update our state when the user flips our switch. Inside of that function, we call useState
and pass in a new state with a flipped value for state.isOn
.
Lines 15 – 49
: This is all JSX that our component returns. That JSX is made up of two React elements:
Lines 16 — 48
: a Frame for our Switch’s containerLines 29 — 37
: a Frame as our Switch’s knob
Line 21
: Here we set an event so that will run flipSwitch
when the user taps on the container Frame.
Lines 22 — 25
: We define our container Frame’s visual states using a pair of variants named "on"
and "off"
.
Line 26
: Next, we tell our container Frame to start in the "on"
variant if the component’s initial state.isOn
is true
, otherwise to start in the "off"
variant.
Line 27
: We also tell our container Frame to animate to a different state, using the same logic as in the previous step, for any future renders. (The Frame will render each time its state changes.)
Lines 28 — 31
: Here we define our animation’s transition, a tween animation that takes .2
seconds to complete.
Lines 39 — 42
: We also define two variants on our knob Frame. Because this Frame is the child of our container, the changes we make to the container’s variant (using the initial
and animate
props on lines 26
and 27
) will also set the knob Frame’s variant.
Lines 43 — 46
: We also define a transition for our knob’s animation.
To download the finished project, click here.
Next Step: Props
While our component works, it does have some limitations. You may have thought of these already:
- What if I want some Switch instances to start on but others to start off?
- What if I want to disable the Switch?
- How can I use the Switch to control other components?
- How can I use other components to control the Switch?
In the next tutorial, we’ll set up props for our Switch, so that we can control it from outside. In the third and final part, we’ll learn how we can alert the rest of the project when our Switch’s state changes.
If you liked this article, smash that clap button! 👏👏👏