Mobile Prototyping Fundamentals

30m

Intermediate

When I worked at Deliveroo, high-fidelity prototypes played a key role in our process and were integral to our user testing sessions. We strove for parity with our production apps, using real data from the area we were testing in (London, Dubai, Singapore, etc.).

As I began learning React and the capabilities of Framer X over Framer Classic, it became apparent how much time I could have saved when building my prototypes.

While some tutorials for Framer X focus on more granular subjects, I want to go through core workflows in the context of a larger mobile prototype—the way I learned as a designer.

So, for this guide I’ll touch on three key concepts:

  1. Advanced scroll interactions with overrides
  2. Creating code components with property controls
  3. Communicating between code components and overrides

You can download the starter and final files to follow along and compare as you go.

Setting Up

Open the file and take note of our basic buildings blocks. We have a single design component for our row, with design overrides to let us change the title and subtitle strings.

There’s already a scroll component setup in our Home frame, which links to a stack, housing a list of rows (our design components).

If you select the Home frame and hit CMD+P, you can preview our simple scroll view.

However, we’ll want to adjust the navigation and some other elements when we start scrolling. To do this, we’ll use overrides.

Advanced Scroll Interactions

As mentioned before, we want a few things to happen on scroll.

  1. Decrease the height of the navigation bar.
  2. Fade out the large header and fade in the small header (which is at 0% opacity in the navigation).
  3. Shrink the size and adjust the position of the search icon.

Using Overrides

Let’s create our first override file. Click the code icon, select New, and create a file called Scroll.tsx.

Delete the existing code, then at the top of your file, import Override, motionValue, and useTransform from the Framer Library.

import { Override, motionValue, useTransform } from "framer"
COPY

Assign motionValue to a variable called contentOffsetY, then create an override function called TrackScroll.

To see if everything is working, and to see the values from the scroll component, add console.log(contentOffsetY.get()) under an onScroll() event.

A value will be displayed in the console once we assign the override to our scroll component. We’ll use this to determine when we start animating the navigation elements and when we stop.

import { Override, motionValue, useTransform } from "framer"
const contentOffsetY = motionValue(0)
export function TrackScroll(): Override {
return {
contentOffsetY: contentOffsetY,
onScroll() {
console.log(contentOffsetY.get());
},
}
}
COPY

Now go back to your canvas and select your Scroll component. Assign the override on the properties panel, then preview (CMD+P) the Home frame.

Click the console icon in the top right of the preview window to see the console. Now you should be seeing values coming back on scroll.

We can do so many things with these. If you were are/were a Framer Classic user, we’re essentially about to start Modulating, or changing an element's properties based on another’s changing values.

MotionValue and useTransform

As stated before, we want to animate the height of the header from 144px to 88px, fade out our large heading text layer, and fade in the hidden text layer in the navigation.

We’ll do this by importing and using useTransform.

In the code below, we’re saying: Between the scroll values of 0 and -60 (which we chose from seeing the values in the console), animate the nav bar’s height from 144px to 88px.

import { Override, motionValue, useTransform } from "framer"
const contentOffsetY = motionValue(0)
export function TrackScroll(): Override {
return {
contentOffsetY: contentOffsetY,
onScroll() {
console.log(contentOffsetY.get())
},
}
}
// Create a new Override to assign to your parent App Bar frame
export function AppBar(): Override {
return {
height: useTransform(contentOffsetY, [0, -60], [144, 88]),
}
}
COPY

This is a new override, so make sure to assign it to your nav bar.

Now you should have something like this. Note how the child frames animate along with the nav bar because they’re pinned to the bottom in the constraints panel.

Let’s create some more overrides to animate our two-header strings (large and small). Then let’s add one for the search icon.

export function LargeHeading(): Override {
return {
opacity: useTransform(contentOffsetY, [0, -40], [1, 0]),
scale: useTransform(contentOffsetY, [0, -40], [1, 0.95]),
}
}
export function SmallHeading(): Override {
return {
opacity: useTransform(contentOffsetY, [-60, -90], [0, 1]),
}
}
export function SearchIcon(): Override {
return {
originX: 1, // Icon is anchored to the right when scaling
bottom: useTransform(contentOffsetY, [0, -72], [15, 18]),
scale: useTransform(contentOffsetY, [-30, -72], [1, 0.7]),
y: useTransform(contentOffsetY, [0, -72], [0, 8]),
right: useTransform(contentOffsetY, [0, -72], [24, 16]),
}
}
COPY

Assign each of these to the frames on your canvas and you’ve easily created some nice micro-interactions on scroll using overrides.

Code Components

Now that we have our scrolling set up, let’s translate our design components into code components. This will allow us to build some nice slide-to-reveal micro-interactions and easily control data through property controls.

Here’s what we’ll be making:

  1. A draggable row with dynamic icons, icon color, and strings that we can easily change.
  2. We want the dragging to feel great and snap when the user stops dragging. Where it snaps to should be dependant on its position.
  3. A row underneath with actions, animating in based on the drag position of the top row.

In the end, you should have something like this:

Building blocks

Create a new code file called RowDrive and delete all the code. At the top of every new .tsx file, we import React and what we need from the Framer Library. Let’s import stack and frame, as these will be the building blocks of our component.

At this point, you can delete the existing design components as we’ll be replacing these with the code component. Feel free to keep it around for reference as long as you need it.

import * as React from 'react'
import { Stack, Frame } from 'framer'
COPY

After importing, write out your top-level function that will house the meat of our component. Start by adding an empty frame component.

import * as React from 'react'
import { Stack, Frame } from 'framer'
export function RowDrive() {
return (
<Frame />
)
}
COPY

Head over to the components panel and you should see a RowDrive component.

Drag that onto the canvas. It’s just a blue frame at the moment, but as you update your code you’ll see the component change on the canvas in real time.

Let’s head back to our code and build out the structure of the RowDrive function with frames and stacks.

Use the Framer API when using stacks to see which options are available to you.

import * as React from "react"
import { Stack, Frame } from "framer"
export function RowDrive() {
return (
<Stack
name={"Top Row"}
direction={"horizontal"}
distribution={"start"}
width={"100%"}
height={"100%"}
gap={16}
background={"white"}
>
{/* Row Icon */}
<Frame
name={"Icon Container"}
size={48}
borderRadius={48 / 2}
background={"#0055FF"}
></Frame>
{/* Row Metadata */}
<Stack
name={"Metadata"}
width={"1fr"}
height={"100%"}
distribution={"center"}
gap={2}
alignment={"start"}
>
<Frame
name={"Title"}
width={"auto"}
height={18}
background={null}
>
Title // Placeholder string
</Frame>
<Frame
name={"Subtitle"}
width={"auto"}
height={18}
background={null}
>
Subtitle // Placeholder string
</Frame>
</Stack>
</Stack>
)
}
COPY

You’ll start to see your component update on the canvas.

Styling with JSX

We have a skeleton, but the title and subtitle strings aren’t looking great. Let’s create some styling variables outside of our RowDrive function.

// Styles
const styleTitle = {
fontSize: 15,
color: "#111",
fontWeight: 600,
letterSpacing: -0.22,
}
const styleSubtitle = {
fontSize: 13,
color: "#A6AFB9",
fontWeight: 400,
}
COPY

Then simply attach the variables to the frames containing the strings:

<Frame
name={"Title"}
width={"auto"}
height={18}
background={null}
style={styleTitle} // Reference style here
>
Title
</Frame>
<Frame
name={"Subtitle"}
width={"auto"}
height={18}
background={null}
style={styleSubtitle} // Reference style here
>
Subtitle
</Frame>
COPY

Setting Up Props

We know that we want the ability to change a lot of this metadata, so we’ll need to use Props for this. You can learn more about props here.

We’ve simply used “Title” and “Subtitle” as placeholders within our string frames. So let’s allow those to be changed from the properties panel.

Start by setting up default props outside of your RowDrive function. This sets a default width and height to your component when dragging it onto the canvas again. There’s no need to resize that bounding box like before.

RowDrive.defaultProps = {
width: 375,
height: 72,
}
COPY

Before these take effect, we need to “pass” our props into our RowDrive function like so:

export function RowDrive(props) {
return (
<Stack
name={"Top Row"}
direction={"horizontal"}
distribution={"start"}
width={props.width}
height={props.height}
gap={16}
background={"white"}
>
COPY

We’re passing our props into the RowDrive function iteself first, then we’re able to use our props.width and props.height in place of our top frame’s width/height values.

Try deleting the code component on your canvas, then dragging a new one in.

Property Controls

Now comes the real power of Framer X: property controls. These expose UI in the right-hand panel on the canvas, which takes the input from the user and overrides the default props you’ve setup in the component. You can read more about property controls here.

As mentioned before, let’s set some up for our title and subtitle strings.

First, make sure you import addPropertyControls and ControlType from the Framer Library at the top of your file.

import { Stack, Frame, addPropertyControls, ControlType } from "framer"
COPY

Then, at the bottom of your file (below the RowDrive function), add two property controls for the Title and Subtitle strings.

addPropertyControls(RowDrive, {
// Change the title
title: {
type: ControlType.String,
defaultValue: "Title",
title: "Title",
},
// Change the title
subtitle: {
type: ControlType.String,
defaultValue: "Subtitle",
title: "Subtitle",
},
})
COPY

You can reference the Framer API on the details of each property control type.

Finally replace your placeholder strings in your RowDrive function with props.

import * as React from "react"
import { Frame, Stack, addPropertyControls, ControlType } from "framer"
export function RowDrive(props) {
return (
<Stack
name={"Top Row"}
direction={"horizontal"}
distribution={"start"}
width={props.width}
height={props.height}
gap={16}
background={"white"}
>
{/* Row Icon */}
<Frame
name={"Icon Container"}
size={48}
borderRadius={48 / 2}
background={"#0055FF"}
></Frame>
{/* Row Metadata */}
<Stack
name={"Metadata"}
width={"1fr"}
height={"100%"}
distribution={"center"}
gap={2}
alignment={"start"}
>
<Frame
name={"Title"}
width={"auto"}
height={18}
background={null}
style={styleTitle}
>
{props.title}
</Frame>
<Frame
name={"Subtitle"}
width={"auto"}
height={18}
background={null}
style={styleSubtitle}
>
{props.subtitle}
</Frame>
</Stack>
</Stack>
)
}
COPY
There you go! These are really the fundamentals of building out any component in Framer X (frames, stacks, props, and property controls).

Creating an Icon Picker

Let’s do something a little more complex. On the canvas, we’ve created a bunch of individual icon design components. We want to create a dropdown property control in our component, which will allow the user to select any icon which will then show up in the icon container.

First let’s import these icons from the canvas into our code component.

Note: These should be the same names as your design components on the canvas. You can only import design components from the canvas into your code.

import {
IcnArchive,
IcnBooks,
IcnDelete,
IcnDrive,
IcnFilm,
IcnFramer,
IcnGlobe,
IcnImage,
IcnLock,
IcnMusic,
IcnPodcasts,
IcnDownload,
IcnShopping,
} from "./canvas"
COPY

Then outside your RowDrive function, below your imports, create an Object with the imported icons, each assigned a letter which we’ll reference with our property control.

const icons = {
a: IcnBooks,
b: IcnDrive,
c: IcnFramer,
d: IcnGlobe,
e: IcnImage,
f: IcnLock,
g: IcnMusic,
h: IcnPodcasts,
i: IcnShopping,
j: IcnFilm,
k: IcnArchive,
}
COPY

In your Property Controls area, create an Enum Property Control type. This will create a dropdown, where each choice (option) should match up to the letters in the object you’ve created.

iconChoice: {
type: ControlType.Enum,
title: "Icon",
defaultValue: "a",
options: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"],
optionTitles: [
"Books",
"Drive",
"Framer",
"Globe",
"Image",
"Lock",
"Music",
"Podcasts",
"Shopping",
"Film",
"Archive",
],
},
COPY

Now create a new variable at the top of your RowDrive function, which should say let Icon = icons[props.iconChoice].

This tells Framer to create a new variable, look at the Icons object, and choose one of those based on the active choice from the dropdown property control.

export function RowDrive(props) {
// Create a new icon variable which chooses an icon from the object based on the dropdown choice
let Icon = icons[props.iconChoice]
return (
<Stack
name={"Top Row"}
direction={"horizontal"}
distribution={"start"}
width={props.width}
height={props.height}
gap={16}
background={"white"}
>
{/* Row Icon */}
<Frame
name={"Icon Container"}
size={48}
borderRadius={48 / 2}
background={"#0055FF"}
>
// We render that icon here and center it to the container
<Icon center />
</Frame>
{/* Row Metadata */}
<Stack
name={"Metadata"}
width={"1fr"}
height={"100%"}
distribution={"center"}
gap={2}
alignment={"start"}
>
COPY

Add another simple property control for the icon’s container color, which creates a color picker, and we’re done with props.

iconColor: {
type: ControlType.Color,
defaultValue: "#0055FF",
title: "Icon Color",
}
COPY

Now you should have a fully functioning icon picker and the ability to choose any color you like.

Dragging and micro-Interactions

We know that our top frame will need to be draggable to reveal the bottom actions we’ve yet to build. We’ll also want the top frame to snap on release (either fully to the left, or back to where it started).

You can already make your top stack draggable by adding one line of code.

<Stack
name={"Top Row"}
direction={"horizontal"}
distribution={"start"}
width={"100%"}
height={"100%"}
gap={16}
background={"white"}
drag={"x"}
>
COPY

Let’s add another empty frame below (above in code) as a placeholder for our bottom row. Now if you preview your component and drag, it’ll fly away! That‘s cool, but not what we want.

Let’s stop that from happening with dragConstraints. Let’s also set dragDirectionLock to true, which will disable dragging while we’re scrolling our list, as well as setting dragElastic to 0 so we can’t drag too far past our constraints.

<Stack
name={"Top Row"}
direction={"horizontal"}
distribution={"start"}
width={"100%"}
height={"100%"}
gap={16}
background={"white"}
drag={"x"}
dragConstraints={{
left: -88,
right: 0,
}}
dragElastic={0}
dragDirectionLock={true}
>
COPY

To get proper snapping when we stop dragging, we’ll need to import motionValue again (to track the position of the frame when the user stops dragging), as well as useAnimation.

import {
Frame,
Stack,
ControlType,
addPropertyControls,
useAnimation,
motionValue,
}
COPY

Let’s assign some variables with our dragging values to make our snapping math a little easier. Do this within your RowDrive function, but above the return().

const dragWidth = -88
const dragFull = { x: dragWidth }
const dragReset = { x: 0 }
const contentOffsetX = motionValue(0)
const animation = useAnimation()
COPY

Now, set your animate property to the useAnimation variable we create above (also called animation), and add two event listeners to your draggable stack. While dragging, we can get the values from the console (with the same method we used for the scroll overrides). Now, onDragEnd will trigger an animation, which snaps it’s x position relative to dragWidth / 2.

<Stack
name={"Top Row"}
direction={"horizontal"}
distribution={"start"}
width={"100%"}
height={"100%"}
gap={16}
background={"white"}
drag={"x"}
x={contentOffsetX}
dragConstraints={{ left: dragWidth, right: 0 }}
dragElastic={0}
dragDirectionLock={true}
animate={animation}
onDrag={() => {
console.log(contentOffsetX.get()); // Use this to get your values
}}
onDragEnd={() => {
if (contentOffsetX.get() > dragWidth / 2) {
animation.start(dragReset)
} else if (contentOffsetX.get() < dragWidth / 2) {
animation.start(dragFull)
}
}}
>
COPY

Now take some time to paste some of your new code components into your stack, and change some colors and icons. When you preview your Home frame, you should have a fully interactive list of independent, draggable rows. Each one should have proper snapping behavior in place.

Great, the hard part is done! All we need to do now is flesh out the bottom layer with two fixed icons.

We’ve already imported these from the canvas and because they aren’t changing, we won’t have to worry about adding any Props.

Here’s my final function for RowDrive:

export function RowDrive(props) {
let Icon = icons[props.iconChoice]
// Setup dragging interaction values
const dragWidth = -124
const dragFull = { x: dragWidth }
const dragReset = { x: 0 }
const contentOffsetX = motionValue(0)
const animation = useAnimation()
return (
<Frame
name={"Container"}
width={props.width}
height={props.height}
background={null}
>
{/* Bottom Row w/ Static Actions */}
<Stack
name={"Bottom Row"}
direction={"horizontal"}
distribution={"end"}
width={"100%"}
height={"100%"}
gap={12}
paddingRight={24}
>
<IcnDownload
rotate={useTransform(
contentOffsetX,
[dragWidth / 2, dragWidth],
[25, 0]
)}
scale={useTransform(
contentOffsetX,
[dragWidth / 2, dragWidth],
[0.5, 1]
)}
/>
<IcnDelete
rotate={useTransform(
contentOffsetX,
[0, dragWidth / 2],
[25, 0]
)}
scale={useTransform(
contentOffsetX,
[0, dragWidth / 2],
[0.5, 1]
)}
/>
</Stack>
<Stack
name={"Top Row"}
direction={"horizontal"}
distribution={"start"}
width={"100%"}
height={"100%"}
gap={16}
background={"white"}
animate={animation}
x={contentOffsetX}
drag={"x"}
dragDirectionLock={true}
dragElastic={0}
dragConstraints={{
left: -124,
right: 0,
}}
onDrag={() => {
console.log(contentOffsetX.get())
}}
onDragEnd={() => {
if (contentOffsetX.get() > dragWidth / 2) {
animation.start(dragReset)
} else if (contentOffsetX.get() < dragWidth / 2) {
animation.start(dragFull)
}
}}
>
{/* Row Icon */}
<Frame
name={"Icon Container"}
size={48}
borderRadius={48 / 2}
background={props.iconColor}
>
<Icon center />
</Frame>
{/* Row Metadata */}
<Stack
name={"Metadata"}
width={"1fr"}
height={"100%"}
distribution={"center"}
gap={2}
alignment={"start"}
>
<Frame
name={"Title"}
width={"auto"}
height={18}
background={null}
style={styleTitle}
>
{props.title}
</Frame>
<Frame
name={"Subtitle"}
width={"auto"}
height={18}
background={null}
style={styleSubtitle}
>
{props.subtitle}
</Frame>
</Stack>
</Stack>
</Frame>
)
}
COPY

Just like we did with the scroll overrides, I’ve used useTransform to add some nice micro-interactions to the icons based on my contentOffsetX value when dragging.

Here’s our final result:

Making Things Happen

This is a common question for designers learning Framer X. How do I make things happen when I trigger an event from another component? What if I’m using overrides and code components?

Take a look at the final prototype and breakdown what we want to happen:

  1. Tapping the download icon after swiping triggers a spinner in the tab bar.
  2. The spinner animates in and animates out for a set amount of time.
  3. The user can tap on the downloads tab and see their download row.

Link tool navigation

To navigate between the tabs, we don’t even need to code. Let’s duplicate our Home frame, delete the scroll component so there’s no content, and adjust the nav and tab. Also, make sure to detach the overrides from your nav elements.

Next, select your tap targets in the tab frames and link to your desired screen. Set the transition to Instant to mimic default iOS behaviour.

Spinner setup

Instead of creating a new code component for a spinner, let’s just download this package from the Store. It gives us tons of options and flexibility with color.

Place your spinner of choice within your Downloads tab on the Home frame. Adjust its size and color, then turn the opacity down to 0. We’ll use overrides to animate this in and out based on the row tap later.

Notice how we’re already getting the spinner animation for free. All we’ll have to worry about are the entrance and exit animations.

Setting Up Data

Next, let’s create a new .tsx file called Download. This will contain our logic and overrides needed to make everything happen we listed above.

As always, begin a new code file by importing what you need. We’ll only need Overrides and Data. The first thing we do is to create a Data object.

Data essentially allows you to communicate between overrides. While props would be used to communicate from and to a code component.

import { Override, Data } from "framer"
const data = Data({
isDownloading: false,
downloadCompleted: false
})
COPY

Here, we’re using data to keep track of a few things that our interactions (i.e. showing and hiding the spinner) will be dependant on. You can name these whatever you want, but make sure they are clear at first glance.

Right now, let’s create a boolean set to false called isDownloading. We’ll want the spinner to show when this is true, and it should be hidden when it’s false.

We’ll create a second boolean called downloadCompleted, which is also set to false. We want our row in the Downloads screen (which we’ll create a bit later) to appear when this is set to true.

Data & Overrides

Let’s write our first override. For our prototype, we’ll be attaching it to the first RowDrive code component in the Home screen Stack.

export function TapDownloadAction(): Override {
return {
triggerDownload() {
data.isDownloading = true
setTimeout(() => {
data.isDownloading = false
data.downloadCompleted = true
}, 3000)
},
}
}
COPY

This contains a (triggerDownload) which sets our data.isDownloading to true. As well as a simple setTimeout function which will set data.isDownloading to false, and data.downloadCompleted to true after 3 seconds.

Let’s also setup another override to fade in our spinner based on the data.isDownloading being true/false.

// Animate spinner when download is active
export function AnimateSpinnerIcon(): Override {
return {
animate: {
opacity: data.isDownloading ? 1 : 0,
scale: data.isDownloading ? 1 : 0,
},
}
}
COPY

The code within the opacity and scale properties is in-line conditional jsx. It’s essentially a shorter way of saying if data.isDownloading is true, then set opacity to 1, else set it to 0.

Framer will automatically animate anything attached to this override when that data value is changed.

Communicate between Code Components & Overrides

There’s one more step before we can see this all in action. Go back to your RowDrive file and in your RowDrive function, add an onTap event to your Download icon frame. Through props, a tap will trigger the overrides function you wrote in the Downloads file (TapDownloadAction()).

It should look something like this:

// RowDrive.tsx
<IcnDownload
rotate={useTransform(
contentOffsetX,
[dragWidth / 2, dragWidth],
[25, 0]
)}
scale={useTransform(
contentOffsetX,
[dragWidth / 2, dragWidth],
[0.5, 1]
)}
transition={"Spring"}
whileTap={{ opacity: 0.3 }}
onTap={() => {
// Pass in props to your code component here
props.triggerDownload();
}}
/>
COPY

Again, we’re using Props to pass data to and from a code component. Note that triggerDownload is exact name of the function you wrote in your Downloads.tsx function.

We’ve now setup two-way communication between code components and design components using overrides.

Let’s add one more override to animate out the Download icon in the tab bar when data.isDownloading is true.

// Animate download icon
export function AnimateDownloadIcon(): Override {
return {
animate: {
opacity: data.isDownloading ? 0 : 1,
scale: data.isDownloading ? 0 : 1,
},
}
}
COPY

The hard part is done! Now assign the overrides to your frames on the canvas.

  1. Choose the first RowDrive component in your stack and assign the TapDownloadAction Override to it.
  2. Assign the AnimateSpinnerIcon to your spinner component in the tab bar.
  3. Assign the AnimateDownloadIcon to your download component in the tab bar.

You can see what I have here:

Finishing the Downloads screen

Now we want one more thing to happen. When you tap that first row, we want a new Download row (which shows different metadata and actions) to appear in the Downloads screen.

Remember how we added an earlier boolean to our Data object called downloadCompleted, and set it to false?

const data = Data({
isDownloading: false,
downloadCompleted: false,
})
COPY

Now, just like we did before, we can use conditionals to animate anything based that boolean being true or false.

Let’s write another override in our Downloads files.

export function ShowDownloadRow(): Override {
return {
animate: {
opacity: data.downloadCompleted ? 1 : 0,
},
}
}
COPY

Finally, we’ll need to create a new component for our Download section to link this override to. Don’t worry, we won’t have to take as much time as before. We can simply copy and paste our code for RowDrive.tsx, paste it into a new file called RowDownload.tsx and take out what we don’t need.

In our case, it’s simply removing the Download action and using our existing props to change the metadata on the canvas.

Here’s what my code looks like. Remember—all the imports, props, and styles remain the same.

export function RowDownload(props) {
let Icon = icons[props.iconChoice]
const dragWidth = -88
const dragFull = { x: dragWidth }
const dragReset = { x: 0 }
const contentOffsetX = motionValue(0)
const animation = useAnimation()
return (
<Frame
name={"Container"}
width={props.width}
height={props.height}
background={null}
>
{/* Bottom Row w/ Static Actions */}
<Stack
name={"Bottom Row"}
direction={"horizontal"}
distribution={"end"}
width={"100%"}
height={"100%"}
gap={12}
paddingRight={24}
>
<IcnDelete
rotate={useTransform(
contentOffsetX,
[0, dragWidth / 2],
[25, 0]
)}
scale={useTransform(
contentOffsetX,
[0, dragWidth / 2],
[0.5, 1]
)}
/>
</Stack>
<Stack
name={"Top Row"}
direction={"horizontal"}
distribution={"start"}
width={"100%"}
height={"100%"}
gap={16}
background={"white"}
animate={animation}
x={contentOffsetX}
drag={"x"}
dragDirectionLock={true}
dragElastic={0}
dragConstraints={{
left: -124,
right: 0,
}}
onDrag={() => {
console.log(contentOffsetX.get())
}}
onDragEnd={() => {
if (contentOffsetX.get() > dragWidth / 2) {
animation.start(dragReset)
} else if (contentOffsetX.get() < dragWidth / 2) {
animation.start(dragFull)
}
}}
>
{/* Row Icon */}
<Frame
name={"Icon Container"}
size={48}
borderRadius={48 / 2}
background={props.iconColor}
>
<Icon center />
</Frame>
{/* Row Metadata */}
<Stack
name={"Metadata"}
width={"1fr"}
height={"100%"}
distribution={"center"}
gap={2}
alignment={"start"}
>
<Frame
name={"Title"}
width={"auto"}
height={18}
background={null}
style={styleTitle}
>
{props.title}
</Frame>
<Frame
name={"Subtitle"}
width={"auto"}
height={18}
background={null}
style={styleSubtitle}
>
{props.subtitle}
</Frame>
</Stack>
</Stack>
</Frame>
)
}
COPY

Drag that new component into your Downloads screen and assign the correct override (ShowDownloadRow).

Wrap Up

We’ve done a lot! We learned how to use overrides to add polished micro-interactions based on scrolling. Then we created a code component with props and advanced dragging behavior. Finally, we learned the key step for bringing everything together—letting code components communicate with overrides through data and props.

These are all core workflows needed to build flexible high-fidelity prototypes. You can take this even further by reading about how to work with JSON and APIs here.

I’ve also created a handy component in the Framer Store which will automatically create add slide-to-reveal actions underneath any frame you attach it to.

Download the Swipe Actions package ›

If you have any questions, find me on Twitter. Make sure to check out our great communities on Spectrum and Facebook as well.