Live Weather Widget Simulator

Real-time simulated weather station with location search, temperature conversion (Β°C/Β°F), humidity, wind speeds, and a 5-day forecast.

Difficulty: Beginner Est. Time: 30 mins Vanilla JS (ES6+) Includes HTML, CSS, JS & README (.ZIP)
Live UI Runner

Live Weather Widget Simulator

live-sandbox://weather-app

1. Project Overview & Features

The Live Weather Widget Simulator is an essential frontend project designed to help you bridge the gap between learning JavaScript syntax and building interactive, responsive web applications.

Key Features Implemented:

  • City search with presets (London, New York, Tokyo, Paris, Sydney)
  • Dynamic weather cards (Sunny, Rainy, Snowy, Cloudy, Thunderstorm)
  • Instant Celsius to Fahrenheit temperature toggle
  • Atmospheric metrics: Wind speed, Humidity, UV Index, Air Quality
  • 5-day scrollable forecast strip with animated icons

2. Step-by-Step Code Walkthrough

A Step 1: Semantic HTML Structure

We structure the interface using clean, semantic HTML elements. This ensures maximum accessibility and provides intuitive hooks for CSS classes and JavaScript query selectors.

HTML (index.html)
<div class="weather-card">
  <div class="search-box">
    <input type="text" id="city-input" placeholder="Search city (e.g., Tokyo, London, NYC)...">
    <button id="search-btn"><i class="fas fa-search"></i></button>
  </div>

  <div class="preset-chips">
    <button class="chip active" data-city="New York">New York</button>
    <button class="chip" data-city="London">London</button>
    <button class="chip" data-city="Tokyo">Tokyo</button>
    <button class="chip" data-city="Paris">Paris</button>
  </div>

  <div class="main-weather">
    <div class="weather-icon-wrapper" id="weather-icon">
      <i class="fas fa-sun"></i>
    </div>
    <div class="temp-wrapper">
      <span class="temp-val" id="temp-val">24</span>
      <span class="unit-toggle" id="unit-btn">Β°C</span>
    </div>
    <h2 id="city-name" class="city-name">New York, US</h2>
    <p id="weather-desc" class="weather-desc">Sunny and Clear Sky</p>
  </div>

  <div class="metrics-grid">
    <div class="metric-item">
      <i class="fas fa-wind"></i>
      <div>
        <span class="m-val" id="wind-speed">14 km/h</span>
        <span class="m-label">Wind Speed</span>
      </div>
    </div>
    <div class="metric-item">
      <i class="fas fa-tint"></i>
      <div>
        <span class="m-val" id="humidity-val">58%</span>
        <span class="m-label">Humidity</span>
      </div>
    </div>
    <div class="metric-item">
      <i class="fas fa-sun"></i>
      <div>
        <span class="m-val" id="uv-val">6 (Mod)</span>
        <span class="m-label">UV Index</span>
      </div>
    </div>
    <div class="metric-item">
      <i class="fas fa-eye"></i>
      <div>
        <span class="m-val" id="vis-val">10 km</span>
        <span class="m-label">Visibility</span>
      </div>
    </div>
  </div>

  <div class="forecast-section">
    <h3>5-Day Forecast</h3>
    <div class="forecast-strip" id="forecast-strip"></div>
  </div>
</div>

B Step 2: Styling & Responsive CSS

Modern CSS flexbox and grid layouts are used to make the UI look polished, centered, and responsive across desktop, tablet, and mobile screens.

CSS (style.css)
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
body {
  background: linear-gradient(135deg, #0284c7 0%, #0f172a 100%);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  color: #ffffff;
}
.weather-card {
  background: rgba(15, 23, 42, 0.85);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 24px;
  padding: 24px;
  width: 100%;
  max-width: 440px;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
}
.search-box {
  display: flex;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  padding: 4px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  margin-bottom: 12px;
}
.search-box input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  padding: 10px 14px;
  color: #ffffff;
  font-size: 0.95rem;
}
.search-box input::placeholder {
  color: #94a3b8;
}
.search-box button {
  background: #38bdf8;
  border: none;
  color: #0f172a;
  width: 40px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
  transition: background 0.2s;
}
.search-box button:hover {
  background: #7dd3fc;
}
.preset-chips {
  display: flex;
  gap: 6px;
  margin-bottom: 20px;
  overflow-x: auto;
  padding-bottom: 4px;
}
.chip {
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: #cbd5e1;
  padding: 4px 12px;
  border-radius: 20px;
  font-size: 0.8rem;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.2s;
}
.chip.active, .chip:hover {
  background: #38bdf8;
  color: #0f172a;
  font-weight: 600;
}
.main-weather {
  text-align: center;
  margin-bottom: 24px;
}
.weather-icon-wrapper {
  font-size: 4rem;
  color: #facc15;
  margin-bottom: 8px;
  animation: float 3s ease-in-out infinite;
}
@keyframes float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-8px); }
}
.temp-wrapper {
  display: flex;
  align-items: flex-start;
  justify-content: center;
}
.temp-val {
  font-size: 4.5rem;
  font-weight: 800;
  line-height: 1;
}
.unit-toggle {
  font-size: 1.5rem;
  color: #38bdf8;
  cursor: pointer;
  margin-left: 6px;
  font-weight: 600;
}
.city-name {
  font-size: 1.5rem;
  font-weight: 700;
  margin-top: 6px;
}
.weather-desc {
  color: #94a3b8;
  font-size: 0.95rem;
  text-transform: capitalize;
}
.metrics-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-bottom: 24px;
}
.metric-item {
  background: rgba(255, 255, 255, 0.06);
  border-radius: 12px;
  padding: 12px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.metric-item i {
  font-size: 1.4rem;
  color: #38bdf8;
}
.m-val {
  display: block;
  font-weight: 700;
  font-size: 0.95rem;
}
.m-label {
  color: #94a3b8;
  font-size: 0.75rem;
}
.forecast-section h3 {
  font-size: 0.95rem;
  color: #94a3b8;
  margin-bottom: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.forecast-strip {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}
.forecast-day {
  background: rgba(255, 255, 255, 0.05);
  border-radius: 10px;
  padding: 10px 8px;
  text-align: center;
  flex: 1;
}
.forecast-day .day-name {
  font-size: 0.75rem;
  color: #94a3b8;
  display: block;
  margin-bottom: 4px;
}
.forecast-day i {
  font-size: 1.2rem;
  color: #38bdf8;
  margin-bottom: 4px;
  display: block;
}
.forecast-day .day-temp {
  font-size: 0.85rem;
  font-weight: 700;
}

C Step 3: JavaScript Logic & Event Handling

Here is the complete JavaScript code handling the state, event listeners, mathematical logic, and dynamic DOM rendering:

JavaScript (script.js)
const weatherDatabase = {
  'new york': { name: 'New York, US', tempC: 24, condition: 'Sunny', icon: 'fa-sun', color: '#facc15', wind: '14 km/h', humidity: '58%', uv: '6 (Mod)', vis: '10 km' },
  'london': { name: 'London, UK', tempC: 16, condition: 'Rainy', icon: 'fa-cloud-showers-heavy', color: '#60a5fa', wind: '22 km/h', humidity: '82%', uv: '3 (Low)', vis: '7 km' },
  'tokyo': { name: 'Tokyo, JP', tempC: 28, condition: 'Partly Cloudy', icon: 'fa-cloud-sun', color: '#fb923c', wind: '10 km/h', humidity: '65%', uv: '7 (High)', vis: '10 km' },
  'paris': { name: 'Paris, FR', tempC: 19, condition: 'Cloudy', icon: 'fa-cloud', color: '#cbd5e1', wind: '12 km/h', humidity: '70%', uv: '4 (Mod)', vis: '9 km' },
  'sydney': { name: 'Sydney, AU', tempC: 22, condition: 'Clear Sky', icon: 'fa-sun', color: '#facc15', wind: '18 km/h', humidity: '50%', uv: '8 (Very High)', vis: '10 km' }
};

let currentTempC = 24;
let isCelsius = true;

const tempVal = document.getElementById('temp-val');
const unitBtn = document.getElementById('unit-btn');
const cityName = document.getElementById('city-name');
const weatherDesc = document.getElementById('weather-desc');
const weatherIcon = document.getElementById('weather-icon');
const windSpeed = document.getElementById('wind-speed');
const humidityVal = document.getElementById('humidity-val');
const uvVal = document.getElementById('uv-val');
const visVal = document.getElementById('vis-val');
const forecastStrip = document.getElementById('forecast-strip');
const searchInput = document.getElementById('city-input');
const searchBtn = document.getElementById('search-btn');
const chips = document.querySelectorAll('.chip');

function updateWeatherUI(data) {
  currentTempC = data.tempC;
  renderTemperature();
  cityName.textContent = data.name;
  weatherDesc.textContent = data.condition;
  weatherIcon.innerHTML = `<i class="fas ${data.icon}"></i>`;
  weatherIcon.style.color = data.color;
  windSpeed.textContent = data.wind;
  humidityVal.textContent = data.humidity;
  uvVal.textContent = data.uv;
  visVal.textContent = data.vis;

  renderForecast(data.tempC);
}

function renderTemperature() {
  if (isCelsius) {
    tempVal.textContent = Math.round(currentTempC);
    unitBtn.textContent = 'Β°C';
  } else {
    tempVal.textContent = Math.round((currentTempC * 9/5) + 32);
    unitBtn.textContent = 'Β°F';
  }
}

function renderForecast(baseTemp) {
  const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
  const icons = ['fa-sun', 'fa-cloud-sun', 'fa-cloud-rain', 'fa-bolt', 'fa-sun'];
  forecastStrip.innerHTML = '';

  days.forEach((day, i) => {
    const diff = ((i * 3) % 7) - 3;
    const dayTemp = isCelsius ? `${Math.round(baseTemp + diff)}Β°` : `${Math.round(((baseTemp + diff) * 9/5) + 32)}Β°`;
    const div = document.createElement('div');
    div.className = 'forecast-day';
    div.innerHTML = `
      <span class="day-name">${day}</span>
      <i class="fas ${icons[i]}"></i>
      <span class="day-temp">${dayTemp}</span>
    `;
    forecastStrip.appendChild(div);
  });
}

function searchCity(name) {
  const key = name.trim().toLowerCase();
  if (weatherDatabase[key]) {
    updateWeatherUI(weatherDatabase[key]);
  } else {
    // Generate simulated dynamic data for unknown city
    const randomTemp = Math.floor(Math.random() * 25) + 10;
    const generated = {
      name: `${name.charAt(0).toUpperCase() + name.slice(1)}, World`,
      tempC: randomTemp,
      condition: 'Partly Sunny',
      icon: 'fa-cloud-sun',
      color: '#facc15',
      wind: `${Math.floor(Math.random() * 20) + 5} km/h`,
      humidity: `${Math.floor(Math.random() * 40) + 40}%`,
      uv: '5 (Mod)',
      vis: '10 km'
    };
    updateWeatherUI(generated);
  }
}

searchBtn.addEventListener('click', () => {
  if (searchInput.value) {
    searchCity(searchInput.value);
    chips.forEach(c => c.classList.remove('active'));
  }
});

searchInput.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' && searchInput.value) {
    searchCity(searchInput.value);
    chips.forEach(c => c.classList.remove('active'));
  }
});

chips.forEach(chip => {
  chip.addEventListener('click', () => {
    chips.forEach(c => c.classList.remove('active'));
    chip.classList.add('active');
    searchCity(chip.dataset.city);
  });
});

unitBtn.addEventListener('click', () => {
  isCelsius = !isCelsius;
  renderTemperature();
  renderForecast(currentTempC);
});

// Init
updateWeatherUI(weatherDatabase['new york']);

3. Core JavaScript Concepts You Learned

Async/Await & Simulation

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Dynamic Object Lookups

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Conditional CSS Icons

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Unit Conversion (Β°C / Β°F)

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Responsive Cards

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

4. How to Run Locally on Your Computer

  1. Download the project ZIP: Click the Download Project (.ZIP) button at the top or in the live runner to save weather-app-project.zip.
  2. Extract the archive: Unzip the downloaded file. Inside you will find separate index.html, style.css, script.js, index-standalone.html, and a comprehensive README.md.
  3. Open in any browser: Simply double-click index.html (or index-standalone.html) to open and test immediately in Chrome, Firefox, Safari, or Edge.
  4. Edit in Visual Studio Code: Open the extracted folder in VS Code. Right-click index.html and select "Open with Live Server" to enjoy instant live reloading as you modify HTML, CSS, or JS!

Bonus Challenges for Learners

Ready to level up? Try adding these extra features on your own:

  • Add sound effects or audio feedback for user button clicks.
  • Add custom theme color customizer with CSS variables.
  • Export the results to PDF or copy as a formatted text summary.

Other Beginner Projects You Might Like

Beginner30 mins
Math & Calculators

Interactive Calculator

Build a sleek, fully functional calculator with arithmetic operations, decimal support, backspace, and calculation history.

DOM ManipulationEvent Listenerseval & Math Logic+2
Beginner25 mins
Productivity & UI

Smart To-Do List with LocalStorage

A full-featured task manager with local storage persistence, filtering (All, Active, Completed), task counter, and priority levels.

Safe LocalStorage APIArray Methods (map, filter, unshift)Event Delegation & Keyboard Triggers+2
Beginner20 mins
Timers & Clocks

Digital & Analog Dual Clock

A dynamic dual-mode clock with synchronized analog ticking hands, 12h/24h digital time, date formatting, and light/dark theme switch.

setInterval & requestAnimationFrameDate Object MethodsCSS transform: rotate()+2