bottega

sections

Weather Widget Card

A compact weather display card featuring a custom-shaped SVG background gradient that tapers in from a swept upper-left edge, a decorative cloud+lightning SVG icon floating in the top-right corner, a large temperature readout, and a bottom info bar showing high/low range, location, and current condition. All gradient stop colors derive from `--primary` via `color-mix(in oklch, …)` so the card themes correctly under any palette in both light and dark modes. The shaped path, cloud, and icon layers are `aria-hidden`; the card root is an `<article>` with an aria-label summarising temperature, condition, and location for assistive technology. Fully server-renderable — no hooks or pointer logic required.

Ported from sahilxkhadka (MIT)

24°

H:32° L:16°

Edmonton, Canada

Light Rain


Install

1. Register the namespace (once per project):

json
// components.json — register the @bottega namespace once
{
  "registries": {
    "@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
  }
}

2. Add the component:

bash
npx shadcn add @bottega/weather-widget-card

Usage

Usagetsx
<WeatherWidgetCard />

Props

PropTypeDefaultDescription
temperaturestring24°Current temperature string, e.g. "24°".
highLowstringH:32° L:16°High/Low summary string, e.g. "H:32° L:16°".
locationstringKathmandu, NepalLocation name, e.g. "Kathmandu, Nepal".
conditionstringMid RainCondition label shown bottom-right, e.g. "Mid Rain".
classNamestringAdditional class names merged onto the root element.

Source

weather-widget-card.tsxtsx
import type React from "react";
import styles from "./weather-widget-card.module.css";

export type WeatherWidgetCardProps = {
  /** Current temperature string, e.g. "24°". */
  temperature: string;
  /** High/Low summary string, e.g. "H:32° L:16°". */
  highLow?: string;
  /** Location name, e.g. "Kathmandu, Nepal". */
  location?: string;
  /** Condition label shown bottom-right, e.g. "Mid Rain". */
  condition?: string;
  /** Additional class names merged onto the root element. */
  className?: string;
};

/** Decorative cloud+lightning SVG icon — always aria-hidden. */
function CloudIcon() {
  return (
    <svg
      aria-hidden="true"
      fill="none"
      viewBox="0 0 64 64"
      xmlns="http://www.w3.org/2000/svg"
      className={styles.cloudIcon}
    >
      {/* rain drops — accent tint */}
      <g fill="var(--weather-icon-rain)">
        <path d="M10.8 42.9c-.5 1.5-.1 3 1 3.4c1.1.4 2.4-.5 3-2c.6-1.8.7-4.1.2-6.9c-2.1 1.9-3.6 3.8-4.2 5.5" />
        <path d="M13.2 57.4c.6-1.8.7-4.1.2-6.9c-2.1 1.8-3.6 3.7-4.2 5.5c-.5 1.5-.1 3 1 3.4c1.1.4 2.5-.5 3-2" />
        <path d="M51.5 37.4c-2.1 1.8-3.6 3.7-4.2 5.5c-.5 1.5-.1 3 1 3.4c1.1.4 2.4-.5 3-2c.5-1.7.6-4.1.2-6.9" />
        <path d="M38.2 55.9c-.5 1.5-.1 3 1 3.4s2.4-.5 3-2c.6-1.8.7-4.1.2-6.9c-2 1.9-3.5 3.8-4.2 5.5" />
        <path d="M46.9 55.9c-.5 1.5-.1 3 1 3.4s2.4-.5 3-2c.6-1.8.7-4.1.2-6.9c-2.1 1.9-3.6 3.8-4.2 5.5" />
        <path d="M18.6 55.9c-.5 1.5-.1 3 1 3.4s2.4-.5 3-2c.6-1.8.7-4.1.2-6.9c-2.1 1.9-3.6 3.8-4.2 5.5" />
      </g>
      {/* lightning bolt — primary accent */}
      <path
        d="M24.5 31.9l-4.9 16.2h12.5L27.9 62l16.5-20.2H32.5l2.9-9.9z"
        fill="var(--weather-icon-bolt)"
      />
      {/* cloud white body — background surface */}
      <path
        fill="var(--weather-icon-cloud-body)"
        d="M18.2 32.5c-.8 0-1.6-.1-2.4-.4c-3.1-1-5.3-3.9-5.3-7.2c0-2.2 1-4.3 2.6-5.7c.4-.4.9-.7 1.4-1l.5-1.8c1.3-4.4 5.4-7.5 10-7.5c.5 0 .9 0 1.5.1c.4.1.8.1 1.2.3l.2-.4c1.9-3.3 5.4-5.4 9.2-5.4C43 3.5 47.7 8.2 47.7 14v1c.4.2.9.4 1.3.6c2.8 1.6 4.5 4.6 4.5 7.8c0 4.2-2.9 7.8-7 8.8c-.7.2-1.4.2-2 .2H18.2z"
      />
      {/* cloud outline — muted tint */}
      <path
        fill="var(--weather-icon-cloud-outline)"
        d="M37.1 5c5 0 9 4 9 8.9v.7c-2.1.2-4 1-5.4 2.3c1.1-.6 2.4-1 3.7-1c.5 0 1 .1 1.5.1c.8.2 1.6.5 2.3.9c2.3 1.3 3.8 3.7 3.8 6.5c0 3.6-2.5 6.5-5.8 7.3c-.7.2-1.2.3-1.8.3H18.2c-.7 0-1.3-.1-1.9-.3c-2.4-.8-4.2-3.1-4.2-5.8c0-1.8.8-3.5 2.1-4.6c.6-.5 1.3-.9 2-1.2c.6-.2 1.3-.3 2-.3c2 0 3.7.9 4.9 2.4h.1c-1.3-2.4-3.7-4.1-6.6-4.3c1.1-3.7 4.5-6.4 8.5-6.4c.4 0 .9 0 1.3.1c.8.1 1.6.3 2.3.7c2.7 1.2 4.7 3.7 5.1 6.8V18c0-3.4-1.8-6.5-4.5-8.3C30.8 6.9 33.8 5 37.1 5m0-3C33 2 29.2 4.1 27 7.6h-.3c-.6-.1-1.2-.1-1.7-.1c-5.3 0-10 3.5-11.4 8.6l-.3 1.2c-.4.2-.7.5-1.1.8c-2 1.7-3.1 4.2-3.1 6.9c0 4 2.5 7.4 6.3 8.7c.9.3 1.9.5 2.9.5h26.2c.8 0 1.6-.1 2.4-.3c4.8-1.1 8.2-5.3 8.2-10.3c0-3.8-2-7.3-5.3-9.1c-.2-.1-.3-.2-.5-.3v-.1C49.2 7.4 43.8 2 37.1 2z"
      />
    </svg>
  );
}

/**
 * WeatherWidgetCard — a compact weather display card with a shaped SVG
 * primary-gradient background, a decorative cloud+lightning icon, a large
 * temperature readout, and a bottom bar showing hi/lo + location + condition.
 * All brand colors use `--primary` token-derived gradients; the white/muted
 * text is driven by `--primary-foreground` and `--foreground`.
 */
export function WeatherWidgetCard({
  temperature = "24°",
  highLow = "H:32° L:16°",
  location = "Kathmandu, Nepal",
  condition = "Mid Rain",
  className = "",
}: WeatherWidgetCardProps) {
  const rootClass = [styles.card, className].filter(Boolean).join(" ");

  return (
    <article className={rootClass} aria-label={`Weather: ${temperature}, ${condition}, ${location}`}>
      {/* Shaped gradient background — decorative, aria-hidden */}
      <svg
        aria-hidden="true"
        fill="none"
        viewBox="0 0 342 175"
        height="175"
        width="342"
        xmlns="http://www.w3.org/2000/svg"
        className={styles.background}
        preserveAspectRatio="none"
      >
        <path
          fill="url(#weather-bg-gradient)"
          d="M0 66.4396C0 31.6455 0 14.2484 11.326 5.24044C22.6519 -3.76754 39.6026 0.147978 73.5041 7.97901L307.903 62.1238C324.259 65.9018 332.436 67.7909 337.218 73.8031C342 79.8154 342 88.2086 342 104.995V131C342 151.742 342 162.113 335.556 168.556C329.113 175 318.742 175 298 175H44C23.2582 175 12.8873 175 6.44365 168.556C0 162.113 0 151.742 0 131V66.4396Z"
        />
        <defs>
          <linearGradient
            gradientUnits="userSpaceOnUse"
            y2="128"
            x2="354.142"
            y1="128"
            x1="0"
            id="weather-bg-gradient"
          >
            <stop stopColor="var(--weather-grad-start)" />
            <stop stopColor="var(--weather-grad-end)" offset="1" />
          </linearGradient>
        </defs>
      </svg>

      {/* Cloud icon — top-right decorative */}
      <div className={styles.cloud} aria-hidden="true">
        <CloudIcon />
      </div>

      {/* Main temperature readout */}
      <p className={styles.temperature}>{temperature}</p>

      {/* Bottom info bar */}
      <div className={styles.info}>
        <div className={styles.infoLeft}>
          <p className={styles.highLow}>{highLow}</p>
          <p className={styles.location}>{location}</p>
        </div>
        <p className={styles.condition}>{condition}</p>
      </div>
    </article>
  );
}

Dependencies

  • @bottega/tokens