Google Stitch is an AI-powered UI design and development tool that allows users to generate professional-quality UI designs along with the corresponding web and mobile source code from text prompts.
Vibe coding involves using AI to build applications via prompts. This practice is often very effective for design, as we will discover in this article.
We‘ll use Stitch to generate the app design and implement it in React code using Google Gemini.
Follow along to build a responsive, multi-page e-commerce site containing a Homepage, Product listing, and Detail pages — with the UI generated from text prompts using Stitch. Our page will also contain the Add to Cart and Checkout pages.
We’ll also discuss the benefits and drawbacks of using Stitch, and how tools like these will affect the future of UI and frontend design.
There are a couple of assumptions in this article:
Currently, Stitch can be accessed through stitch.withgoogle.com
Stitch has a bold and straight-to-the-point interface.
It comes with a big input for prompting the AI on the kind of features of the design you want. Stitch is currently in beta and offers both standard and experimental modes. The standard mode is powered by Google Gemini 2.5 Flash, which allows quick editing of themes and export to Figma.
The experimental mode has Gemini 2.5 Pro under the hood, offering the capability to upload a design image as a sketch, mockup, or visual inspiration to Stitch for inspiration.
Stitch can be accessed at no cost, but it comes with specific usage limitations.
Every user is granted a monthly quota of 350 UI generations in standard mode and 50 generations in experimental mode. When you are finally done with your design, Stitch outputs the design through a design file, code, and Figma file. The code is just a plain HTML file with styling done using Tailwind CSS. It utilizes a CDN to support Tailwind CSS.
In this article, we will not be using Tailwind CDN, but instead will set up React and Tailwind locally on our machine and copy the HTML we need to get our vibe coding going. Let’s get into it.
It is worth noting that exporting to a Figma file is only available in Standard mode.
These are a few things you have to keep at the back of your mind to get optimum results with Stitch:
With that out of the way, it is time to put the above-stated points into practice.
We are creating an e-commerce homepage for a fictional clothing brand. We need it to come with a hero section, featured products, and a newsletter banner.
Let’s distill the above into a clear and concise prompt:
A clean and warm-looking e-commerce homepage for a clothing startup. Includes hero section, featured products, and a newsletter banner:
Let’s further fine-tune our prompt:
A clean and warm-looking e-commerce homepage for a clothing startup called Skatee. Includes hero section, featured products, and a newsletter banner. Make Skatee the logo:
Did you notice the change? Our app logo was changed to Skatee, the name of the fictional clothing company we created.
We can even go a bit further by telling Stitch to change a particular image. Let’s refine our prompt.
Click on the Describe your design input and paste in the following prompt:
Change the image in the hero section to a lady wearing a Skirt and blouse:
Luckily for us, in the experimental version of Stitch, prompt output can be enhanced using images.
All we need to do is give Stitch a prompt and back it up with a product mockup, logo, or vibe board.
I downloaded a t-shirt image from Pixabay. Let’s use it to experiment with Stitch:
Open Stitch and switch to Experimental Mode. Then click the image icon to upload the image:
Select the t-shirt photo you downloaded.
Then paste in the prompt below:
A clean and warm-looking e-commerce homepage for a clothing startup called Skatee. Includes hero section, featured products, and a newsletter banner. Make Skatee the logo:
As seen from the generated design, Stitch included a similar-looking t-shirt to the one we uploaded.
Did you also notice the subtle orange colour theme it added to our design? This can also be done with a design mockup.
With all of that out of the way, we can now create our designs.
Our app consists of 5 pages:
Open Stitch and paste in the prompt below:
A clean orange color-themed e-commerce homepage for a clothing startup called Skatee. Includes hero section, featured products, and a newsletter banner. Also, create product listing, detail, add to cart, and checkout pages:
In the next section, we will experiment with creating a mobile version of our app using the same prompt.
Stitch has a separate tab for generating mobile versions of designs. Let’s try using the same prompt we used in generating the web version.
Click the mobile tab and add the following prompt:
A clean orange color theme e-commerce homepage for a clothing startup called Skatee. Includes hero section, featured products, and a newsletter banner. Also, create product listing, detail, add to cart, and checkout pages:
Like the web version we designed earlier, we can also customize it to our taste through prompts.
Let’s play around with it a bit and ask it to change the image in the hero section. Click on the Describe your design input and paste in the following prompt:
Change the image in the hero section to a man dressed in denim:
In this section, we will be looking at exporting generated designs into Figma and static HTML/CSS.
Click the Figma button at the top of the design, and paste it into Figma like so:
Here you can edit and customize the design like a normal Figma file, and collaborate with other designers.
Apart from the design file, Stitch also generates frontend code consisting of HTML and Tailwind CSS that you can copy and use in your project.
To copy the code, click on the design, and it will pop out like so:
Then click on code, and it will pop out a read-only code editor where you can copy the code for editing or use in your project:
The code editor in Stitch is read-only, so you may want to copy and paste it into your code editor for further tweaking.
Let’s get all the different pages of our e-commerce app working by using another AI tool, Google Gemini. We will prompt Gemini to create the React code for us.
We’ll give it the HTML we copied to create our e-commerce app.
Open up Gemini and give it the following prompt:
I have a React boilerplate already set up on my machine, together with Tailwind CSS. I will give you plain HTML files of 5 pages of an e-commerce site that you will make work with React.
The pages are the homepage, product Listing, detail, add to cart, and checkout. Make it into a working e-commerce site. Handle page routing with React Router DOM, and I want the pages to be interactive
After that, Gemini will ask for the following:
It will then spit out complete and working React code that we will copy and paste into an App.jsx file (if you used Vite to initialize your React app) or App.js file (if you used create-react-app).
You can even prompt Gemini to rewrite the HTML to any framework of your choice. Let’s see our code in action.
Here is the generated code for the React components:
// src/App.jsx (or src/App.js) import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router, Routes, Route, Link, useNavigate, useParams } from 'react-router-dom'; // --- Static Product Data (for demonstration) --- const PRODUCTS_DATA = [ { id: '1', name: 'Classic Tee', category: 'Clothing', price: 25, description: 'The Skatee Classic Tee is a staple for any wardrobe. Made from soft, breathable cotton, it offers all-day comfort and a timeless style. Available in a range of colors, this tee is perfect for everyday wear.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBXMANTPmkj7h7TmZrtwZxxdnXx-9AttRDxMOCExNe7y6S_T8lBMs9o8cBE30OUYqbTO88OUx1DJrpyoxw2BdMcTPO0zfaqANA548EIc6HLWOom7Z-0VQ9UviN1_5pzqK2oJuTCQtNibPEeSfwgKqdi1h2jPzJ5d313FVF9jnuuLAFcd0oYD-pl4_TrO24U85HSHXQUfgpisIH2yYoFs3JY1khGGeZUx82noAU2Fngl-GwGV_f35ejojRpzPMvhemRkLfDG-RJjoL0', // Using this image for detail page listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBGNk4iE4-2BmQhRXbQmXrttj2j_PclNEim82_calxmM491rk4nmkuI0p-wNnVjPm3m3HXEjLcE4yCc9tTiz3f1T5_26JNSZMvBBabS3E2VE4cV4aIfsCK9TdFjZncWRFBX8nX-EzVfKX-m7VrpHipXhjmNPzSONXn511Vy4dA4gbAMoYXOtrXmEBzHS_DqP2mR3xqyb1-np8bdLBei94S_xIXw7KKYQKnd5PZO4afiZBjlm2UKC-EsTonY0udoeOR7v4MMvX_xG1E', colors: [{ name: 'Orange', hex: '#FFA500' }, { name: 'Black', hex: '#000000' }, { name: 'White', hex: '#FFFFFF' }], sizes: ['S', 'M', 'L', 'XL'] }, { id: '2', name: 'Street Style Hoodie', category: 'Clothing', price: 60, description: 'Stay warm and stylish with our Street Style Hoodie. Designed for comfort and durability, perfect for those cooler skate sessions.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuAsLAggoIiF2T5RNQGjYB2fnO6-MplQman0Ls4gnl9jXSuPa1yGEgCmOiWDVrquc8vEoxM8dw9IiUFM7cTieWJIhh9y9DCKSKig2WdngS76E54PilSLOoLgKHMD-zLUpOvfRRhnHg6Ki-pGI0s_VtaD36iVkZ7yWxCOVyeDBQowDLcnUM6eRnmU54s6cwOz6RILTGy-O_lsuCczMZ-4jFjVMAWGuqHfvB5hra9wJflsxKVRalxkx2Jb3hiTBPoeLqKYpcn6m_5h-zQ', listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuAsLAggoIiF2T5RNQGjYB2fnO6-MplQman0Ls4gnl9jXSuPa1yGEgCmOiWDVrquc8vEoxM8dw9IiUFM7cTieWJIhh9y9DCKSKig2WdngS76E54PilSLOoLgKHMD-zLUpOvfRRhnHg6Ki-pGI0s_VtaD36iVkZ7yWxCOVyeDBQowDLcnUM6eRnmU54s6cwOz6RILTGy-O_lsuCczMZ-4jFjVMAWGuqHfvB5hra9wJflsxKVRalxkx2Jb3hiTBPoeLqKYpcn6m_5h-zQ', colors: [{ name: 'Black', hex: '#000000' }, { name: 'Grey', hex: '#808080' }], sizes: ['S', 'M', 'L', 'XL'] }, { id: '3', name: 'Skate Pants', category: 'Clothing', price: 45, description: 'Durable and flexible skate pants, built to withstand the demands of the street while providing maximum comfort.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuB0HEKVuRt9ZqY2oTp_-e-fVgiyO6qjcnkLZWRzhp7sy8nC39Vles6s_pxQVLdrhkH_XaKLK7m_NDrzBb4M_6n3XCYIF0x1hgtitY9HzVYMN2sk04OylVnGsPojvnJFVYc6LdvbaRpYQUyu4N4JcCQ3rCoaw-z5HCQ0PlM9TnVranDbP5lTeaBTyYuiZpe93d9HbG-CLyosQ97dQ627Qy6quilvqYG4jOERkja_YU4UyPaW0GE9R3_B8hSXNgi4BTbriTvNmOfjXj0', listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuB0HEKVuRt9ZqY2oTp_-e-fVgiyO6qjcnkLZWRzhp7sy8nC39Vles6s_pxQVLdrhkH_XaKLK7m_NDrzBb4M_6n3XCYIF0x1hgtitY9HzVYMN2sk04OylVnGsPojvnJFVYc6LdvbaRpYQUyu4N4JcCQ3rCoaw-z5HCQ0PlM9TnVranDbP5lTeaBTyYuiZpe93d9HbG-CLyosQ97dQ627Qy6quilvqYG4jOERkja_YU4UyPaW0GE9R3_B8hSXNgi4BTbriTvNmOfjXj0', colors: [{ name: 'Khaki', hex: '#C3B091' }, { name: 'Black', hex: '#000000' }], sizes: ['28', '30', '32', '34', '36'] }, { id: '4', name: 'Summer Shorts', category: 'Clothing', price: 30, description: 'Lightweight and breathable shorts, ideal for summer rides and casual wear. Freedom of movement guaranteed.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuB8ldI-IfPk5aiMmizXwRv8rZIS9i3H6XTSDo_IvBA8dkAXPaMp3UrQP0y0UVEBTHjPTY6I5-eCKTpILxdWxYn7KUmwwRUp1TNMpbOlrBWXMP8qJIZT2I5Up7I-1Pk5fRS-hz1fRCQTUlFFuzXNe9CZJ8m1CPI6opXpIT4NmEXj_WD_3gOBBW0c_z-xVZa70J9wq9C9ki8ErCcY7WtlcJpgcgcbS3dDyvvA4ING70VoXPvs-eQS9aP7kHVjigg801y8wS9t2ewhbx4', listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuB8ldI-IfPk5aiMmizXwRv8rZIS9i3H6XTSDo_IvBA8dkAXPaMp3UrQP0y0UVEBTHjPTY6I5-eCKTpILxdWxYn7KUmwwRUp1TNMpbOlrBWXMP8qJIZT2I5Up7I-1Pk5fRS-hz1fRCQTUlFFuzXNe9CZJ8m1CPI6opXpIT4NmEXj_WD_3gOBBW0c_z-xVZa70J9wq9C9ki8ErCcY7WtlcJpgcgcbS3dDyvvA4ING70VoXPvs-eQS9aP7kHVjigg801y8wS9t2ewhbx4', colors: [{ name: 'Blue', hex: '#0000FF' }, { name: 'Green', hex: '#008000' }], sizes: ['S', 'M', 'L', 'XL'] }, { id: '5', name: 'Graphic Tee', category: 'Clothing', price: 28, description: 'Express yourself with our unique graphic tees. Featuring custom designs that pop, made from soft, high-quality cotton.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuD7HeARdxPqbaTU4pQxzNBWt7DIpC_uy_c0RJxyPCbcAsWzGfvhzmleYyo68Wp_RWvPhH8ymqvUuOQFuCbK1gmdjwmkT8VfsN8D8azVy8COw2zUVG_UFYB-W9Uqds4oxeO5CDZ_cQ2rF9ly1BzTY5XHXizsslfG1tImv4T-AIBTJ0iqaDdKtMrsmGJ4qVyR2weoNGlXRKyHLKosL-1OoxfFMAzKyehYpFC2hnCbxdZhQPKDv6pAQlCwioFfVrnVl_5h1pGN1GTDxkE', listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuD7HeARdxPqbaTU4pQxzNBWt7DIpC_uy_c0RJxyPCbcAsWzGfvhzmleYyo68Wp_RWvPhH8ymqvUuOQFuCbK1gmdjwmkT8VfsN8D8azVy8COw2zUVG_UFYB-W9Uqds4oxeO5CDZ_cQ2rF9ly1BzTY5XHXizsslfG1tImv4T-AIBTJ0iqaDdKtMrsmGJ4qVyR2weoNGlXRKyHLKosL-1OoxfFMAzKyehYpFC2hnCbxdZhQPKDv6pAQlCwioFfVrnVl_5h1pGN1GTDxkE', colors: [{ name: 'White', hex: '#FFFFFF' }, { name: 'Black', hex: '#000000' }], sizes: ['S', 'M', 'L', 'XL'] }, { id: '6', name: 'Urban Jacket', category: 'Clothing', price: 80, description: 'The Urban Jacket combines street style with practical durability. Perfect for layering and protecting against the elements.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuB8wNFx71-tbY7OkHzfVLsYUTIEyuxJQaaP7BkvjjFx9lQ8SsLO0BULZrm1iFKjQz9aU47GK4Zj2qy7bxZXG9oqh8SkQ76ClttnUvrlwuXeHlEA5VgjPX61kBbpPaarmk3G_pxXRbpiYTatxWmewHEVpVwH4g9T_BhnSgzjgI_4NwbglVsMfeLAP6l43jxuLcMHnEGC93cMKX08320UBBfNEtHjzI2pZ7qVRbeH-gDWfiONkMRbqA6h7lJTx4TbwDbVPBfPW2zXwPI', listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuB8wNFx71-tbY7OkHzfVLsYUTIEyuxJQaaP7BkvjjFx9lQ8SsLO0BULZrm1iFKjQz9aU47GK4Zj2qy7bxZXG9oqh8SkQ76ClttnUvrlwuXeHlEA5VgjPX61kBbpPaarmk3G_pxXRbpiYTatxWmewHEVpVwH4g9T_BhnSgzjgI_4NwbglVsMfeLAP6l43jxuLcMHnEGC93cMKX08320UBBfNEtHjzI2pZ7qVRbeH-gDWfiONkMRbqA6h7lJTx4TbwDbVPBfPW2zXwPI', colors: [{ name: 'Grey', hex: '#808080' }, { name: 'Navy', hex: '#000080' }], sizes: ['S', 'M', 'L', 'XL'] }, { id: '7', name: 'Cargo Pants', category: 'Clothing', price: 50, description: 'Utility meets style with our Cargo Pants. Plenty of pockets for your essentials, and a comfortable fit for all-day wear.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBfw6nzzMm0eGwES5ZoEvQUPd9h7U1ea460PilQgJdxMEN0B00xK3f0ZDNYWZnhuSvWBhZZcamUQHUGS_VK0sXqvRLk1qJeM0i5CPTLXyt2hcJ4QCvVWROCo4wPDRLvZ1x62mE3wCX-fP3jYYFFvj0-Z14KgmEpGRCb05723xN2t-T6YdaUlRG28qAyBsKZtQMMdB6a5ojebh88laPNsbS1S8Q7pp6fX_eiVcnucgbfZxnYosULFBrgHMpo_5VaWxJsd6y-zb3uMRA', listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBfw6nzzMm0eGwES5ZoEvQUPd9h7U1ea460PilQgJdxMEN0B00xK3f0ZDNYWZnhuSvWBhZZcamUQHUGS_VK0sXqvRLk1qJeM0i5CPTLXyt2hcJ4QCvVWROCo4wPDRLvZ1x62mE3wCX-fP3jYYFFvj0-Z14KgmEpGRCb05723xN2t-T6YdaUlRG28qAyBsKZtQMMdB6a5ojebh88laPNsbS1S8Q7pp6fX_eiVcnucgbfZxnYosULFBrgHMpo_5VaWxJsd6y-zb3uMRA', colors: [{ name: 'Olive', hex: '#808000' }, { name: 'Black', hex: '#000000' }], sizes: ['28', '30', '32', '34', '36'] }, { id: '8', name: 'Beach Shorts', category: 'Clothing', price: 20, description: 'Relaxed fit beach shorts, perfect for a day by the water or a casual hangout.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBrEVoTbQpwk1V56W-7slr5DMRUCvBRKNnq_igcAOX0xmd3O7hLocIvcfWFYEwd2DZP6oKvFln-nleeelvb5wd46zC65GQ6weIfL-HJ1QQmeKLTsl_261adQpLTRRive7_Tkc--tLzNxhpZo1fPDSWCDo1rpAOa_27GD1HHfCBccqQISGS2orjGalHUczOR2C9aGKPP30vXGfKy05j4cy7FlSA10AAXnp_RtTvrit4AyPUyCNZ7FZhUbkqtVdMzfc8Ev98pJcZrNNw', listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBrEVoTbQpwk1V56W-7slr5DMRUCvBRKNnq_igcAOX0xmd3O7hLocIvcfWFYEwd2DZP6oKvFln-nleeelvb5wd46zC65GQ6weIfL-HJ1QQmeKLTsl_261adQpLTRRive7_Tkc--tLzNxhpZo1fPDSWCDo1rpAOa_27GD1HHfCBccqQISGS2orjGalHUczOR2C9aGKPP30vXGfKy05j4cy7FlSA10AAXnp_RtTvrit4AyPUyCNZ7FZhUbkqtVdMzfc8Ev98pJcZrNNw', colors: [{ name: 'Teal', hex: '#008080' }, { name: 'Yellow', hex: '#FFFF00' }], sizes: ['S', 'M', 'L', 'XL'] }, // --- New products for homepage featured section --- { id: '15', name: 'Skatee Denim Jacket', category: 'Clothing', price: 75, description: 'A classic denim jacket for any season. Durable and stylish.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuDwFefe_gc1gr5cGgjORPUtetQJNftRdA29eYghmWXsmCXDcFc9NlLhVnZtNUUHXDQ53TNu_MriTOAXQJ_durtJnt0vYHZKVEXA11lbKXQJLdxESiY7GOpPikyOV5BYa3cPy9TOlnF7tB1BLgApTmQj8QGrQC4mYa6k8l3UHrLAg_NnZqCZx2n8VHDRxMGPH8o1IUDDUSjA7mm92hWnOfdDVSsl71W_MNezJydf6VjPvDjJA9cBDGm6Or3xZBIqN1MJmjoCYlGHnT4', listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuDwFefe_gc1gr5cGgjORPUtetQJNftRdA29eYghmWXsmCXDcFc9NlLhVnZtNUUHXDQ53TNu_MriTOAXQJ_durtJnt0vYHZKVEXA11lbKXQJLdxESiY7GOpPikyOV5BYa3cPy9TOlnF7tB1BLgApTmQj8QGrQC4mYa6k8l3UHrLAg_NnZqCZx2n8VHDRxMGPH8o1IUDDUSjA7mm92hWnOfdDVSsl71W_MNezJydf6VjPvDjJA9cBDGm6Or3xZBIqN1MJmjoCYlGHnT4', colors: [{ name: 'Blue Denim', hex: '#4169E1' }], sizes: ['S', 'M', 'L', 'XL'] }, { id: '16', name: 'Skatee Joggers', category: 'Clothing', price: 50, description: 'Comfortable and trendy joggers, perfect for lounging or active wear.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuA0MLr_gzrRWri80KnuHyYMpq8nVakJGCeMb8qJf6AVa_rEG3pAL3wXqGJYfUbEBW935vDYWBdAgbNG_3gRzPVJG_FPtsabl7NtnqiGxHG0WcYu1edHZL-B2TxI8P4aHGV-RTUsaTtHq_Iuqc2YbV5aIt-yfzldSsAxRxA_R62kaJ9oPyfH4ZwI3lgyhQ0Tl-9-8E1ktUmQx4e5REh2-eP0OveAH27RoibiYszBxi6Jk9l_Evc8WH_GboklT1L4n8UD6RvLLOvuNT0', listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuA0MLr_gzrRWri80KnuHyYMpq8nVakJGCeMb8qJf6AVa_rEG3pAL3wXqGJYfUbEBW935vDYWBdAgbNG_3gRzPVJG_FPtsabl7NtnqiGxHG0WcYu1edHZL-B2TxI8P4aHGV-RTUsaTtHq_Iuqc2YbV5aIt-yfzldSsAxRxA_R62kaJ9oPyfH4ZwI3lgyhQ0Tl-9-8E1ktUmQx4e5REh2-eP0OveAH27RoibiYszBxi6Jk9l_Evc8WH_GboklT1L4n8UD6RvLLOvuNT0', colors: [{ name: 'Grey', hex: '#808080' }], sizes: ['S', 'M', 'L', 'XL'] }, { id: '17', name: 'Skatee Snapback', category: 'Accessories', price: 20, description: 'A stylish snapback cap to complete your look.', imageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuDc8gXONfaw9T2V8RJ23VYFpLrPz_PYPn-CuBlGRFzLAOrtAJ8nqbwE9IXWo3Y4YqKTDoymx8J8ZHLQrp92s26BBhCSZnbmnGyGX3VVHb16amciZlKg6iZUDxngLPDUzFHBdZ262eI5jIgbXJxjxHkS1zFFJkL1OOcCkcCYRL-RdZSHX_ZsQS1farab6wZkHas3XsPH5UUpPxTMLz-nO7Duvjh44jfjrnBadau6n4i_fkrB0WDnC4LkOHRROHC3h0_0Wk5y-klXY3I', listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuDc8gXONfaw9T2V8RJ23VYFpLrPz_PYPn-CuBlGRFzLAOrtAJ8nqbwE9IXWo3Y4YqKTDoymx8J8ZHLQrp92s26BBhCSZnbmnGyGX3VVHb16amciZlKg6iZUDxngLPDUzFHBdZ262eI5jIgbXJxjxHkS1zFFJkL1OOcCkcCYRL-RdZSHX_ZsQS1farab6wZkHas3XsPH5UUpPxTMLz-nO7Duvjh44jfjrnBadau6n4i_fkrB0WDnC4LkOHRROHC3h0_0Wk5y-klXY3I', colors: [{ name: 'Black', hex: '#000000' }], sizes: ['One Size'] } ]; // --- Reusable Header Component --- function Header({ cartItemCount = 0 }) { return ( <header className="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-[#f4ede7] px-10 py-3"> <div className="flex items-center gap-4 text-[#1c140d]"> <Link to="/" className="flex items-center gap-4 text-[#1c140d]"> <div className="size-4"> <svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 6H42L36 24L42 42H6L12 24L6 6Z" fill="currentColor"></path></svg> </div> <h2 className="text-[#1c140d] text-lg font-bold leading-tight tracking-[-0.015em]">Skatee</h2> </Link> </div> <div className="flex flex-1 justify-end gap-8"> <div className="flex items-center gap-9"> <Link className="text-[#1c140d] text-sm font-medium leading-normal" to="/products">New Arrivals</Link> <Link className="text-[#1c140d] text-sm font-medium leading-normal" to="/products">Men</Link> <Link className="text-[#1c140d] text-sm font-medium leading-normal" to="/products">Women</Link> <Link className="text-[#1c140d] text-sm font-medium leading-normal" to="/products">Accessories</Link> <Link className="text-[#1c140d] text-sm font-medium leading-normal" to="/products">Sale</Link> </div> <div className="flex gap-2"> <button className="flex max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 bg-[#f4ede7] text-[#1c140d] gap-2 text-sm font-bold leading-normal tracking-[0.015em] min-w-0 px-2.5" > <div className="text-[#1c140d]" data-icon="MagnifyingGlass" data-size="20px" data-weight="regular"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256"> <path d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z" ></path> </svg> </div> </button> <button className="flex max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 bg-[#f4ede7] text-[#1c140d] gap-2 text-sm font-bold leading-normal tracking-[0.015em] min-w-0 px-2.5" > <div className="text-[#1c140d]" data-icon="User" data-size="20px" data-weight="regular"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256"> <path d="M230.92,212c-15.23-26.33-38.7-45.21-66.09-54.16a72,72,0,1,0-73.66,0C63.78,166.78,40.31,185.66,25.08,212a8,8,0,1,0,13.85,8c18.84-32.56,52.14-52,89.07-52s70.23,19.44,89.07,52a8,8,0,1,0,13.85-8ZM72,96a56,56,0,1,1,56,56A56.06,56.06,0,0,1,72,96Z" ></path> </svg> </div> </button> <Link to="/checkout" className="flex max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 bg-[#f4ede7] text-[#1c140d] gap-2 text-sm font-bold leading-normal tracking-[0.015em] min-w-0 px-2.5" > <div className="text-[#1c140d]" data-icon="ShoppingBag" data-size="20px" data-weight="regular"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256"> <path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,160H40V56H216V200ZM176,88a48,48,0,0,1-96,0,8,8,0,0,1,16,0,32,32,0,0,0,64,0,8,8,0,0,1,16,0Z" ></path> </svg> </div> {cartItemCount > 0 && ( <span className="ml-1 text-xs">{cartItemCount}</span> )} </Link> </div> </div> </header> ); } // --- Footer Component --- function Footer() { return ( <footer className="flex justify-center"> <div className="flex max-w-[960px] flex-1 flex-col"> <footer className="flex flex-col gap-6 px-5 py-10 text-center @container"> <div className="flex flex-wrap items-center justify-center gap-6 @[480px]:flex-row @[480px]:justify-around"> <Link className="text-[#9c7049] text-base font-normal leading-normal min-w-40" to="#">About Us</Link> <Link className="text-[#9c7049] text-base font-normal leading-normal min-w-40" to="#">Contact</Link> <Link className="text-[#9c7049] text-base font-normal leading-normal min-w-40" to="#">FAQ</Link> <Link className="text-[#9c7049] text-base font-normal leading-normal min-w-40" to="#">Shipping & Returns</Link> <Link className="text-[#9c7049] text-base font-normal leading-normal min-w-40" to="#">Privacy Policy</Link> </div> <div className="flex flex-wrap justify-center gap-4"> <Link to="#"> <div className="text-[#9c7049]" data-icon="TwitterLogo" data-size="24px" data-weight="regular"> <svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 256 256"> <path d="M247.39,68.94A8,8,0,0,0,240,64H209.57A48.66,48.66,0,0,0,168.1,40a46.91,46.91,0,0,0-33.75,13.7A47.9,47.9,0,0,0,120,88v6.09C79.74,83.47,46.81,50.72,46.46,50.37a8,8,0,0,0-13.65,4.92c-4.31,47.79,9.57,79.77,22,98.18a110.93,110.93,0,0,0,21.88,24.2c-15.23,17.53-39.21,26.74-39.47,26.84a8,8,0,0,0-3.85,11.93c.75,1.12,3.75,5.05,11.08,8.72C53.51,229.7,65.48,232,80,232c70.67,0,129.72-54.42,135.75-124.44l29.91-29.9A8,8,0,0,0,247.39,68.94Zm-45,29.41a8,8,0,0,0-2.32,5.14C196,166.58,143.28,216,80,216c-10.56,0-18-1.4-23.22-3.08,11.51-6.25,27.56-17,37.88-32.48A8,8,0,0,0,92,169.08c-.47-.27-43.91-26.34-44-96,16,13,45.25,33.17,78.67,38.79A8,8,0,0,0,136,104V88a32,32,0,0,1,9.6-22.92A30.94,30.94,0,0,1,167.9,56c12.66.16,24.49,7.88,29.44,19.21A8,8,0,0,0,204.67,80h16Z" ></path> </svg> </div> </Link> <Link to="#"> <div className="text-[#9c7049]" data-icon="InstagramLogo" data-size="24px" data-weight="regular"> <svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 256 256"> <path d="M128,80a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160ZM176,24H80A56.06,56.06,0,0,0,24,80v96a56.06,56.06,0,0,0,56,56h96a56.06,56.06,0,0,0,56-56V80A56.06,56.06,0,0,0,176,24Zm40,152a40,40,0,0,1-40,40H80a40,40,0,0,1-40-40V80A40,40,0,0,1,80,40h96a40,40,0,0,1,40,40ZM192,76a12,12,0,1,1-12-12A12,12,0,0,1,192,76Z" ></path> </svg> </div> </Link> <Link to="#"> <div className="text-[#9c7049]" data-icon="FacebookLogo" data-size="24px" data-weight="regular"> <svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 256 256"> <path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm8,191.63V152h24a8,8,0,0,0,0-16H136V112a16,16,0,0,1,16-16h16a8,8,0,0,0,0-16H152a32,32,0,0,0-32,32v24H96a8,8,0,0,0,0,16h24v63.63a88,88,0,1,1,16,0Z" ></path> </svg> </div> </Link> </div> <p className="text-[#9c7049] text-base font-normal leading-normal">@2024 Skatee. All rights reserved.</p> </footer> </div> </footer> ); } // --- Home Page Component --- function HomePage() { // Featured products from the new HTML (ensure these IDs exist in PRODUCTS_DATA) const featuredProducts = [ { id: '1', name: 'Skatee Classic Tee', price: 25, listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuA5wRPdj_rIsh_QY6zFaUry42zg_0eQIDBoGAgoKFijnDInGmzqDmaide7Z7vshEnQk3ueK9dVuq6Z1SHDsTtOVXA2lxn84lJeBWcwhdQN23WDhkl60tq0N12WNTF3tmZQrdhr5oR0g4xv5qCM1Bymy1bF3mjuP8kiTsCUgZeA2_6njinrgmqVYxgVoM3B5Orl7ij1rXOhtFZa-EfN-ay-ldWEpZ3wcwIeBe8_9DyLoKANoKVkgOyqlGmXhfa4OKufW_0OTh1Vv0u0' }, { id: '15', name: 'Skatee Denim Jacket', price: 75, listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuDwFefe_gc1gr5cGgjORPUtetQJNftRdA29eYghmWXsmCXDcFc9NlLhVnZtNUUHXDQ53TNu_MriTOAXQJ_durtJnt0vYHZKVEXA11lbKXQJLdxESiY7GOpPikyOV5BYa3cPy9TOlnF7tB1BLgApTmQj8QGrQC4mYa6k8l3UHrLAg_NnZqCZx2n8VHDRxMGPH8o1IUDDUSjA7mm92hWnOfdDVSsl71W_MNezJydf6VjPvDjJA9cBDGm6Or3xZBIqN1MJmjoCYlGHnT4' }, { id: '16', name: 'Skatee Joggers', price: 50, listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuA0MLr_gzrRWri80KnuHyYMpq8nVakJGCeMb8qJf6AVa_rEG3pAL3wXqGJYfUbEBW935vDYWBdAgbNG_3gRzPVJG_FPtsabl7NtnqiGxHG0WcYu1edHZL-B2TxI8P4aHGV-RTUsaTtHq_Iuqc2YbV5aIt-yfzldSsAxRxA_R62kaJ9oPyfH4ZwI3lgyhQ0Tl-9-8E1ktUmQx4e5REh2-eP0OveAH27RoibiYszBxi6Jk9l_Evc8WH_GboklT1L4n8UD6RvLLOvuNT0' }, { id: '17', name: 'Skatee Snapback', price: 20, listingImageUrl: 'https://lh3.googleusercontent.com/aida-public/AB6AXuDc8gXONfaw9T2V8RJ23VYFpLrPz_PYPn-CuBlGRFzLAOrtAJ8nqbwE9IXWo3Y4YqKTDoymx8J8ZHLQrp92s26BBhCSZnbmnGyGX3VVHb16amciZlKg6iZUDxngLPDUzFHBdZ262eI5jIgbXJxjxHkS1zFFJkL1OOcCkcCYRL-RdZSHX_ZsQS1farab6wZkHas3XsPH5UUpPxTMLz-nO7Duvjh44jfjrnBadau6n4i_fkrB0WDnC4LkOHRROHC3h0_0Wk5y-klXY3I' }, ]; return ( <div className="px-40 flex flex-1 justify-center py-5"> <div className="layout-content-container flex flex-col max-w-[960px] flex-1"> <div className="@container"> <div className="@[480px]:p-4"> <div className="flex min-h-[480px] flex-col gap-6 bg-cover bg-center bg-no-repeat @[480px]:gap-8 @[480px]:rounded-lg items-start justify-end px-4 pb-10 @[480px]:px-10" style={{ backgroundImage: 'linear-gradient(rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.4) 100%), url("https://lh3.googleusercontent.com/aida-public/AB6AXuDnHvZmUtgx_e1v1ai0btjgvG8jhqdFj2eLtp3LiW2QfWMO0LyBtIcAtLKEDYbCzG7F9i5Ei6GTQgVjOC1lAz_P7V4auN63zO1_ZJfFxTXxe3hZT9L7xxkYc5J5SvFBbCPzha4ke0_m7Bd5AKUKKNZ-nJFHZ43WMgv7DXiXvRkHkDcuz_en4J2sXXEyTkr7oL-0Ovk6cItBBFQ9-IihQAE1r_Aoku1QT5vRJx_ROsHzAkEMwv-FthS3X98p5rRxuE8xgkwaEHZphzc")' }} > <div className="flex flex-col gap-2 text-left"> <h1 className="text-white text-4xl font-black leading-tight tracking-[-0.033em] @[480px]:text-5xl @[480px]:font-black @[480px]:leading-tight @[480px]:tracking-[-0.033em]" > Skatee's New Collection </h1> <h2 className="text-white text-sm font-normal leading-normal @[480px]:text-base @[480px]:font-normal @[480px]:leading-normal"> Explore the latest styles and trends in our new collection. Find your perfect fit and express your unique style. </h2> </div> <Link to="/products" className="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 @[480px]:h-12 @[480px]:px-5 bg-[#f38220] text-[#1c140d] text-sm font-bold leading-normal tracking-[0.015em] @[480px]:text-base @[480px]:font-bold @[480px]:leading-normal @[480px]:tracking-[0.015em]" > <span className="truncate">Shop Now</span> </Link> </div> </div> </div> <h2 className="text-[#1c140d] text-[22px] font-bold leading-tight tracking-[-0.015em] px-4 pb-3 pt-5">Featured Products</h2> <div className="grid grid-cols-[repeat(auto-fit,minmax(158px,1fr))] gap-3 p-4"> {featuredProducts.map(product => ( <div key={product.id} className="flex flex-col gap-3 pb-3"> <Link to={`/products/${product.id}`}> <div className="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-lg" style={{ backgroundImage: `url("${product.listingImageUrl}")` }} ></div> </Link> <div> <p className="text-[#1c140d] text-base font-medium leading-normal">{product.name}</p> <p className="text-[#9c7049] text-sm font-normal leading-normal">${product.price}</p> </div> </div> ))} </div> <div className="@container"> <div className="flex flex-col justify-end gap-6 px-4 py-10 @[480px]:gap-8 @[480px]:px-10 @[480px]:py-20"> <div className="flex flex-col gap-2 text-center"> <h1 className="text-[#1c140d] tracking-light text-[32px] font-bold leading-tight @[480px]:text-4xl @[480px]:font-black @[480px]:leading-tight @[480px]:tracking-[-0.033em] max-w-[720px]" > Stay up to date with Skatee </h1> <p className="text-[#1c140d] text-base font-normal leading-normal max-w-[720px]"> Sign up for our newsletter to receive exclusive offers and be the first to know about new arrivals. </p> </div> <div className="flex flex-1 justify-center"> <label className="flex flex-col min-w-40 h-14 max-w-[480px] flex-1 @[480px]:h-16"> <div className="flex w-full flex-1 items-stretch rounded-lg h-full"> <input placeholder="Enter your email" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border-none bg-[#f4ede7] focus:border-none h-full placeholder:text-[#9c7049] px-4 rounded-r-none border-r-0 pr-2 text-sm font-normal leading-normal @[480px]:text-base @[480px]:font-normal @[480px]:leading-normal" value="" /> <div className="flex items-center justify-center rounded-r-lg border-l-0 border-none bg-[#f4ede7] pr-2"> <button className="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 @[480px]:h-12 @[480px]:px-5 bg-[#f38220] text-[#1c140d] text-sm font-bold leading-normal tracking-[0.015em] @[480px]:text-base @[480px]:font-bold @[480px]:leading-normal @[480px]:tracking-[0.015em]" > <span className="truncate">Subscribe</span> </button> </div> </div> </label> </div> </div> </div> </div> </div> ); } // --- Product Listing Page Component --- function ProductListingPage() { const [selectedSize, setSelectedSize] = useState(''); const [selectedColor, setSelectedColor] = useState(''); const [selectedPrice, setSelectedPrice] = useState(''); // Not fully implemented, placeholder const filteredProducts = PRODUCTS_DATA.filter(product => { let matches = true; if (selectedSize && !product.sizes.includes(selectedSize)) { matches = false; } if (selectedColor && !product.colors.some(c => c.name === selectedColor)) { matches = false; } // Basic price filter logic (can be expanded) if (selectedPrice) { if (selectedPrice === 'Under $30' && product.price >= 30) matches = false; if (selectedPrice === '$30 - $50' && (product.price < 30 || product.price > 50)) matches = false; if (selectedPrice === 'Over $50' && product.price <= 50) matches = false; } return matches; }); return ( <div className="px-40 flex flex-1 justify-center py-5"> <div className="layout-content-container flex flex-col max-w-[960px] flex-1"> <div className="flex flex-wrap gap-2 p-4"> <Link className="text-[#9c7049] text-base font-medium leading-normal" to="/">Home</Link> <span className="text-[#9c7049] text-base font-medium leading-normal">/</span> <span className="text-[#1c140d] text-base font-medium leading-normal">Clothing</span> </div> <div className="flex flex-wrap justify-between gap-3 p-4"> <p className="text-[#1c140d] tracking-light text-[32px] font-bold leading-tight min-w-72">Clothing</p> </div> <div className="flex gap-3 p-3 flex-wrap pr-4"> <button className="flex h-8 shrink-0 items-center justify-center gap-x-2 rounded-lg bg-[#f4ede7] pl-4 pr-2" onClick={() => setSelectedSize(selectedSize === 'S' ? '' : 'S')} // Toggle example > <p className="text-[#1c140d] text-sm font-medium leading-normal">Size: {selectedSize || 'All'}</p> <div className="text-[#1c140d]" data-icon="CaretDown" data-size="20px" data-weight="regular"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256"> <path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"></path> </svg> </div> {/* A simple dropdown would be more complex here */} </button> <button className="flex h-8 shrink-0 items-center justify-center gap-x-2 rounded-lg bg-[#f4ede7] pl-4 pr-2" onClick={() => setSelectedColor(selectedColor === 'Black' ? '' : 'Black')} // Toggle example > <p className="text-[#1c140d] text-sm font-medium leading-normal">Color: {selectedColor || 'All'}</p> <div className="text-[#1c140d]" data-icon="CaretDown" data-size="20px" data-weight="regular"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256"> <path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"></path> </svg> </div> </button> <button className="flex h-8 shrink-0 items-center justify-center gap-x-2 rounded-lg bg-[#f4ede7] pl-4 pr-2" onClick={() => setSelectedPrice(selectedPrice === 'Over $50' ? '' : 'Over $50')} // Toggle example > <p className="text-[#1c140d] text-sm font-medium leading-normal">Price: {selectedPrice || 'All'}</p> <div className="text-[#1c140d]" data-icon="CaretDown" data-size="20px" data-weight="regular"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256"> <path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"></path> </svg> </div> </button> </div> <div className="grid grid-cols-[repeat(auto-fit,minmax(158px,1fr))] gap-3 p-4"> {filteredProducts.map(product => ( <div key={product.id} className="flex flex-col gap-3 pb-3"> <Link to={`/products/${product.id}`}> <div className="w-full bg-center bg-no-repeat aspect-[3/4] bg-cover rounded-lg" style={{ backgroundImage: `url("${product.listingImageUrl}")` }} ></div> </Link> <div> <p className="text-[#1c140d] text-base font-medium leading-normal">{product.name}</p> <p className="text-[#9c7049] text-sm font-normal leading-normal">${product.price}</p> </div> </div> ))} </div> </div> </div> ); } // --- Product Detail Page Component --- function ProductDetailPage({ handleAddToCart }) { const { id } = useParams(); const navigate = useNavigate(); const product = PRODUCTS_DATA.find(p => p.id === id); const [selectedColor, setSelectedColor] = useState(product?.colors[0]?.name || ''); const [selectedSize, setSelectedSize] = useState(product?.sizes[0] || ''); useEffect(() => { // Reset selections if product changes if (product) { setSelectedColor(product.colors[0]?.name || ''); setSelectedSize(product.sizes[0] || ''); } }, [product]); if (!product) { return ( <div className="px-40 flex flex-1 justify-center py-5"> <div className="layout-content-container flex flex-col max-w-[960px] flex-1 text-center py-10"> <h1 className="text-[#1c140d] text-[22px] font-bold leading-tight tracking-[-0.015em]">Product Not Found</h1> <p className="text-[#9c7049] text-base font-normal leading-normal pt-2"> The product you are looking for does not exist. </p> <Link to="/products" className="text-[#f38220] text-base font-bold leading-normal mt-4"> Back to Products </Link> </div> </div> ); } const onAddToCart = () => { handleAddToCart({ id: product.id, name: product.name, price: product.price, imageUrl: product.imageUrl, color: selectedColor, size: selectedSize, quantity: 1 }); navigate('/add-to-cart-confirmation'); }; return ( <div className="px-40 flex flex-1 justify-center py-5"> <div className="layout-content-container flex flex-col max-w-[960px] flex-1"> <div className="flex flex-wrap gap-2 p-4"> <Link className="text-[#9c7049] text-base font-medium leading-normal" to="/products">Men</Link> <span className="text-[#9c7049] text-base font-medium leading-normal">/</span> <span className="text-[#1c140d] text-base font-medium leading-normal">T-Shirts</span> </div> <div className="flex w-full grow bg-[#fcfaf8] @container p-4"> <div className="w-full gap-1 overflow-hidden bg-[#fcfaf8] @[480px]:gap-2 aspect-[2/3] rounded-lg flex"> <div className="w-full bg-center bg-no-repeat bg-cover aspect-auto rounded-none flex-1" style={{ backgroundImage: `url("${product.imageUrl}")` }} ></div> </div> </div> <h1 className="text-[#1c140d] text-[22px] font-bold leading-tight tracking-[-0.015em] px-4 text-left pb-3 pt-5">{product.name}</h1> <p className="text-[#9c7049] text-sm font-normal leading-normal pb-3 pt-1 px-4">Men's T-Shirt</p> <p className="text-[#1c140d] text-base font-normal leading-normal pb-3 pt-1 px-4"> {product.description} </p> <h3 className="text-[#1c140d] text-lg font-bold leading-tight tracking-[-0.015em] px-4 pb-2 pt-4">Color</h3> <div className="flex flex-wrap gap-5 p-4"> {product.colors.map(color => ( <label key={color.name} className={`size-10 rounded-full border border-[#e8dace] ring-[color-mix(in_srgb,#1c140d_50%,_transparent)] has-[:checked]:border-[3px] has-[:checked]:border-[#fcfaf8] has-[:checked]:ring cursor-pointer`} style={{ backgroundColor: color.hex }} > <input type="radio" className="invisible" name="product-color" value={color.name} checked={selectedColor === color.name} onChange={() => setSelectedColor(color.name)} /> </label> ))} </div> <h3 className="text-[#1c140d] text-lg font-bold leading-tight tracking-[-0.015em] px-4 pb-2 pt-4">Size</h3> <div className="flex flex-wrap gap-3 p-4"> {product.sizes.map(size => ( <label key={size} className={`text-sm font-medium leading-normal flex items-center justify-center rounded-lg border border-[#e8dace] px-4 h-11 text-[#1c140d] has-[:checked]:border-[3px] has-[:checked]:px-3.5 has-[:checked]:border-[#f38220] relative cursor-pointer`} > {size} <input type="radio" className="invisible absolute" name="product-size" value={size} checked={selectedSize === size} onChange={() => setSelectedSize(size)} /> </label> ))} </div> <div className="flex px-4 py-3"> <button onClick={onAddToCart} className="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-12 px-5 flex-1 bg-[#f38220] text-[#1c140d] text-base font-bold leading-normal tracking-[0.015em]" > <span className="truncate">Add to Cart</span> </button> </div> </div> </div> ); } // --- Add to Cart Confirmation Page Component --- function AddToCartConfirmationPage() { return ( <div className="px-40 flex flex-1 justify-center py-5"> <div className="layout-content-container flex flex-col w-[512px] max-w-[512px] py-5 max-w-[960px] flex-1"> <h1 className="text-[#1c140d] text-[22px] font-bold leading-tight tracking-[-0.015em] px-4 text-center pb-3 pt-5">Added to Cart</h1> <div className="flex w-full grow bg-[#fcfaf8] @container p-4"> <div className="w-full gap-1 overflow-hidden bg-[#fcfaf8] @[480px]:gap-2 aspect-[2/3] rounded-lg flex"> <div className="w-full bg-center bg-no-repeat bg-cover aspect-auto rounded-none flex-1" style={{ backgroundImage: 'url("https://lh3.googleusercontent.com/aida-public/AB6AXuBf3rJpjqVNUrJWJevQrSKObvtmdkjxrAtvgfaa8YwUaGyDl9jIqUbZZYmx04jALKHCSzPK1JQVborwRMoM9bdC952izTe8HmkT8kaCph8WRyrAYlWqYStq50i1osqwVdt-XfCAK50zWXpQrO73SYX4l9yyPjCNUKTs6VfKWUYRYPWTXxQGYhGIlR6yt-DOLsGliIpjeuj-X31sCk1uERN9Kxin-7dtyoXWKQyUdAR3D1pJkDGzsgB6Df8adTvgtuEVMuOZT2eHB4w")' }} ></div> </div> </div> <p className="text-[#1c140d] text-base font-normal leading-normal pb-3 pt-1 px-4 text-center">The item has been added to your cart.</p> <div className="flex justify-stretch"> <div className="flex flex-1 gap-3 flex-wrap px-4 py-3 justify-between"> <Link to="/checkout" className="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-[#f4ede7] text-[#1c140d] text-sm font-bold leading-normal tracking-[0.015em]" > <span className="truncate">View Cart</span> </Link> <Link to="/products" className="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-[#f38220] text-[#1c140d] text-sm font-bold leading-normal tracking-[0.015em]" > <span className="truncate">Continue Shopping</span> </Link> </div> </div> </div> </div> ); } // --- Checkout Page Component --- function CheckoutPage({ cartItems, clearCart }) { const navigate = useNavigate(); const [formData, setFormData] = useState({ email: '', name: '', address: '', city: '', state: '', zipCode: '', country: '', cardNumber: '', expirationDate: '', cvv: '' }); const handleInputChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const calculateTotal = () => { const subtotal = cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0); const shipping = 5.00; // Flat shipping rate return { subtotal: subtotal.toFixed(2), shipping: shipping.toFixed(2), total: (subtotal + shipping).toFixed(2) }; }; const totals = calculateTotal(); const handlePlaceOrder = (e) => { e.preventDefault(); // Basic validation if (!formData.email || !formData.name || !formData.cardNumber) { alert('Please fill in all required fields (Email, Name, Card Number)'); return; } alert('Order Placed! (This is a demo)'); console.log('Order Details:', { cartItems, formData }); clearCart(); // Clear cart after successful order navigate('/'); // Redirect to home or order confirmation page }; return ( <div className="px-40 flex flex-1 justify-center py-5"> <div className="layout-content-container flex flex-col w-[512px] max-w-[512px] py-5 max-w-[960px] flex-1"> <div className="flex flex-wrap gap-2 p-4"> <Link className="text-[#9c7049] text-base font-medium leading-normal" to="/add-to-cart-confirmation">Cart</Link> <span className="text-[#9c7049] text-base font-medium leading-normal">/</span> <span className="text-[#1c140d] text-base font-medium leading-normal">Checkout</span> </div> <h1 className="text-[#1c140d] text-[22px] font-bold leading-tight tracking-[-0.015em] px-4 text-left pb-3 pt-5">Checkout</h1> <h3 className="text-[#1c140d] text-lg font-bold leading-tight tracking-[-0.015em] px-4 pb-2 pt-4">Customer Information</h3> <div className="flex max-w-[480px] flex-wrap items-end gap-4 px-4 py-3"> <label className="flex flex-col min-w-40 flex-1"> <p className="text-[#1c140d] text-base font-medium leading-normal pb-2">Email</p> <input placeholder="[email protected]" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border border-[#e8dace] bg-[#fcfaf8] focus:border-[#e8dace] h-14 placeholder:text-[#9c7049] p-[15px] text-base font-normal leading-normal" name="email" value={formData.email} onChange={handleInputChange} /> </label> </div> <div className="flex max-w-[480px] flex-wrap items-end gap-4 px-4 py-3"> <label className="flex flex-col min-w-40 flex-1"> <p className="text-[#1c140d] text-base font-medium leading-normal pb-2">Name</p> <input placeholder="John Doe" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border border-[#e8dace] bg-[#fcfaf8] focus:border-[#e8dace] h-14 placeholder:text-[#9c7049] p-[15px] text-base font-normal leading-normal" name="name" value={formData.name} onChange={handleInputChange} /> </label> </div> <div className="flex max-w-[480px] flex-wrap items-end gap-4 px-4 py-3"> <label className="flex flex-col min-w-40 flex-1"> <p className="text-[#1c140d] text-base font-medium leading-normal pb-2">Address</p> <input placeholder="123 Main St" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border border-[#e8dace] bg-[#fcfaf8] focus:border-[#e8dace] h-14 placeholder:text-[#9c7049] p-[15px] text-base font-normal leading-normal" name="address" value={formData.address} onChange={handleInputChange} /> </label> </div> <div className="flex max-w-[480px] flex-wrap items-end gap-4 px-4 py-3"> <label className="flex flex-col min-w-40 flex-1"> <p className="text-[#1c140d] text-base font-medium leading-normal pb-2">City</p> <input placeholder="Anytown" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border border-[#e8dace] bg-[#fcfaf8] focus:border-[#e8dace] h-14 placeholder:text-[#9c7049] p-[15px] text-base font-normal leading-normal" name="city" value={formData.city} onChange={handleInputChange} /> </label> <label className="flex flex-col min-w-40 flex-1"> <p className="text-[#1c140d] text-base font-medium leading-normal pb-2">State</p> <input placeholder="CA" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border border-[#e8dace] bg-[#fcfaf8] focus:border-[#e8dace] h-14 placeholder:text-[#9c7049] p-[15px] text-base font-normal leading-normal" name="state" value={formData.state} onChange={handleInputChange} /> </label> </div> <div className="flex max-w-[480px] flex-wrap items-end gap-4 px-4 py-3"> <label className="flex flex-col min-w-40 flex-1"> <p className="text-[#1c140d] text-base font-medium leading-normal pb-2">Zip Code</p> <input placeholder="90210" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border border-[#e8dace] bg-[#fcfaf8] focus:border-[#e8dace] h-14 placeholder:text-[#9c7049] p-[15px] text-base font-normal leading-normal" name="zipCode" value={formData.zipCode} onChange={handleInputChange} /> </label> <label className="flex flex-col min-w-40 flex-1"> <p className="text-[#1c140d] text-base font-medium leading-normal pb-2">Country</p> <input placeholder="USA" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border border-[#e8dace] bg-[#fcfaf8] focus:border-[#e8dace] h-14 placeholder:text-[#9c7049] p-[15px] text-base font-normal leading-normal" name="country" value={formData.country} onChange={handleInputChange} /> </label> </div> <h3 className="text-[#1c140d] text-lg font-bold leading-tight tracking-[-0.015em] px-4 pb-2 pt-4">Payment Information</h3> <div className="flex max-w-[480px] flex-wrap items-end gap-4 px-4 py-3"> <label className="flex flex-col min-w-40 flex-1"> <p className="text-[#1c140d] text-base font-medium leading-normal pb-2">Card Number</p> <input placeholder="1234 5678 9012 3456" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border border-[#e8dace] bg-[#fcfaf8] focus:border-[#e8dace] h-14 placeholder:text-[#9c7049] p-[15px] text-base font-normal leading-normal" name="cardNumber" value={formData.cardNumber} onChange={handleInputChange} /> </label> </div> <div className="flex max-w-[480px] flex-wrap items-end gap-4 px-4 py-3"> <label className="flex flex-col min-w-40 flex-1"> <p className="text-[#1c140d] text-base font-medium leading-normal pb-2">Expiration Date</p> <input placeholder="MM/YY" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border border-[#e8dace] bg-[#fcfaf8] focus:border-[#e8dace] h-14 placeholder:text-[#9c7049] p-[15px] text-base font-normal leading-normal" name="expirationDate" value={formData.expirationDate} onChange={handleInputChange} /> </label> <label className="flex flex-col min-w-40 flex-1"> <p className="text-[#1c140d] text-base font-medium leading-normal pb-2">CVV</p> <input placeholder="123" className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1c140d] focus:outline-0 focus:ring-0 border border-[#e8dace] bg-[#fcfaf8] focus:border-[#e8dace] h-14 placeholder:text-[#9c7049] p-[15px] text-base font-normal leading-normal" name="cvv" value={formData.cvv} onChange={handleInputChange} /> </label> </div> <h3 className="text-[#1c140d] text-lg font-bold leading-tight tracking-[-0.015em] px-4 pb-2 pt-4">Order Summary</h3> {cartItems.length === 0 ? ( <p className="text-[#9c7049] text-base font-normal leading-normal px-4 py-2">Your cart is empty.</p> ) : ( cartItems.map(item => ( <div key={`${item.id}-${item.color}-${item.size}`} className="flex items-center gap-4 bg-[#fcfaf8] px-4 min-h-[72px] py-2"> <div className="bg-center bg-no-repeat aspect-square bg-cover rounded-lg size-14" style={{ backgroundImage: `url("${item.imageUrl}")` }} ></div> <div className="flex flex-col justify-center"> <p className="text-[#1c140d] text-base font-medium leading-normal line-clamp-1">{item.name}</p> <p className="text-[#9c7049] text-sm font-normal leading-normal line-clamp-2"> Size {item.size}, Color {item.color} (Qty: {item.quantity}) - ${item.price * item.quantity} </p> </div> </div> )) )} <div className="p-4"> <div className="flex justify-between gap-x-6 py-2"> <p className="text-[#9c7049] text-sm font-normal leading-normal">Subtotal</p> <p className="text-[#1c140d] text-sm font-normal leading-normal text-right">${totals.subtotal}</p> </div> <div className="flex justify-between gap-x-6 py-2"> <p className="text-[#9c7049] text-sm font-normal leading-normal">Shipping</p> <p className="text-[#1c140d] text-sm font-normal leading-normal text-right">${totals.shipping}</p> </div> <div className="flex justify-between gap-x-6 py-2"> <p className="text-[#1c140d] text-base font-bold leading-normal">Total</p> <p className="text-[#1c140d] text-base font-bold leading-normal text-right">${totals.total}</p> </div> </div> <div className="flex px-4 py-3"> <button onClick={handlePlaceOrder} className="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-12 px-5 flex-1 bg-[#f38220] text-[#1c140d] text-base font-bold leading-normal tracking-[0.015em]" > <span className="truncate">Place Order</span> </button> </div> </div> </div> ); } // --- Main App Component --- function App() { const [cartItems, setCartItems] = useState([]); const handleAddToCart = (item) => { setCartItems(prevItems => { const existingItemIndex = prevItems.findIndex( (cartItem) => cartItem.id === item.id && cartItem.color === item.color && cartItem.size === item.size ); if (existingItemIndex > -1) { const updatedItems = [...prevItems]; updatedItems[existingItemIndex].quantity += item.quantity; return updatedItems; } else { return [...prevItems, item]; } }); }; const clearCart = () => { setCartItems([]); }; const totalCartItemsCount = cartItems.reduce((acc, item) => acc + item.quantity, 0); return ( <div className="relative flex size-full min-h-screen flex-col bg-[#fcfaf8] group/design-root overflow-x-hidden" style={{ fontFamily: '"Plus Jakarta Sans", "Noto Sans", sans-serif' }}> <Router> <div className="layout-container flex h-full grow flex-col"> <Header cartItemCount={totalCartItemsCount} /> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/products" element={<ProductListingPage />} /> <Route path="/products/:id" element={<ProductDetailPage handleAddToCart={handleAddToCart} />} /> <Route path="/add-to-cart-confirmation" element={<AddToCartConfirmationPage />} /> <Route path="/checkout" element={<CheckoutPage cartItems={cartItems} clearCart={clearCart} />} /> </Routes> <Footer /> </div> </Router> </div> ); } export default App
Copy and paste the generated code into the App.jsx
file and run the project. Since I used Vite to initialize my React boilerplate, I’ll run npm run dev
.
Here is what the final app build looks like:
For now, the best use cases for Stitch are MVPs, startups, and side projects, as it can help in a faster launch.
You just need to type what you need, and Stitch will turn it into clean UI, plus the HTML and CSS to match. With this, you can start building right away and not be bothered by design bottlenecks.
I’d only recommend Stitch for prototypes for now, as it has not matured enough for building enterprise software.
It’s not all rosy with Stitch, as it tends to produce similar-looking designs. If you need custom designs, then Stitch is not the tool for you.
Stitch doesn’t seem to understand design positioning, and especially center positioning. It sometimes produces misaligned design elements.
When you prompt it to correct the misalignment, it returns the same result, claiming that the issue has been fixed. Getting the best outcome from Stitch requires some level of experience with design tools like Figma, as well as a good knowledge and experience with CSS.
Since Stitch is in beta, we can hope that the tool will get better with time. For now, it is not replacing Figma, but it can be helpful for design inspiration.
There is so much to be said about how AI has changed the way we complete tasks. A tool like Stitch helps cut the product ideation and implementation stage to a minimum.
But there is still room for improvement. Stitch can be improved to produce responsive HTML code for all screen sizes.
In this article, we designed the pages of our fictional startup called Skatee. We also generated mobile versions of our Skatee e-commerce app.
We finally ended by giving the HTML/CSS code we generated from Stitch to Gemini to generate the program logic for us.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowTanStack Start vs. Next.js: both are powerful full-stack React frameworks, but they take fundamentally different approaches to architecture, routing, and developer experience. This guide breaks down their core features from SSR and data fetching to TypeScript support and deployment, to help you choose the right tool for your next React project.
While it may seem like a maintenance update, Angular v20 is packed with practical, production-ready upgrades that will enable us to build apps faster and with more confidence.
Explore how to build and deploy a Next.js app to Cloudflare Workers to enjoy Vercel-like performance with more flexibility and lower costs.
Looking for a Next.js alternative and want to keep using React? Discover the best frameworks to consider and where they shine.