Convert VELOURS to React + Tailwind CSS

Rebuild a static bridal landing page as a React app with Tailwind CSS v4.

Introduction

30 Second Summary

When you build a website with plain HTML and CSS, it works great at first. But as the page grows, maintaining hundreds of lines of styles in a single file becomes painful and repetitive.

In this project, you will rebuild the VELOURS luxury bridal landing page as a React application styled with Tailwind CSS v4. You'll decompose a monolithic HTML page into reusable components and migrate vanilla CSS to utility-first classes.

What You'll Build

A React-powered recreation of the VELOURS luxury bridal website landing page, scaffolded with Vite and styled entirely with Tailwind CSS v4 utility classes.

By the end of this project, you'll have:

  • A Vite + React project with Tailwind CSS v4 configured via the official Vite plugin.
  • 4 React components (AnnouncementBar, Navbar, Hero, ProductCard) decomposed from a single HTML page.
  • Full Tailwind utility-class styling matching the original design's colours, layout, and typography using arbitrary values.
  • Secret Mission: Add a responsive mobile hamburger menu with React state and Tailwind breakpoints.

Are there any prerequisites?

This project assumes basic React knowledge (components, JSX, hooks) and familiarity with HTML/CSS. You do not need prior Tailwind CSS experience.

Scaffold React + Tailwind CSS v4

Before you can rebuild the VELOURS landing page as a component-based application, you need a modern development environment that supports React and Tailwind CSS.

In this step, you will scaffold a new React project using Vite (a fast build tool) and configure Tailwind CSS v4 with its official Vite plugin. By the end, you will have a clean slate ready for building components.

In this step, get ready to:
  • Create a Vite + React project and install dependencies.
  • Configure Tailwind CSS v4 with the Vite plugin.
  • Clean up scaffold files and verify the dev server runs.
Create the Vite + React project

Vite provides a scaffolding tool that generates a ready-to-use React project in seconds. You will use it to create the velours-react folder with all the base files.

  • Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar, then type Terminal (macOS) or PowerShell (Windows) and press Enter.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Scaffold the project and install its base dependencies by running these commands:
npm create vite@latest velours-react -- --template react
cd velours-react
npm install

What do these commands do?

  • npm create vite@latest velours-react -- --template react generates a new React project in a folder called velours-react.
  • cd velours-react moves your terminal into the new project folder.
  • npm install downloads all the packages the scaffold needs to run.
  • Confirm the project was created by running:
ls

You should see files like index.html, package.json, vite.config.js, and a src folder listed in the output.

Not seeing these files?

Make sure you ran cd velours-react after the scaffold command. If you see an error about Node.js version, Vite requires Node.js 20.19+ or 22.12+. Check your version with node --version.

help me troubleshoot Vite scaffold errors

  • Install Tailwind CSS and its Vite plugin by running:
npm install tailwindcss @tailwindcss/vite

Why a Vite plugin?

Tailwind CSS v4 provides an official Vite plugin that integrates directly into Vite's build pipeline. This replaces the PostCSS-based setup from older versions and provides faster builds.

  • Confirm the install succeeded by checking the output for a message like "added X packages".

Install failed?

Check that you are inside the velours-react folder (not the Desktop). Run pwd to see your current location. You should see a path ending in velours-react.

help me fix npm install errors for Tailwind CSS

Configure Tailwind in Vite

Tailwind CSS v4 needs two configuration changes: the Vite plugin registered in your build config, and a single CSS import that loads Tailwind's styles.

  • Open the velours-react folder in VS Code. Press Cmd+Space (macOS) or the Windows key (Windows), type VS Code, and press Enter.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the velours-react folder, and click Open.
  • In the Explorer sidebar on the left, click vite.config.js to open it.
  • Select all the existing content and replace it with this code:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
})

What does this config do?

  • import tailwindcss from '@tailwindcss/vite' brings in the official Tailwind Vite plugin.
  • The plugins array registers both the React plugin (for JSX support) and the Tailwind plugin (for utility class processing).
  • Tailwind v4 does not need a tailwind.config.js file. Content paths are auto-detected.
  • Save vite.config.js.
  • Now open src/index.css from the Explorer sidebar.
  • Select all the existing content and replace it with this single line:
@import "tailwindcss";

What does this import do?

In Tailwind CSS v4, @import "tailwindcss" replaces the old @tailwind base/components/utilities directives from v3. This single line loads all of Tailwind's base styles, component classes, and utility classes.

  • Save src/index.css.

✔️ Awesome, I've got everything!

Great. Make sure both files are saved before moving on.

ⓧ I'd like to double check the full code

Here is what each file should look like:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
})
@import "tailwindcss";
Clean scaffold files and verify

The Vite scaffold includes demo files you do not need. Removing them gives you a blank canvas for building the VELOURS components.

  • In the Explorer sidebar, right-click src/App.css and select Delete.
  • Expand the src/assets folder, right-click react.svg, and select Delete.
  • Open src/App.jsx from the Explorer sidebar.
  • Select all the content inside (Cmd+A on macOS or Ctrl+A on Windows) and delete it so the file is completely empty.
  • Save src/App.jsx.

Why clear App.jsx?

The scaffold's App.jsx imports App.css (which you just deleted) and renders demo content. Clearing it avoids import errors and gives you a fresh file to build your components in.

  • Start the dev server by running this command in the VS Code terminal (open it with Ctrl+` or View > Terminal):
npm run dev
  • Open your browser and go to http://localhost:5173.

You should see a blank white page. Open the browser console (Cmd+Option+J on macOS or Ctrl+Shift+J on Windows) and confirm there are no red error messages.

Seeing errors in the console?

If you see an error about App.css not found, make sure src/App.jsx is completely empty and saved. The old content imported App.css, which no longer exists.

If the dev server did not start, check that you are in the velours-react folder. Run pwd in the terminal to confirm.

help me fix my Vite dev server errors

Your development environment is ready. Next up, you will build the first two visual components: the AnnouncementBar and the Navbar.

Build the AnnouncementBar and Navbar Components

Your React project is scaffolded and Tailwind CSS is configured. Right now the page is blank because App.jsx is empty.

Every luxury website starts with two things visitors see immediately: a promotional banner and a navigation bar. In this step, you will build both as separate React components and wire them into your app.

In this step, get ready to:
  • Create the AnnouncementBar component with Tailwind utility classes.
  • Create the Navbar component with a 3-column CSS Grid layout.
  • Render both components in App.jsx and verify the layout in the browser.
Create the AnnouncementBar component

The announcement bar is the thin black strip at the very top of the VELOURS page. It displays a single line of promotional text in white uppercase letters.

  • In VS Code, right-click the src folder in the Explorer sidebar.
  • Click New Folder and name it components.
  • Right-click the new components folder and click New File.
  • Name the file AnnouncementBar.jsx.
  • Paste the following code into AnnouncementBar.jsx:
function AnnouncementBar() {
  return (
    <div className="bg-[#111] text-white text-center py-3 text-xs tracking-[2px] uppercase">
      Complimentary alterations on all bridal gowns — By appointment only
    </div>
  )
}

export default AnnouncementBar

What does this code do?

This component returns a single <div> styled entirely with Tailwind utility classes. Each class maps to one CSS property:

  • bg-[#111] sets the background to near-black using Tailwind's arbitrary value syntax (square brackets).
  • tracking-[2px] adds 2px of letter-spacing, matching the original VELOURS design.
  • uppercase transforms the text to all capitals without changing the source text.

What are arbitrary values in Tailwind?

When a design uses custom colours or spacing that are not in Tailwind's default scale, you wrap the exact value in square brackets. For example, bg-[#111] generates background-color: #111.

This lets you match any design precisely without needing a configuration file.

  • Save AnnouncementBar.jsx.
  • You will not see a change in the browser yet. The component needs to be imported into App.jsx first, which you will do after building the Navbar.
Create the Navbar component

The VELOURS navbar uses a CSS Grid 3-column layout. Navigation links sit on the left, the brand logo is centered, and a "Shop Now" button aligns to the right.

  • Right-click the components folder and click New File.
  • Name the file Navbar.jsx.
  • Paste the following code into Navbar.jsx:
function Navbar() {
  return (
    <nav className="h-20 grid grid-cols-[1fr_auto_1fr] items-center px-[5%] bg-[#f8f5f0] border-b border-[#e5e5e5]">
      {/* Left column: navigation links */}
      <div className="flex gap-8">
        <a href="#" className="text-[#555] uppercase text-xs tracking-[2px] hover:text-black transition-colors">
          Home
        </a>
        <a href="#" className="text-[#555] uppercase text-xs tracking-[2px] hover:text-black transition-colors">
          Shop
        </a>
        <a href="#" className="text-[#555] uppercase text-xs tracking-[2px] hover:text-black transition-colors">
          About
        </a>
        <a href="#" className="text-[#555] uppercase text-xs tracking-[2px] hover:text-black transition-colors">
          Blog
        </a>
      </div>

      {/* Center column: brand logo */}
      <div className="text-center">
        <a href="#" className="text-[#222] text-3xl tracking-[10px] font-light">
          VELOURS
        </a>
      </div>

      {/* Right column: action button */}
      <div className="flex justify-end items-center gap-4">
        <a
          href="#"
          className="text-[#222] border border-[#222] px-6 py-3 text-xs uppercase tracking-[2px] hover:bg-[#222] hover:text-white transition-colors"
        >
          Shop Now
        </a>
      </div>
    </nav>
  )
}

export default Navbar

What does this code do?

The <nav> element uses CSS Grid to divide the bar into three columns:

  • grid-cols-[1fr_auto_1fr] gives the left and right columns equal flexible width, while the center column sizes to its content. This keeps the logo perfectly centered regardless of how wide the nav links or button are.
  • h-20 sets the navbar height to 80px (Tailwind's spacing scale: 20 * 4px = 80px).
  • hover:bg-[#222] hover:text-white on the Shop Now button fills the button background on hover, creating an interactive transition.
  • Save Navbar.jsx.
  • You will not see a change in the browser yet. The next substep wires both components into your app.
Wire components into App and verify

Both components exist but nothing renders them yet. You need to import them into App.jsx and return them as JSX.

  • Open src/App.jsx in the Explorer sidebar.
  • Replace the empty file contents with the following code:
import AnnouncementBar from './components/AnnouncementBar'
import Navbar from './components/Navbar'

function App() {
  return (
    <>
      <AnnouncementBar />
      <Navbar />
    </>
  )
}

export default App

What does this code do?

The two import statements pull in your components from the components folder. The <>...</> wrapper is a React Fragment. It lets you return multiple sibling elements without adding an extra <div> to the DOM.

  • Save App.jsx.
  • Switch to your browser at http://localhost:5173. You should see a thin black bar with white uppercase text at the top, and below it a beige navbar with navigation links on the left, the VELOURS logo centered, and a Shop Now button on the right.

Page still blank or showing an error?

  • Check your browser console for import errors. Make sure the file paths in App.jsx match the exact file names: ./components/AnnouncementBar and ./components/Navbar.
  • Confirm both component files have export default at the bottom. Without the export, the import in App.jsx cannot find the component.
  • If styles are missing but components render, confirm that src/index.css still contains @import "tailwindcss"; and nothing else.

Why aren't my AnnouncementBar and Navbar components showing up in the browser?

✔️ Awesome, I've got everything!

Great. Double check you have saved all three files before moving on.

ⓧ I'd like to double check the full code

Here is the complete code for each file at this point. Compare yours against these:

function AnnouncementBar() {
  return (
    <div className="bg-[#111] text-white text-center py-3 text-xs tracking-[2px] uppercase">
      Complimentary alterations on all bridal gowns — By appointment only
    </div>
  )
}

export default AnnouncementBar
function Navbar() {
  return (
    <nav className="h-20 grid grid-cols-[1fr_auto_1fr] items-center px-[5%] bg-[#f8f5f0] border-b border-[#e5e5e5]">
      <div className="flex gap-8">
        <a href="#" className="text-[#555] uppercase text-xs tracking-[2px] hover:text-black transition-colors">
          Home
        </a>
        <a href="#" className="text-[#555] uppercase text-xs tracking-[2px] hover:text-black transition-colors">
          Shop
        </a>
        <a href="#" className="text-[#555] uppercase text-xs tracking-[2px] hover:text-black transition-colors">
          About
        </a>
        <a href="#" className="text-[#555] uppercase text-xs tracking-[2px] hover:text-black transition-colors">
          Blog
        </a>
      </div>

      <div className="text-center">
        <a href="#" className="text-[#222] text-3xl tracking-[10px] font-light">
          VELOURS
        </a>
      </div>

      <div className="flex justify-end items-center gap-4">
        <a
          href="#"
          className="text-[#222] border border-[#222] px-6 py-3 text-xs uppercase tracking-[2px] hover:bg-[#222] hover:text-white transition-colors"
        >
          Shop Now
        </a>
      </div>
    </nav>
  )
}

export default Navbar
import AnnouncementBar from './components/AnnouncementBar'
import Navbar from './components/Navbar'

function App() {
  return (
    <>
      <AnnouncementBar />
      <Navbar />
    </>
  )
}

export default App

Your page now has a professional header with two distinct components. Next up, you will build the full-viewport hero section with a gradient overlay and positioned text content.

Build the Hero Section

Your announcement bar and navbar are in place. The page has a solid header structure that frames the rest of the content.

Now you need the centrepiece of any landing page: the hero section. This is a full-viewport area with a warm background, a dark gradient overlay that makes text readable, and positioned content including a headline, description, and call-to-action buttons.

In this step, get ready to:
  • Create the Hero component with a full-viewport section and gradient overlay.
  • Add styled text content including a subtitle, heading, and description.
  • Add two call-to-action buttons that match the original design.
Create the Hero component structure

The hero section spans the entire viewport height and uses absolute positioning to layer a gradient overlay on top of the background colour. A content container sits at the bottom-left of the section, ready to hold the text and buttons.

  • Create a new file src/components/Hero.jsx and paste the following code:
function Hero() {
  return (
    // Full-viewport section with warm beige background
    <section className="relative h-screen overflow-hidden bg-[#ddd5c5]">
      {/* Dark gradient overlay fading from left to right */}
      <div className="absolute inset-0 bg-gradient-to-r from-[rgba(24,21,15,0.55)] via-[rgba(24,21,15,0.2)] to-transparent" />

      {/* Content container positioned at bottom-left */}
      <div className="absolute bottom-20 left-0 px-20 pb-20 max-w-[608px]">
      </div>
    </section>
  )
}

export default Hero

What does this code do?

The relative class on the section makes it the positioning parent for any absolute children inside it.

The gradient overlay div covers the entire section (inset-0) and fades from a dark semi-transparent colour on the left to fully transparent on the right. This makes white text readable against the light background.

The content container uses max-w-[608px] to constrain the text width, keeping lines readable.

Before you can see the Hero in your browser, you need to wire it into your App component.

  • In src/App.jsx, find this line at the top of the file:
import Navbar from './components/Navbar'
  • Add a new import line directly below it:
import Hero from './components/Hero'
  • Then find <Navbar /> inside the return statement and add <Hero /> directly below it:
      <Navbar />
      <Hero />
  • Save both Hero.jsx and App.jsx.
  • Check your browser. You should see a large warm beige area filling the rest of the viewport below the navbar, with a subtle dark gradient fading in from the left edge.

Don't see the beige section?

  • Make sure you saved both files. The dev server hot-reloads, but only for saved changes.
  • Check that the import path in App.jsx is exactly './components/Hero' with a capital H.
  • Confirm the section uses h-screen. Without it the section collapses to zero height because it has no visible content yet.
  • Still stuck? Help me debug why my Hero component isn't rendering in the browser.
Add the hero text content

The hero needs three text elements stacked vertically inside the content container: a small uppercase subtitle, a large headline, and a supporting description paragraph.

  • In Hero.jsx, find the empty content container div (the one with max-w-[608px]). Paste the following code inside it:
        {/* Small uppercase subtitle */}
        <p className="text-white/70 text-[11px] tracking-[3px] uppercase">
          2025 Bridal Collection
        </p>

        {/* Main headline */}
        <h1 className="text-white text-8xl leading-none font-light mt-5 max-w-[448px]">
          Dressed for your finest day
        </h1>

        {/* Supporting description */}
        <p className="text-white/60 text-sm leading-7 tracking-[0.35px] mt-8 max-w-[448px]">
          Couture bridal gowns, tuxedos, and formal wear — made to order, finished by hand.
        </p>

What does this code do?

  • text-white/70 and text-white/60 use Tailwind's opacity modifier to create semi-transparent white text. The subtitle is 70% opaque and the description is 60%.
  • text-8xl with leading-none creates a massive headline with tight line-height, matching the original design's 96px heading.
  • tracking-[3px] and tracking-[0.35px] use Tailwind's arbitrary value syntax (square brackets) to set exact letter-spacing values that match the original CSS.
  • Save Hero.jsx.
  • Check your browser. You should see white text in the lower-left area of the hero: a small "2025 Bridal Collection" subtitle, the large "Dressed for your finest day" heading, and a description paragraph below it.

Text not showing up?

  • Make sure you pasted the text elements inside the content container div (the one with absolute bottom-20 left-0 classes), not outside it.
  • If the text appears but looks invisible, check that the gradient overlay div is self-closing (/> at the end). If it wraps the content container, everything inside it gets the gradient applied.
  • Need a hand? Help me figure out why the hero text is not visible.
Add the CTA buttons

The hero needs two call-to-action buttons below the description. One has a solid white background and the other uses a transparent style with a white border.

  • In Hero.jsx, find the closing </p> tag of the description paragraph (the one about "Couture bridal gowns"). Add the following code directly below it:
        {/* Call-to-action buttons */}
        <div className="flex gap-4 mt-10">
          <a
            href="#"
            className="bg-white text-[#18150f] px-9 py-4 text-[10px] tracking-[2.2px] uppercase font-medium inline-flex items-center gap-3"
          >
            Explore Collection
          </a>
          <a
            href="#"
            className="border border-white/50 text-white px-9 py-4 text-[10px] tracking-[2.2px] uppercase font-medium inline-flex items-center gap-3"
          >
            Our Atelier
          </a>
        </div>

What does this code do?

  • The "Explore Collection" button uses bg-white text-[#18150f] for a solid white background with dark text. This is the primary CTA that draws the eye first.
  • The "Our Atelier" button uses border border-white/50 for a subtle outline style. The 50% opacity border keeps it visually secondary to the solid button.
  • Both buttons share the same sizing (px-9 py-4) and typography (text-[10px] tracking-[2.2px] uppercase) to maintain visual consistency.
  • Save Hero.jsx.
  • Check your browser. You should see two buttons side by side below the description text: a white "Explore Collection" button and a bordered "Our Atelier" button.

Buttons not appearing?

  • Make sure the button div is inside the content container (the max-w-[608px] div), not outside it or after the closing </section> tag.
  • Check for any missing closing tags. Each <a> element needs a matching </a>, and the wrapper div needs its own </div>.
  • Still stuck? Help me debug why my CTA buttons are not rendering in the Hero component.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file.

ⓧ I'd like to double check the full code

Here is the complete Hero.jsx file:

function Hero() {
  return (
    <section className="relative h-screen overflow-hidden bg-[#ddd5c5]">
      <div className="absolute inset-0 bg-gradient-to-r from-[rgba(24,21,15,0.55)] via-[rgba(24,21,15,0.2)] to-transparent" />

      <div className="absolute bottom-20 left-0 px-20 pb-20 max-w-[608px]">
        <p className="text-white/70 text-[11px] tracking-[3px] uppercase">
          2025 Bridal Collection
        </p>

        <h1 className="text-white text-8xl leading-none font-light mt-5 max-w-[448px]">
          Dressed for your finest day
        </h1>

        <p className="text-white/60 text-sm leading-7 tracking-[0.35px] mt-8 max-w-[448px]">
          Couture bridal gowns, tuxedos, and formal wear — made to order, finished by hand.
        </p>

        <div className="flex gap-4 mt-10">
          <a
            href="#"
            className="bg-white text-[#18150f] px-9 py-4 text-[10px] tracking-[2.2px] uppercase font-medium inline-flex items-center gap-3"
          >
            Explore Collection
          </a>
          <a
            href="#"
            className="border border-white/50 text-white px-9 py-4 text-[10px] tracking-[2.2px] uppercase font-medium inline-flex items-center gap-3"
          >
            Our Atelier
          </a>
        </div>
      </div>
    </section>
  )
}

export default Hero

And here is your updated App.jsx:

import AnnouncementBar from './components/AnnouncementBar'
import Navbar from './components/Navbar'
import Hero from './components/Hero'

function App() {
  return (
    <>
      <AnnouncementBar />
      <Navbar />
      <Hero />
    </>
  )
}

export default App

Your hero section fills the viewport with the VELOURS branding front and centre. Next up, you will add the ProductCard and ScrollIndicator to complete the landing page layout.

Add the ProductCard and ScrollIndicator

Your hero section fills the viewport with a beautiful gradient and headline text. Two small details remain to complete the original VELOURS design: a product card anchored to the bottom-right corner, and a scroll indicator centered at the bottom.

Both of these are standalone components that get positioned absolutely inside the hero. You will build each one separately, then wire them into Hero.jsx to finish the page.

In this step, get ready to:
  • Build a ProductCard component that displays gown details.
  • Build a ScrollIndicator component with an SVG chevron arrow.
  • Position both components inside the Hero to complete the landing page.
Create the ProductCard component

The product card is a small overlay in the bottom-right of the hero that highlights a featured gown. It contains a label, product name, and price.

  • Create a new file at src/components/ProductCard.jsx and add the following code:
function ProductCard() {
  return (
    <div className="bg-[#f8f4ee]/90 px-6 py-4">
      {/* Category label */}
      <p className="text-[#6e6355] text-[9px] tracking-[2px] uppercase">
        As seen here
      </p>
      {/* Product name */}
      <p className="text-[#18150f] text-sm tracking-[0.5px] mt-1 font-light">
        The Elara Gown
      </p>
      {/* Price */}
      <p className="text-[#6e6355] text-xs mt-0.5">
        From $2,890
      </p>
    </div>
  )
}

export default ProductCard

What does this code do?

  • bg-[#f8f4ee]/90 gives the card a warm off-white background at 90% opacity, letting the hero show through slightly.
  • The three <p> elements stack vertically: a tiny uppercase label, a light-weight product name, and a muted price.
  • tracking-[2px] and tracking-[0.5px] are arbitrary values that match the original CSS letter-spacing exactly.
  • Save src/components/ProductCard.jsx.

You will not see the card in the browser yet because it has not been imported into the Hero. The next substep builds the second component first, then you will wire both in together.

Seeing a syntax error in your editor?

Make sure the file extension is .jsx (not .js). Vite uses the extension to determine whether to process JSX syntax.

Check that every opening tag has a matching closing tag and that the className strings use straight double quotes.

Help me fix a syntax error in my ProductCard component.

Create the ScrollIndicator component

The scroll indicator is a subtle prompt at the bottom-center of the hero. It uses the word "Scroll" above a small SVG chevron arrow pointing down.

  • Create a new file at src/components/ScrollIndicator.jsx and add the following code:
function ScrollIndicator() {
  return (
    <div className="flex flex-col items-center gap-2">
      {/* "Scroll" label */}
      <span className="text-white/50 text-[9px] tracking-[1.8px] uppercase font-medium">
        Scroll
      </span>
      {/* Chevron-down SVG arrow */}
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="14"
        height="14"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
        className="text-white/50"
      >
        <polyline points="6 9 12 15 18 9" />
      </svg>
    </div>
  )
}

export default ScrollIndicator

What does this code do?

  • The outer div stacks the text and arrow vertically using flex flex-col with a small gap between them.
  • text-white/50 sets white text at 50% opacity, making it subtle against the hero background.
  • The <svg> element draws a small downward-pointing chevron. In JSX, SVG attributes like stroke-width become camelCase: strokeWidth.
  • Save src/components/ScrollIndicator.jsx.

Like the ProductCard, this component will not appear in the browser until you wire it into the Hero in the next substep.

Seeing an error about strokeWidth?

JSX requires camelCase for SVG attributes. Make sure you wrote strokeWidth, strokeLinecap, and strokeLinejoin instead of the hyphenated HTML versions.

Help me fix SVG attributes in my ScrollIndicator component.

Wire both components into the Hero

Both components are built. Now you will import them into the Hero and position each one absolutely within the hero section.

  • Open src/components/Hero.jsx.
  • Add these two import lines at the very top of the file, above the function Hero() declaration:
import ProductCard from './ProductCard'
import ScrollIndicator from './ScrollIndicator'
  • Find the closing </section> tag at the bottom of the component. Add these two positioned containers just above it:
      {/* Product card positioned bottom-right */}
      <div className="absolute bottom-20 right-12">
        <ProductCard />
      </div>

      {/* Scroll indicator centered at the bottom */}
      <div className="absolute bottom-8 left-1/2 -translate-x-1/2">
        <ScrollIndicator />
      </div>

What does this positioning do?

  • absolute bottom-20 right-12 pins the ProductCard to the bottom-right corner of the hero, offset from the edges.
  • absolute bottom-8 left-1/2 -translate-x-1/2 centers the ScrollIndicator horizontally at the bottom. The left-1/2 moves it 50% from the left, then -translate-x-1/2 pulls it back by half its own width for true centering.
  • Both work because the parent <section> has relative set, which creates the positioning context for absolutely-placed children.
  • Save Hero.jsx and check your browser.
  • You should see a small cream-coloured product card in the bottom-right corner showing "The Elara Gown" and "From $2,890". At the bottom center, the word "Scroll" with a tiny chevron arrow should appear in faint white.

Don't see the ProductCard or ScrollIndicator?

Check that your import paths match the filenames exactly. The imports should be './ProductCard' and './ScrollIndicator' (relative to the components folder, no file extension needed).

Confirm that both new files export their component with export default at the bottom. A missing export causes a blank render with no error.

Help me debug why my ProductCard or ScrollIndicator isn't showing up in the Hero.

✔️ Awesome, I've got everything!

Great. Double check you have saved all three files: ProductCard.jsx, ScrollIndicator.jsx, and Hero.jsx.

ⓧ I'd like to double check the full code

Here is the complete code for all three files.

function ProductCard() {
  return (
    <div className="bg-[#f8f4ee]/90 px-6 py-4">
      <p className="text-[#6e6355] text-[9px] tracking-[2px] uppercase">
        As seen here
      </p>
      <p className="text-[#18150f] text-sm tracking-[0.5px] mt-1 font-light">
        The Elara Gown
      </p>
      <p className="text-[#6e6355] text-xs mt-0.5">
        From $2,890
      </p>
    </div>
  )
}

export default ProductCard
function ScrollIndicator() {
  return (
    <div className="flex flex-col items-center gap-2">
      <span className="text-white/50 text-[9px] tracking-[1.8px] uppercase font-medium">
        Scroll
      </span>
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="14"
        height="14"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
        className="text-white/50"
      >
        <polyline points="6 9 12 15 18 9" />
      </svg>
    </div>
  )
}

export default ScrollIndicator
import ProductCard from './ProductCard'
import ScrollIndicator from './ScrollIndicator'

function Hero() {
  return (
    <section className="relative h-screen overflow-hidden bg-[#ddd5c5]">
      <div className="absolute inset-0 bg-gradient-to-r from-[rgba(24,21,15,0.55)] via-[rgba(24,21,15,0.2)] to-transparent" />

      <div className="absolute bottom-20 left-0 px-20 pb-20 max-w-[608px]">
        <p className="text-white/70 text-[11px] tracking-[3px] uppercase">
          2025 Bridal Collection
        </p>

        <h1 className="text-white text-8xl leading-none font-light mt-5 max-w-[448px]">
          Dressed for your finest day
        </h1>

        <p className="text-white/60 text-sm leading-7 tracking-[0.35px] mt-8 max-w-[448px]">
          Couture bridal gowns, tuxedos, and formal wear — made to order, finished by hand.
        </p>

        <div className="flex gap-4 mt-10">
          <a
            href="#"
            className="bg-white text-[#18150f] px-9 py-4 text-[10px] tracking-[2.2px] uppercase font-medium inline-flex items-center gap-3"
          >
            Explore Collection
          </a>
          <a
            href="#"
            className="border border-white/50 text-white px-9 py-4 text-[10px] tracking-[2.2px] uppercase font-medium inline-flex items-center gap-3"
          >
            Our Atelier
          </a>
        </div>
      </div>

      <div className="absolute bottom-20 right-12">
        <ProductCard />
      </div>

      <div className="absolute bottom-8 left-1/2 -translate-x-1/2">
        <ScrollIndicator />
      </div>
    </section>
  )
}

export default Hero

Secret mission

Add Responsive Mobile Navigation

Your VELOURS page looks great on desktop, but what happens when someone visits on their phone? The navigation links disappear into a cramped row. In this mission, you will add a hamburger menu that toggles a mobile navigation panel using React state and Tailwind responsive breakpoints.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

This project runs entirely locally, so there are no ongoing costs.

Resources you used:

  • The velours-react project folder on your Desktop (contains all source files and node_modules).
  • The Vite dev server (a local process running in your terminal).

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with your VELOURS React project.

  • Your dev server keeps running at http://localhost:5173 until you close the terminal.
  • Your project folder stays on your Desktop, ready to open in VS Code anytime.

Pause - I'll come back to this later

Shut down the running dev server to free up your terminal. Your files stay on disk so you can pick up where you left off.

  • Switch to the terminal where the Vite dev server is running.
  • Press Ctrl+C to stop the server.
  • To resume later, open the velours-react folder in VS Code and run npm run dev in the terminal.

Delete - I don't want to use this again

Remove the entire project folder and all installed dependencies. This cannot be undone.

  • If the Vite dev server is still running, press Ctrl+C in its terminal to stop it.
  • Delete the project folder by running this command (macOS/Linux):
rm -rf ~/Desktop/velours-react

On Windows, run this instead:

rmdir /s /q %USERPROFILE%\Desktop\velours-react
  • Confirm the folder is gone by checking your Desktop. You should no longer see the velours-react folder.

Nice Work!

Nice Work!

You just converted a static HTML/CSS luxury bridal landing page into a fully modular React application styled with Tailwind CSS v4.

You've learned how to:

  • Scaffold a Vite + React project with Tailwind CSS v4 configured via the official @tailwindcss/vite plugin.
  • Decompose a monolithic HTML page into reusable React components including AnnouncementBar, Navbar, Hero, ProductCard, and ScrollIndicator.
  • Migrate vanilla CSS to Tailwind utility classes, including arbitrary values for custom colours, CSS Grid layouts, gradient overlays, and absolute positioning.
  • Secret Mission: Add responsive mobile navigation with React state and Tailwind breakpoints.

Ready to quiz yourself?