weather-img

Interactive Weather Dashboard

We'll show you how to make a **Interactive Weather Dashboard** that shows current weather information with slick animations and an eye-catching user interface.

  1. You'll discover how to organise HTML to produce visually appealing elements, like information panels for temperature, wind speed, humidity, and air quality, among other things.
  2. The project makes the dashboard active and interesting by enhancing the weather features with SVG icons and animations.
  3. The dashboard's adaptable structure ensures seamless operation on desktop and mobile devices, offering the best possible experience on all platforms.
  4. This project shows you how to use HTML, CSS, and JavaScript to create an interactive weather dashboard that shows current weather information for a better user experience.

How to Design:

Start by constructing a simple HTML structure that includes sections for the city name, temperature, and meteorological information such as humidity, wind, visibility, and air quality.


Use CSS to style the layout for a crisp, contemporary appearance. To make the dashboard dynamic and eye-catching, add animations to the elements and icons.


Use JavaScript to fetch real-time weather data from an API and dynamically update widget values. Add event listeners for refreshing data or switching between different cities.


            <!DOCTYPE html>
            <html lang="en" >
            <head>
              <meta charset="UTF-8">
              <title>Weather Widgets</title>
              <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"><link rel="stylesheet" href="./style.css">
            
            </head>
            <body>
            <!-- partial:index.partial.html -->
            <svg display="none">
                <symbol id="wind" viewBox="0 0 16 16">
                    <g fill="none" stroke="hsl(0,0%,100%)" stroke-linecap="round" stroke-width="2">
                        <polyline points="2 3,10 3" />
                        <polyline points="6 8,14 8" />
                        <polyline points="2 13,10 13" />
                    </g>
                </symbol>
                <symbol id="visibility" viewBox="0 0 16 16">
                    <g fill="none" stroke="hsl(0,0%,100%)" stroke-width="2">
                        <ellipse cx="8" cy="8" rx="7" ry="4" />
                        <circle cx="8" cy="8" r="2" />
                    </g>
                </symbol>
                <symbol id="air-quality" viewBox="0 0 16 16">
                    <g fill="hsl(0,0%,100%)">
                        <circle cx="4" cy="11" r="2" />
                        <circle cx="8" cy="5" r="2" />
                        <circle cx="12" cy="11" r="2" />
                    </g>
                </symbol>
                <symbol id="humidity" viewBox="0 0 16 16">
                    <g fill="none" stroke="hsl(0,0%,100%)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
                        <path d="M 1 3 C 6 7, 10 0, 15 4" transform="translate(0,-1)" />
                        <path d="M 1 3 C 6 7, 10 0, 15 4" transform="translate(0,3)" />
                        <path d="M 1 3 C 3 5, 6 4, 8 3" transform="translate(0,7)" />
                        <path d="M 4 1 C -3 8, 11 8, 4 1" transform="translate(8,8)" />
                    </g>
                </symbol>
                <symbol id="sunny" viewBox="0 0 48 48">
                    <g stroke="hsl(53,90%,50%)" stroke-linecap="round" stroke-width="4">
                        <polyline points="24 24,24 21" transform="rotate(0,24,24) translate(0,-18)" />
                        <polyline points="24 24,24 21" transform="rotate(45,24,24) translate(0,-18)" />
                        <polyline points="24 24,24 21" transform="rotate(90,24,24) translate(0,-18)" />
                        <polyline points="24 24,24 21" transform="rotate(135,24,24) translate(0,-18)" />
                        <polyline points="24 24,24 21" transform="rotate(180,24,24) translate(0,-18)" />
                        <polyline points="24 24,24 21" transform="rotate(225,24,24) translate(0,-18)" />
                        <polyline points="24 24,24 21" transform="rotate(270,24,24) translate(0,-18)" />
                        <polyline points="24 24,24 21" transform="rotate(315,24,24) translate(0,-18)" />
                    </g>
                    <circle cx="24" cy="24" r="12" fill="hsl(53,90%,50%)" />
                </symbol>
                <symbol id="cloudy" viewBox="0 0 48 48">
                    <g fill="hsl(0,0%,100%)" transform="translate(0,6)">
                        <rect x="0" y="12" width="48" height="20" rx="10" ry="10" />
                        <circle cx="28" cy="14" r="14" />
                        <circle cx="14" cy="10" r="6" />
                    </g>
                </symbol>
            </svg>
            <main>
                <details id="widget1" class="widget" open>
                    <summary class="widget__btn">
                        <span class="widget__top">
                            <span data-stat="city">-</span>
                            <small class="widget__weather">
                                <span data-stat="kind">-</span>
                                <span class="widget__bull">&bull;</span>
                                <time data-stat="time">-</time>
                            </small>
                        </span>
                        <div class="widget__temp">
                            <span data-stat="temperature">-</span><sup>&deg;<span data-stat="temperature_scale">-</span></sup>
                        </div>
                    </summary>
                    <div class="widget__details">
                        <svg class="widget__weather-symbol" width="16px" height="16px" aria-hidden="true">
                            <use href="#" data-symbol />
                        </svg>
                        <span class="widget__detail">
                            <small class="widget__detail-name">Wind</small>
                            <br>
                            <span class="widget__detail-value">
                                <svg class="widget__detail-icon" width="16px" height="16px" aria-hidden="true">
                                    <use href="#wind" />
                                </svg>
                                <span data-stat="wind">-</span>
                            </span>
                        </span>
                        <span class="widget__detail">
                            <small class="widget__detail-name">Visibility</small>
                            <br>
                            <span class="widget__detail-value">
                                <svg class="widget__detail-icon" width="16px" height="16px" aria-hidden="true">
                                    <use href="#visibility" />
                                </svg>
                                <span data-stat="visibility">-</span>
                            </span>
                        </span>
                        <span class="widget__detail">
                            <small class="widget__detail-name">Air Quality</small>
                            <br>
                            <span class="widget__detail-value">
                                <svg class="widget__detail-icon" width="16px" height="16px" aria-hidden="true">
                                    <use href="#air-quality" />
                                </svg>
                                <span data-stat="air_quality">-</span>
                            </span>
                        </span>
                        <span class="widget__detail">
                            <small class="widget__detail-name">Humidity</small>
                            <br>
                            <span class="widget__detail-value">
                                <svg class="widget__detail-icon" width="16px" height="16px" aria-hidden="true">
                                    <use href="#humidity" />
                                </svg>
                                <span data-stat="humidity">-</span>
                            </span>
                        </span>
                    </div>
                </details>
                <details id="widget2" class="widget">
                    <summary class="widget__btn">
                        <span class="widget__top">
                            <span data-stat="city">-</span>
                            <small class="widget__weather">
                                <span data-stat="kind">-</span>
                                <span class="widget__bull">&bull;</span>
                                <time data-stat="time">-</time>
                            </small>
                        </span>
                        <div class="widget__temp">
                            <span data-stat="temperature">-</span><sup>&deg;<span data-stat="temperature_scale">-</span></sup>
                        </div>
                    </summary>
                    <div class="widget__details">
                        <svg class="widget__weather-symbol" width="16px" height="16px" aria-hidden="true">
                            <use href="#" data-symbol />
                        </svg>
                        <span class="widget__detail">
                            <small class="widget__detail-name">Wind</small>
                            <br>
                            <span class="widget__detail-value">
                                <svg class="widget__detail-icon" width="16px" height="16px" aria-hidden="true">
                                    <use href="#wind" />
                                </svg>
                                <span data-stat="wind">-</span>
                            </span>
                        </span>
                        <span class="widget__detail">
                            <small class="widget__detail-name">Visibility</small>
                            <br>
                            <span class="widget__detail-value">
                                <svg class="widget__detail-icon" width="16px" height="16px" aria-hidden="true">
                                    <use href="#visibility" />
                                </svg>
                                <span data-stat="visibility">-</span>
                            </span>
                        </span>
                        <span class="widget__detail">
                            <small class="widget__detail-name">Air Quality</small>
                            <br>
                            <span class="widget__detail-value">
                                <svg class="widget__detail-icon" width="16px" height="16px" aria-hidden="true">
                                    <use href="#air-quality" />
                                </svg>
                                <span data-stat="air_quality">-</span>
                            </span>
                        </span>
                        <span class="widget__detail">
                            <small class="widget__detail-name">Humidity</small>
                            <br>
                            <span class="widget__detail-value">
                                <svg class="widget__detail-icon" width="16px" height="16px" aria-hidden="true">
                                    <use href="#humidity" />
                                </svg>
                                <span data-stat="humidity">-</span>
                            </span>
                        </span>
                    </div>
                </details>
            </main>
            <!-- partial -->
              <script  src="./script.js"></script>
            
            </body>
            </html>            
            
	

            * {
                border: 0;
                box-sizing: border-box;
                margin: 0;
                padding: 0;
              }
              
              :root {
                --hue: 223;
                --bg: hsl(var(--hue),90%,90%);
                --fg: hsl(var(--hue),90%,10%);
                --primary: hsl(var(--hue),90%,50%);
                --anim-dur: 0.4s;
                --trans-dur: 0.3s;
                --ease-in-out: cubic-bezier(0.67,0,0.33,1);
                --ease-out: cubic-bezier(0.33,1,0.67,1);
                font-size: calc(14px + (30 - 14) * (100vw - 280px) / (3840 - 280));
              }
              
              body,
              button {
                color: var(--fg);
                font: 1em/1.5 -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif;
              }
              
              body {
                background: url("https://assets.codepen.io/416221/beach_landscape.jpg") center/cover, var(--bg);
                height: 100vh;
              }
              
              main {
                backdrop-filter: blur(8px);
                -webkit-backdrop-filter: blur(8px);
                display: flex;
                flex-direction: column;
                justify-content: center;
                align-items: center;
                gap: 0.75em;
                padding: 1.5em 0;
                width: 100%;
                height: min-content;
                min-height: 100%;
              }
              
              .widget {
                background-color: hsla(var(--hue), 90%, 10%, 0.35);
                backdrop-filter: blur(16px);
                -webkit-backdrop-filter: blur(16px);
                border-radius: 1.75em;
                color: white;
                flex-shrink: 0;
                position: relative;
                width: 18em;
                min-height: 4.5em;
              }
              .widget__btn, .widget__details {
                padding: 1em;
                padding-inline-start: 1.25em;
              }
              .widget__btn {
                background-color: hsla(var(--hue), 90%, 10%, 0);
                border-radius: 1.75em;
                box-shadow: 0 0 0 0.125em hsla(var(--hue), 90%, 90%, 0) inset;
                color: currentColor;
                cursor: pointer;
                display: flex;
                align-items: center;
                outline: transparent;
                position: relative;
                width: 100%;
                transition: background-color var(--trans-dur) var(--ease-in-out), box-shadow calc(var(--trans-dur) / 2) var(--ease-in-out);
                -webkit-tap-highlight-color: transparent;
              }
              .widget__btn:focus-visible {
                box-shadow: 0 0 0 0.125em hsla(var(--hue), 90%, 90%, 1) inset;
              }
              .widget__btn:hover {
                background-color: hsla(var(--hue), 90%, 10%, 0.1);
              }
              .widget__btn::marker, .widget__btn::-webkit-details-marker {
                display: none;
              }
              .widget__bull {
                margin: 0 0.5em;
              }
              .widget__detail {
                display: block;
              }
              .widget__detail-icon {
                display: block;
                width: 1em;
                height: 1em;
              }
              .widget__detail-name {
                font-size: 0.75em;
                opacity: 0.8;
              }
              .widget__detail-value {
                display: flex;
                align-items: center;
                gap: 0.5em;
              }
              .widget__details {
                display: grid;
                grid-template-columns: 1fr 1fr;
                grid-gap: 1em;
                padding-inline-end: 6em;
                position: relative;
              }
              .widget__temp {
                font-size: 3em;
                line-height: 1;
              }
              .widget__temp sup {
                display: inline-block;
                font-size: 0.833rem;
                line-height: 1;
                transform: translateY(-66.7%);
              }
              [dir=rtl] .widget__temp {
                right: auto;
                left: 1rem;
              }
              .widget__top, .widget__weather {
                display: flex;
              }
              .widget__top {
                flex: 1;
                flex-direction: column;
                margin-inline-end: 0.5em;
              }
              .widget__weather {
                font-size: 0.75em;
                line-height: 1;
                opacity: 0.8;
                text-transform: capitalize;
              }
              .widget__weather-symbol {
                display: block;
                margin-bottom: 1em;
                pointer-events: none;
                position: absolute;
                right: 1rem;
                bottom: 100%;
                width: 3em;
                height: 3em;
              }
              [dir=rtl] .widget__weather-symbol {
                right: auto;
                left: 1rem;
              }
              .widget[open] .widget__btn {
                border-radius: 1.75em 1.75em 0 0;
              }
              .widget.collapsing, .widget.expanding {
                overflow: hidden;
              }
              .widget.collapsing .widget__temp {
                animation: widget-slide-up var(--anim-dur) var(--ease-out);
              }
              .widget.collapsing .widget__detail, .widget.collapsing .widget__weather-symbol {
                animation: widget-fade-out var(--anim-dur) var(--ease-out);
              }
              .widget[open] .widget__temp {
                transform: translateY(9rem);
              }
              .widget.expanding .widget__temp {
                animation: widget-slide-down var(--anim-dur) var(--ease-out);
              }
              .widget.expanding .widget__detail, .widget.expanding .widget__weather-symbol {
                animation: widget-fade-slide-in var(--anim-dur) var(--ease-out);
              }
              .widget.expanding .widget__detail:nth-of-type(2) {
                animation-delay: calc(var(--anim-dur) * 0.1);
              }
              .widget.expanding .widget__detail:nth-of-type(3) {
                animation-delay: calc(var(--anim-dur) * 0.2);
              }
              .widget.expanding .widget__detail:nth-of-type(4) {
                animation-delay: calc(var(--anim-dur) * 0.3);
              }
              
              /* Animations */
              @keyframes widget-fade-out {
                from {
                  opacity: 1;
                }
                to {
                  opacity: 0;
                }
              }
              @keyframes widget-fade-slide-in {
                from, 50% {
                  opacity: 0;
                  transform: translateY(50%);
                }
                to {
                  opacity: 1;
                  transform: translateY(0);
                }
              }
              @keyframes widget-slide-down {
                from {
                  transform: translateY(0);
                }
                to {
                  transform: translateY(9rem);
                }
              }
              @keyframes widget-slide-up {
                from {
                  transform: translateY(9rem);
                }
                to {
                  transform: translateY(0);
                }
              }

    

        "use strict";
        window.addEventListener("DOMContentLoaded", () => {
            const widget1 = new WeatherWidget("#widget1", {
                city: "Harrisburg",
                kind: "sunny",
                time: new Date(2024, 5, 21, 11, 15),
                temperature: 88,
                temperature_scale: "F",
                wind: 5,
                wind_unit: "mph",
                visibility: 22,
                visibility_unit: "mi",
                air_quality: 54,
                humidity: 59
            });
            const widget2 = new WeatherWidget("#widget2", {
                city: "Seattle",
                kind: "cloudy",
                time: new Date(2024, 5, 21, 8, 15),
                temperature: 70,
                temperature_scale: "F",
                wind: 6,
                wind_unit: "mph",
                visibility: 23,
                visibility_unit: "mi",
                air_quality: 41,
                humidity: 47
            });
        });
        class WeatherWidget {
            /**
             * @param el CSS selector the widget
             * @param data Weather data
             */
            constructor(el, data) {
                var _a, _b, _c;
                /** Element is collapsing */
                this.isCollapsing = false;
                /** Element is expanding */
                this.isExpanding = false;
                /** Animation duration and easing */
                this.animParams = {
                    duration: 400,
                    easing: "cubic-bezier(0.33,1,0.67,1)"
                };
                /** Actions to run after expanding the item. */
                this.animActionsExpand = {
                    onfinish: this.onAnimationFinish.bind(this, true),
                    oncancel: () => { this.isExpanding = false; }
                };
                /** Actions to run after collapsing the item. */
                this.animActionsCollapse = {
                    onfinish: this.onAnimationFinish.bind(this, false),
                    oncancel: () => { this.isCollapsing = false; }
                };
                /** This widget is expanded to show weather details. */
                this.detailsOpen = true;
                /** Language used for time formatting */
                this.lang = "en-US";
                this.el = document.querySelector(el);
                this.weather = data;
                this.displayWeather();
                this.summary = (_a = this.el) === null || _a === void 0 ? void 0 : _a.querySelector("summary");
                (_b = this.summary) === null || _b === void 0 ? void 0 : _b.addEventListener("click", this.toggle.bind(this));
                this.content = (_c = this.summary) === null || _c === void 0 ? void 0 : _c.nextElementSibling;
            }
            /** Display the weather data in the UI. */
            displayWeather() {
                if (!this.weather)
                    return;
                const weatherProps = Object.keys(this.weather).filter(key => key.indexOf("_unit") < 0);
                weatherProps.forEach(prop => {
                    var _a, _b, _c;
                    const propEl = (_a = this.el) === null || _a === void 0 ? void 0 : _a.querySelector(`[data-stat=${prop}]`);
                    if (!propEl)
                        return;
                    let value = (_b = this.weather) === null || _b === void 0 ? void 0 : _b[prop];
                    let unit = "";
                    if (prop == "kind") {
                        // SVG symbol for the kind of weather
                        const kindSymbol = (_c = this.el) === null || _c === void 0 ? void 0 : _c.querySelector("[data-symbol]");
                        kindSymbol === null || kindSymbol === void 0 ? void 0 : kindSymbol.setAttribute("href", `#${value}`);
                    }
                    else if (prop === "time") {
                        // `datetime` attribute
                        const valueAsDate = value;
                        const hourRaw = valueAsDate.getHours();
                        let hour = hourRaw < 10 ? `0${hourRaw}` : `${hourRaw}`;
                        const minute = `${valueAsDate.getMinutes()}`;
                        propEl.setAttribute("datetime", `${hour}:${minute}`);
                        // display the hour and minute
                        const format = new Intl.DateTimeFormat(this.lang, {
                            hour: "numeric",
                            minute: "2-digit",
                        });
                        value = format.format(value);
                    }
                    else if (prop === "wind") {
                        unit = "mph";
                    }
                    else if (prop === "visibility") {
                        unit = "mi";
                    }
                    if (unit !== "") {
                        // attach the unit if applicable
                        value += ` ${unit}`;
                    }
                    propEl.innerText = `${value}`;
                });
            }
            /**
             * Open or close the widget.
             * @param e Click event whose default behavior should be prevented
             */
            toggle(e) {
                var _a, _b, _c;
                e === null || e === void 0 ? void 0 : e.preventDefault();
                (_a = this.el) === null || _a === void 0 ? void 0 : _a.classList.remove("collapsing", "expanding");
                if (this.isCollapsing || !((_b = this.el) === null || _b === void 0 ? void 0 : _b.open)) {
                    this.open();
                }
                else if (this.isExpanding || ((_c = this.el) === null || _c === void 0 ? void 0 : _c.open)) {
                    this.collapse();
                }
            }
            /** Open the item and run the animation for expanding. */
            open() {
                if (this.el) {
                    this.el.style.height = `${this.el.offsetHeight}px`;
                    this.el.open = true;
                    this.expand();
                }
            }
            /** Expansion animation */
            expand() {
                var _a, _b, _c, _d, _e, _f;
                (_a = this.el) === null || _a === void 0 ? void 0 : _a.classList.add("expanding");
                this.isExpanding = true;
                const startHeight = ((_b = this.el) === null || _b === void 0 ? void 0 : _b.offsetHeight) || 0;
                const summaryHeight = ((_c = this.summary) === null || _c === void 0 ? void 0 : _c.offsetHeight) || 0;
                const contentHeight = ((_d = this.content) === null || _d === void 0 ? void 0 : _d.offsetHeight) || 0;
                const endHeight = summaryHeight + contentHeight;
                (_e = this.animation) === null || _e === void 0 ? void 0 : _e.cancel();
                this.animation = (_f = this.el) === null || _f === void 0 ? void 0 : _f.animate({ height: [`${startHeight}px`, `${endHeight}px`] }, this.animParams);
                if (this.animation) {
                    this.animation.onfinish = this.animActionsExpand.onfinish;
                    this.animation.oncancel = this.animActionsExpand.oncancel;
                }
            }
            /** Close the item and run the animation for collapsing. */
            collapse() {
                var _a, _b, _c, _d, _e;
                (_a = this.el) === null || _a === void 0 ? void 0 : _a.classList.add("collapsing");
                this.isCollapsing = true;
                const startHeight = ((_b = this.el) === null || _b === void 0 ? void 0 : _b.offsetHeight) || 0;
                const endHeight = ((_c = this.summary) === null || _c === void 0 ? void 0 : _c.offsetHeight) || 0;
                (_d = this.animation) === null || _d === void 0 ? void 0 : _d.cancel();
                this.animation = (_e = this.el) === null || _e === void 0 ? void 0 : _e.animate({ height: [`${startHeight}px`, `${endHeight}px`] }, this.animParams);
                if (this.animation) {
                    this.animation.onfinish = this.animActionsCollapse.onfinish;
                    this.animation.oncancel = this.animActionsCollapse.oncancel;
                }
            }
            /** Actions to run when the animation is finished */
            onAnimationFinish(open) {
                if (this.el) {
                    this.el.open = open;
                    if (this.animation) {
                        this.animation = null;
                    }
                    this.isCollapsing = false;
                    this.isExpanding = false;
                    this.el.style.height = "";
                    this.el.classList.remove("collapsing", "expanding");
                }
            }
        }
    


Leave Comments

FOLLOW US