the goal
develop a modern online ordering experience for a smoothie bowl vendor using React.js and Tailwind. these two frameworks will be used to create a playful interface which reacts dynamically to user interaction with components.
project vision
as the first large-scale React.js project for Masha, Christian, and i, we wanted to collaborate and utilize web components to its fullest to create a unique online experience out of an ordinary process.
with this new medium, we decided to create a 3-step smoothie builder utilizing props to pass ingredient information between each step.
working together
since this website was a side-project outside out of school, the three of us agreed that an agile, sprint-based workflow would be ideal. having weekly meetings, a Notion database, and a Discord server ensured that we had the right resources to focus on the task at hand, as we assigned new tasks and deadlines weekly.
brand identity
we first began the project by creating a visual identity for the smoothie vendor. we knew that we wanted the website to have a unique feel with bright colors and fun shapes—we also incorporated a blender silhouette into the logo as the whole experience revolves around it.
with all the colors, fonts, and logos established, we created a style tile to refer to as we develop the website moving forward.

wireframes and mockups
we created wireframes for the project to get an general idea for each page’s layout. with the wireframes and style tile, we then made the mockups for both pages. i focused on the smoothie bowl builder mockup, assuring that the bowl builder had the same playful energy as the homepage. having weekly communication and collaboration ensured that blend it’s visual identity remained consistent across all aspects of the website, even when we worked on sections separately.
react step wizard
using react step wizard, i was able to segment the smoothie bowl builder into 3 main steps; blending the base, picking fruits, and finishing it with toppings. step wizard has the ability to pass props between each step, meaning that we can retain the ingredients selected from one step to the next, and display the bowl in its final state at the end of the process.
import React, { useState } from "react";
import StepWizard from "react-step-wizard";
// import each step from './steps';
import Base from "./steps/Base";
import Fruits from "./steps/Fruits";
import Toppings from "./steps/Toppings";
import Complete from "./steps/Complete";
function Builder() {
return (
<StepWizard isHashEnabled={true}>
{/* Step 1 */}
<Base
hashKey={"base"}
// runs the updateIngredients function when an item is selected
// tells the function that we're on the "base" step, and passes the selected item
updateIngredients={(item) => updateIngredients("base", item)}
// used to display the ingredients and RGb in the current step
selectedIngredients={selectedIngredients.base}
// used to display all ingredients in the order
// flattens the array as all the ingredients are nested in their own steps
allIngredients={Object.values(selectedIngredients).flat()}
// used to color the smoothie base
baseRGBs={baseRGBs}
/>
{/* Step 2 */}
<Fruits
hashKey={"fruits"}
updateIngredients={(item) => updateIngredients("fruits", item)}
selectedIngredients={selectedIngredients.fruits}
allIngredients={Object.values(selectedIngredients).flat()}
baseRGBs={baseRGBs}
/>
{/* Step 3 */}
<Toppings
hashKey={"toppings"}
updateIngredients={(item) => updateIngredients("toppings", item)}
selectedIngredients={selectedIngredients.toppings}
allIngredients={Object.values(selectedIngredients).flat()}
baseRGBs={baseRGBs}
/>
{/* Complete */}
<Complete
hashKey={"complete"}
allIngredients={Object.values(selectedIngredients).flat()}
/>
</StepWizard>
);
}
export default Builder;
// this is an array that holds the ingredients for each step
// we can use this to display ALL selected ingredients
// and to pass the selected ingredients to the next step
const [selectedIngredients, setSelectedIngredients] = useState({
// ingredients for Base step
base: [],
// ingredients for Fruits step
fruits: [],
// ingredients for Toppings step
toppings: [],
});
// this function updates the ingredients per step
const updateIngredients = (step, item) => {
setSelectedIngredients(prevState => {
// creates a variable that holds the ingredients for the current step
const currentIngredients = prevState[step];
// creates a variable that holds the updated ingredients
// we use this variable to apply changes to the ingredient list
let updatedIngredients;
// if the item is already in the order
if (currentIngredients.includes(item)) {
// create a variable that filters out the item from the order
updatedIngredients = currentIngredients.filter(ingredient => ingredient !== item);
} else {
// if the amount of ingredients is less than 3,
if (currentIngredients.length < 3) {
// add the item to the order
updatedIngredients = [...currentIngredients, item];
} else {
// if the amount of ingredients is more than 3
// set the updated ingredients to the current ingredients
updatedIngredients = currentIngredients;
}
}
return {
// ... -> spread operator
// copies the previous state
...prevState,
// and adds the updated state with the new ingredients
[step]: updatedIngredients
};
});
};
a colorful challenge
a lot of discussion was had about how we can dynamically change the color of the smoothie based on the ingredients selected. after some research we found that, colors can be blended together by adding the fruits R, G, and B values together, and dividing it by the amount of ingredients selected. thanks to this solution, we avoided a whole page of if statements in our code.
// this is a variable that holds the RGB colors
const [baseRGBs] = useState([]);
// selects the smoothie base element so it can be colored
const smoothieBase = document.querySelectorAll('.smoothie-base');
// if the baseRGBs array has 3 elements
if (baseRGBs.length === 3) {
// calculate the average RGB color
let Fruit1 = baseRGBs[0];
let Fruit2 = baseRGBs[1];
let Fruit3 = baseRGBs[2];
// add all red values then divide by 3
let Red = (Fruit1[0] + Fruit2[0] + Fruit3[0]) / 3;
// add all green values then divide by 3
let Green = (Fruit1[1] + Fruit2[1] + Fruit3[1]) / 3;
// add all blue values then divide by 3
let Blue = (Fruit1[2] + Fruit2[2] + Fruit3[2]) / 3;
// round the RGB values to whole numbers
Red = Math.round(Red);
Green = Math.round(Green);
Blue = Math.round(Blue);
// convert the RGB values to a string
// this way, we can use it as a CSS color
// rgb(Red, Green, Blue)
let baseRGB = 'rgb(' + [Red, Green, Blue].toString() + ')';
// color the smoothie base
smoothieBase.forEach(item => {
item.style.backgroundColor = baseRGB
}
);
// if the baseRGBs array has 1 or 2 elements
} else if (baseRGBs.length > 1 && baseRGBs.length < 3) {
// remove color from the smoothie base
smoothieBase.forEach(item => {
item.style.backgroundColor = 'unset'
}
);
}
blending it together
working on blend it was more than just a technical project—it was a chance to be creative, collaborative, and innovative. using React.js and Tailwind, we turned the basic ordering system into something interactive and lively. this was achieved due effective project management in an agile workflow, consistent communication, and, of course, positive vibes all around.