Building a Cinema Seat Booking App – More Than Just Clicks and Colors!

If you think UI is “just some buttons and colors,” I invite you to try building a Cinema Seat Booking App.
Yes, this one's for you, dear backend friends, who say “How hard can the frontend be?” 😄

This project isn’t just a pixel-perfect copy of a movie theater—it's a hands-on challenge to master:

  • Dynamic rendering from a matrix

  • Complex state management

  • Local data persistence

  • And a UI that feels real

I built this app to sharpen my frontend skills—not for interviews, but to truly understand how a user interacts with a system like BookMyShow. It turned out to be a fun (and humbling) exercise in building something simple on the surface, but nuanced under the hood.

Let’s explore what I built, how it works, and how you can level up your UI game too! 🍿

𝗜𝘁'𝘀 𝗮 𝗵𝗮𝗻𝗱𝘀-𝗼𝗻 𝗰𝗵𝗮𝗹𝗹𝗲𝗻𝗴𝗲 𝘁𝗼 𝗺𝗮𝘀𝘁𝗲𝗿:
Dynamic rendering from a matrix
Complex state management
Local data persistence
And a UI that feels real

I built this app to sharpen my frontend skills, but to truly understand how a user interacts with a system like BookMyShow. It turned out to be a fun (and humbling) exercise in building something simple on the surface, but nuanced under the hood.

Let’s explore what I built, how it works, and how you can level up your UI game too!

𝗖𝗼𝗿𝗲 𝗟𝗮𝘆𝗼𝘂𝘁
The app supports 7 rows labeled A to G
Each row contains 18 positions, with:
10 actual seats (5 on each side)
𝘞𝘦 𝘤𝘢𝘯 𝘮𝘢𝘬𝘦 𝘪𝘯𝘪𝘵𝘪𝘢𝘭 𝘭𝘢𝘺𝘰𝘶𝘵 𝘢𝘴 𝘥𝘺𝘯𝘢𝘮𝘪𝘤 𝘨𝘰𝘪𝘯𝘨 𝘧𝘰𝘳𝘸𝘢𝘳𝘥

A walkway in the middle to replicate a real-world theatre layout

𝗞𝗲𝘆 𝗙𝗲𝗮𝘁𝘂𝗿𝗲𝘀
Seat Management
𝗔𝘃𝗮𝗶𝗹𝗮𝗯𝗹𝗲 𝗦𝗲𝗮𝘁𝘀 (𝗔) – Selectable by clicking
𝗦𝗲𝗹𝗲𝗰𝘁𝗲𝗱 𝗦𝗲𝗮𝘁𝘀 (𝗦) – Can be toggled on/off
𝗕𝗼𝗼𝗸𝗲𝗱 𝗦𝗲𝗮𝘁𝘀 (𝗕) – Grayed out and locked
𝗪𝗮𝗹𝗸𝘄𝗮𝘆𝘀 (𝗪) – Non-interactive zones for realism

𝗨𝘀𝗲𝗿 𝗜𝗻𝘁𝗲𝗿𝗳𝗮𝗰𝗲
Row Labels (A–G) for quick identification
Seat Numbers visible within each row
𝗖𝗼𝗹𝗼𝗿-𝗰𝗼𝗱𝗲𝗱 𝗦𝘁𝗮𝘁𝗲𝘀 𝗳𝗼𝗿 𝗰𝗹𝗮𝗿𝗶𝘁𝘆:
🟩 Green = Available
🟩✅ Green-filled = Selected
⚫ Gray = Booked

𝗖𝗼𝗻𝘁𝗿𝗼𝗹 𝗕𝘂𝘁𝘁𝗼𝗻𝘀:
🔒 Book – Locks selected seats
🔄 Clear – Resets current selection

💾 𝗗𝗮𝘁𝗮 𝗣𝗲𝗿𝘀𝗶𝘀𝘁𝗲𝗻𝗰𝗲
State is stored in localStorage
Booked seats persist even after a page refresh

🚀 𝗙𝘂𝘁𝘂𝗿𝗲 𝗘𝗻𝗵𝗮𝗻𝗰𝗲𝗺𝗲𝗻𝘁𝘀
While the current version is functional and intuitive, here are some exciting ideas to take it further:

🌐 𝗕𝗮𝗰𝗸𝗲𝗻𝗱 𝗜𝗻𝘁𝗲𝗴𝗿𝗮𝘁𝗶𝗼𝗻
Connect with APIs or databases to store seat data persistently across users
Allow real-time seat availability updates for multi-user scenarios

🔐 𝗨𝘀𝗲𝗿 𝗔𝘂𝘁𝗵𝗲𝗻𝘁𝗶𝗰𝗮𝘁𝗶𝗼𝗻
Enable login-based booking history
Show user-specific bookings and cancellation options

🗓 𝗦𝗵𝗼𝘄 & 𝗦𝗰𝗿𝗲𝗲𝗻 𝗦𝗲𝗹𝗲𝗰𝘁𝗶𝗼𝗻
Add multiple screens and showtimes
Allow users to pick a movie and time before selecting seats

🧠 𝗦𝗺𝗮𝗿𝘁 𝗦𝘂𝗴𝗴𝗲𝘀𝘁𝗶𝗼𝗻𝘀
Auto-suggest best available seats based on group size
Highlight popular seats or aisle preferences

🛠️ 𝗧𝗲𝗰𝗵 𝗦𝘁𝗮𝗰𝗸
Built using React.js with basic CSS for styling.
State management is handled using useState and persisted using localStorage.

🎯 𝗪𝗵𝘆 𝗜 𝗕𝘂𝗶𝗹𝘁 𝗧𝗵𝗶𝘀
I wanted to create something both visually engaging and functionally robust to mimic the complexity of a real-world booking interface — while ensuring a great user experience through visual cues and persistence.

Code

import { useState } from 'react'
import './App.css'

const cinemaLayout = Array(7).fill().map(() => [
  'W', 'W',
  ...Array(5).fill("A"),
  'W', 'W',
  ...Array(5).fill("A"),
])

function App() {
  const [matrix, setMatrix] = useState(JSON.parse(localStorage.getItem("matrix")) || cinemaLayout);

    const handleSeatClick = (rowIdx, colIdx) => {
      const newMatrix = matrix.map((row, rIdx) =>
        row.map((seat, cIdx) => {
          if (rIdx === rowIdx && cIdx === colIdx) {
            if (seat === "A") return "S";
            if (seat === "S") return "A";
          }
          return seat;
        })
      );
      setMatrix(newMatrix);
    };

    const clearSelection = () => {
      const newMatrix = matrix.map(row => row.map(seat => seat === "S" ? "A" : seat));
      setMatrix(newMatrix);
    };

    const bookSeats = () => {
      const newMatrix = matrix.map(row => row.map(seat => seat === "S" ? "B" : seat));
      localStorage.setItem("matrix", JSON.stringify(newMatrix));
      setMatrix(newMatrix);
    };    

  return (
    <div style={{display: "flex", flexDirection: "column", alignItems: "center"}}>
    <h2>Book Tickets</h2>
    <div className="seat-container">
    {matrix.map((row, rowIdx) => (
      <div key={rowIdx} className="seat-row">
        <div className="row-label">{String.fromCharCode(65 + rowIdx)}</div>
        {row.map((seat, colIdx) => (
          <div key={colIdx} 
          className={`seat ${seat === "W" ? "walkway" : seat === "A" ? 
            "available" : seat === "S" ? "selected" : "booked"}`}
            onClick={() =>
              seat !== "W" && seat !== "B" ? handleSeatClick(rowIdx, colIdx) : null
            }
          >
             {seat === "A" || seat === "S" || seat === "B" ? `${row.filter((s, idx) => idx < colIdx && (s === "A" || s === "S" || s === "B")).length + 1}` : ""}  
          </div>    
        ))}
      </div>
    ))}
    </div>

    <div className="controls">
        <button onClick={bookSeats}>Book</button>
        <button onClick={clearSelection}>Clear</button>
      </div>    
  </div>
  )
}

export default App

Thanks for reading

0
Subscribe to my newsletter

Read articles from krishna chaitanya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

krishna chaitanya
krishna chaitanya