For More Realistic FramerJS Prototypes, Just Add Data
As a designer in today‘s world of digital products, prototyping is an essential tool in our workflow. Prototypes allow us to quickly test and validate design ideas, gather user feedback, and iterate towards an optimal solution before investing significant engineering resources.
Over the past decade, prototyping has evolved from simple paper sketches and clickable wireframes to fully interactive, high-fidelity experiences that closely mimic real products. Tools like InVision, Sketch, and Figma have become mainstays in the designer‘s toolkit, allowing us to quickly build out interfaces and animations.
However, there‘s one key ingredient still missing from many prototypes that‘s critical for creating a truly convincing experience: real data.
The Power of Real Data in Prototypes
Think about the last prototype you built or interacted with. Chances are, it was filled with idealized, "lorem ipsum" style placeholder content. The profile pictures were all perfectly smiling headshots, the blog posts were titled "Article Headline Goes Here", and the dashboard charts displayed unconvincingly perfect upwards trends.
We‘ve grown so accustomed to this fake data that we almost don‘t notice it anymore. But to the users interacting with these prototypes, it stands out like a sore thumb. It‘s an instant reminder that what they‘re looking at isn‘t "real".
Now imagine if your prototype pulled in actual, live data. The profiles would have the user‘s real name, photo, and bio. The blog would display the latest posts published seconds ago. The dashboard would be populated with the real revenue numbers and analytics for the company.
Suddenly, the illusion of interacting with a real product is maintained. The user can suspend disbelief and provide genuine reactions and feedback. The insights gathered are much more valuable, as they‘re based on realistic scenarios and data rather than fictional abstractions.
This is the power of incorporating real data into your prototypes. And with the advent of robust APIs and frameworks like FramerJS, it‘s never been more achievable.
Enter FramerJS
FramerJS is a powerful coding framework built specifically for creating interactive prototypes. With an extensive library of interface components, pre-built interactions and animations, and an intuitive coding language (CoffeeScript), FramerJS allows designers to build prototypes ranging from simple click-throughs to complex, data-driven experiences.
While FramerJS has gained popularity for its ability to create fluid micro-interactions and realistic motion, I believe its most powerful capability is integrating with APIs to pull in live data. By writing a few lines of code, you can fetch data from almost any web-based API and seamlessly display it in your prototype.
To illustrate this, let‘s walk through a real example of building a subway tracking prototype in FramerJS that pulls live data from Boston‘s MBTA API.
The Problem: Where‘s My Train?
Boston‘s subway system, known as the "T", is the oldest underground transit system in North America. While historic, it‘s not exactly known for punctuality. Delays and schedule changes are a daily occurrence, making it difficult for riders to plan their commutes.
While the MBTA does have an official app that shows train locations and predicted arrival times, it‘s not the most user-friendly or visually appealing. I saw an opportunity to design a better experience using FramerJS and the MBTA‘s real-time data API.
Accessing Live Location Data
The first step in building this prototype was getting access to the user‘s live location data. We need to know where they are in order to find nearby subway stations and show relevant train information.
Fortunately, modern web browsers make this fairly simple with the Geolocation API. We can use the getCurrentPosition()
method to retrieve the user‘s current latitude and longitude coordinates:
getCurrentLocation = () ->
if navigator.geolocation
navigator.geolocation.getCurrentPosition(onLocationSuccess, onLocationError)
else
alert("Geolocation is not supported by this browser.")
onLocationSuccess = (position) ->
latitude = position.coords.latitude
longitude = position.coords.longitude
# Do something with the location data
onLocationError = (error) ->
alert("Error getting location: #{error.message}")
Here we first check if the browser supports geolocation. If so, we call getCurrentPosition()
which will prompt the user to allow access to their location. If they accept, the onLocationSuccess
callback is invoked with a position
object containing the latitude
and longitude
.
One gotcha I ran into here: some browsers (looking at you, Chrome) don‘t allow geolocation requests from insecure origins. So if you‘re just loading your FramerJS prototype locally from your computer via the file://
protocol, the request will be blocked and the onLocationError
callback will fire instead. The solution is to either host your prototype on a secure https://
domain or use a browser like Firefox that allows local geolocation (for development purposes only).
With the user‘s live location data in hand, we can now use it to find nearby subway stations.
Finding Nearby Stations
The MBTA provides a public-facing API with a plethora of endpoints for accessing data about routes, stops, schedules, and vehicle locations. The one we‘re interested in here is the /stops
endpoint, which allows querying for stops based on location.
The API conveniently accepts latitude
and longitude
parameters and returns a list of stops sorted by distance to that location. Perfect for our use case! Here‘s how we can make the request in FramerJS using the Utils.domLoadDataAsync()
method:
requestNearbyStops = (latitude, longitude) ->
url = "https://api-v3.mbta.com/stops?filter[latitude]=#{latitude}&filter[longitude]=#{longitude}&sort=distance"
Utils.domLoadDataAsync(url, (data) ->
stops = data.data
if stops.length > 0
closestStop = stops[0]
stopName = closestStop.attributes.name
stopId = closestStop.id
# Display the stop name and use the ID for further requests
else
alert("No nearby stops found.")
, (error) ->
alert("Error fetching stops: #{error}")
)
We construct the URL for the API request, passing in the user‘s latitude
and longitude
coordinates as query parameters. We also specify sort=distance
to return the stops in ascending order of distance from that location.
The Utils.domLoadDataAsync()
method takes that URL and makes an asynchronous HTTP request. If the request succeeds, the callback function is invoked with the response data parsed as a JSON object.
We check if the data
array contains any stops. If so, we grab the first one (which will be the closest) and extract its name
and id
attributes. The name
is what we‘ll display in our prototype‘s interface, while the id
will be used to fetch upcoming train arrival times for that specific stop.
If no stops are found (which should only happen if the user is quite far from any subway lines), we display an alert message. We also handle any errors that may occur during the API request.
With the nearest stop determined, we can now fetch and display the upcoming train arrival times.
Displaying Real-Time Train Predictions
To get the predicted arrival times for a specific stop, we can use the MBTA API‘s /predictions
endpoint. This returns an array of predictions for each route serving that stop, with the estimated arrival time and other details.
Here‘s how we can request and parse that data in FramerJS:
requestPredictions = (stopId) ->
url = "https://api-v3.mbta.com/predictions?filter[stop]=#{stopId}"
Utils.domLoadDataAsync(url, (data) ->
predictions = data.data
if predictions.length > 0
# Clear any existing predictions
predictionList.html = ""
for prediction in predictions
# Extract relevant data from the prediction object
route = prediction.relationships.route.data.id
arrivalTime = prediction.attributes.arrival_time
departureTime = prediction.attributes.departure_time
direction = prediction.attributes.direction_id
# Calculate minutes until arrival
now = Date.now()
minutesUntilArrival = Math.ceil((Date.parse(arrivalTime) - now) / 60000)
# Append prediction to the list
predictionList.html += "<li>#{route} #{direction}: #{minutesUntilArrival} min</li>"
# Make the predictions visible
predictionList.visible = true
else
predictionList.html = "<li>No predictions available.</li>"
predictionList.visible = true
, (error) ->
alert("Error fetching predictions: #{error}")
)
As before, we construct the URL for the /predictions
endpoint, this time passing the stopId
as a filter parameter to only return predictions for that specific stop.
In the success callback, we first check if the returned data
array contains any predictions. If not, we display a "No predictions available" message.
If there are predictions, we iterate over each one and extract the relevant bits of data:
route
– The ID of the route (e.g. "Red", "Green-C")arrivalTime
– The predicted arrival time as an ISO 8601 timestamp stringdepartureTime
– The predicted departure time (if available)direction
– The direction of travel (0 for outbound, 1 for inbound)
To make the arrival time more human-friendly, we parse the ISO 8601 timestamp and compare it to the current time to calculate the minutes until arrival.
Finally, we format this data into an HTML string and append it to our predictions list element. The predictions are now displayed in the prototype‘s interface!
Putting it All Together
With the nearby stops and arrival predictions loading, the final step was to design an interface to display all this data in a user-friendly way.
Using FramerJS‘s Layer
component, I created a full-screen container and added child layers for the header, stop name, predictions list, and map.
The map layer uses Mapbox to display an interactive map centered on the user‘s location, with the nearest subway stop plotted as an annotation. (Note: you‘ll need to sign up for a free Mapbox account and replace YOUR_ACCESS_TOKEN
with your own token).
# Set up map layer
mapLayer = new Layer
x: 0, y: 0, width: Screen.width, height: Screen.height
image: "https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/#{userLocation.longitude},#{userLocation.latitude},15,0,0/#{Screen.width}x#{Screen.height}@2x?access_token=YOUR_ACCESS_TOKEN"
# Add the stop as a map annotation
stopAnnotationLayer = new Layer
parent: mapLayer
x: Align.center, y: Align.center
width: 64, height: 64
backgroundColor: "rgba(255,255,255,0.8)"
borderRadius: 32
html: "<img src=‘stop-icon.png‘/>"
For the header and predictions list, I used simple TextLayer
components:
# Header
headerLayer = new TextLayer
x: 0, y: 0, width: Screen.width, height: 128
backgroundColor: "#333"
color: "white"
fontSize: 32
text: "MBTA Subway Tracker"
textAlign: "center"
padding:
top: 64
# Stop name
stopNameLayer = new TextLayer
x: 32, y: headerLayer.maxY + 32
width: Screen.width
color: "#333"
fontSize: 24
text: stopName or "Finding nearest stop..."
# Predictions list
predictionListLayer = new TextLayer
parent: stopNameLayer
y: stopNameLayer.maxY
width: Screen.width
color: "#666"
fontSize: 20
visible: false
html: ""
With all the visual components in place, the final prototype interface looks like this:
[GIF of the final prototype in action]And there you have it! A FramerJS prototype pulling in live MBTA data to display nearby subway stops and upcoming train arrivals.
You can play with the final prototype yourself here: [Live prototype link]
Key Takeaways
Building this seemingly simple prototype surfaced a number of valuable insights that are broadly applicable to designing with real data:
-
Working with APIs is unpredictable – Unlike static design comps where you control the data, APIs are living, changing things. Fields might be renamed or removed. Endpoints can go down. Responses may be slow. Building in robust error handling is crucial.
-
Real data is messy – Transit data is notoriously complex, with hundreds of routes, stops, directions, and service changes. I had to wade through dozens of API fields to find the few pieces of data actually relevant to my use case. Don‘t underestimate the data cleaning and parsing work involved.
-
But real data is powerful – Once you have it flowing, using live API data instantly makes a prototype feel more genuine and engaging. Stakeholders and test users will interact with it differently when the information is real and relevant to them.
-
FramerJS is a capable data manipulation tool – While known primarily as a motion design tool, FramerJS‘s JavaScript underpinnings give you a ton of flexibility to work with data. Constructing URLs, making HTTP requests, and parsing JSON all felt very familiar coming from a web development background.
-
The prototype is a living, evolving thing – Since this prototype pulls live data every time it‘s loaded, it is always showing the latest information. If the MBTA API changes or has issues, that will be immediately reflected. A prototype built on real data must be actively maintained, just like a real product.
Ideas to Expand This Concept
While this prototype focuses on the narrow use case of subway arrival times, the same approach of combining FramerJS with live data could be applied to all sorts of other domains:
- A weather app that displays the real forecast for the user‘s current location
- A stock trading app that shows real-time market data and portfolio performance
- A travel booking app that searches actual flight or hotel availability
- A restaurant reservation app that checks table inventory from a real backend system
- A fitness tracker that pulls in a user‘s real activity, heart rate, and sleep data
The possibilities are truly endless. Any data that is available via an API can be pulled into FramerJS and displayed in a realistic, interactive prototype.
Final Thoughts
As designers, we are perpetually seeking ways to make our creations feel more "real". To cross that uncanny valley from obvious fiction to convincing reality.
Incorporating live data can be a significant step towards that goal. Even if the interface is not fully polished or the interactions are not totally refined, the mere presence of genuine, relevant information can make a prototype feel infinitely more authentic and engaging.
FramerJS, with its powerful set of interface components and data fetching capabilities, is the ideal tool for this new frontier of data-driven prototyping. It allows us to bridge the gap between the static designs of yesterday and the dynamic products of tomorrow.
So go forth and build! Find an API relevant to your product‘s domain, pull in that data, and craft an experience around it. The more real, the better.
The age of lorem ipsum is over. Long live real data in design.