Skip to content
design
Components

Weather bar

The weather now on one line, with the forecast by day, the sea state and the model run a tap below it.

On this page
building 22 kn, gusts 31 knModerate rain showers16 °C1004 hPaSea 2.9 m+6H 25–30 kn+12Hveering 28–36 kn
Forecast by day
DaySkyWindWavesRain
Mon 13Partly cloudy45% cloud12.9 NMWMO 214–19 kn, gusts 262.1 m0.4 mm
Tue 14Moderate rain showers70% cloud4.8 NMWMO 8118–26 kn, gusts 36Reef2.9 m6.2 mm
Wed 15Thunderstorm95% cloud1.4 NMWMO 9528–38 kn, gusts 49Reef4.6 mHeavy sea18.4 mm
Thu 16Slight rain showers60% cloud6.4 NMWMO 8016–24 kn, gusts 34Reef3.8 mHeavy sea3.1 mm
Fri 17Clear sky10% cloud16.1 NMWMO 09–15 kn, gusts 212.6 m0.0 mm
Sea state
Waves
2.9 m, 8 s
Swell
2.2 m, 12 s
Douglas scale
5 · Rough
Weather
Pressure
Falling 3.4 hPa in 3 hrFast fall
Visibility
3.9 NM
Thunderstorms
Thunderstorms possibleCAPE 1350 J/kg, lifted index -3.2
Forecast model
Model
ECMWF IFS
Run
00:00 UTC
Age
7 hr old
Confidence
62%
Wind across models
19–27 kn
Day two of a Biscay crossing, opened: five days with their sky, cloud, wind and gusts, waves, rain and visibility, each with its trend through the day, the thunderstorm gale over the vessel's limits, a rough sea on a long swell, a falling barometer and the model run behind it.
Code
import { WeatherBar } from "@bosunkit/components/weather-bar";
import type { ReactElement } from "react";

export const description: string =
  "Day two of a Biscay crossing from A Coruña to Falmouth, opened: five days with their sky, cloud cover, wind and " +
  "gusts, waves, rain and visibility, each with its trend through the day; Wednesday's thunderstorm gale over the " +
  "reef and heavy-sea limits; a rough sea on an Atlantic swell; a falling barometer; a thunderstorm risk and the " +
  "ECMWF run behind it.";

export default function Showcase(): ReactElement {
  return (
    <WeatherBar
      current={{
        description: "Moderate rain showers",
        wind: "22 kn, gusts 31 kn",
        conditions: "building",
        weatherCode: 81,
        cloudCoverPct: 75,
        windFromDeg: 235,
      }}
      forecasts={[
        { period: "+6H", wind: "25–30 kn", conditions: "" },
        { period: "+12H", wind: "28–36 kn", conditions: "veering" },
      ]}
      temperature="16 °C"
      pressure="1004 hPa"
      seaStateText="2.9 m"
      days={[
        {
          day: "Mon 13",
          weatherCode: 2,
          cloudCoverPct: 45,
          windMinKn: 14,
          windMaxKn: 19,
          windFromDeg: 250,
          gustMaxKn: 26,
          waveHeightM: 2.1,
          precipitationMm: 0.4,
          visibilityMinM: 24000,
          hours: {
            windKn: [14, 15, 16, 18, 19, 18, 16, 15],
            gustKn: [19, 21, 23, 25, 26, 25, 22, 20],
            waveHeightM: [1.9, 2.0, 2.0, 2.1, 2.1, 2.1, 2.0, 2.0],
            precipitationMm: [0, 0, 0, 0.1, 0.2, 0.1, 0, 0],
            cloudCoverPct: [30, 40, 45, 55, 50, 45, 40, 35],
          },
        },
        {
          day: "Tue 14",
          weatherCode: 81,
          cloudCoverPct: 70,
          windMinKn: 18,
          windMaxKn: 26,
          windFromDeg: 235,
          gustMaxKn: 36,
          waveHeightM: 2.9,
          precipitationMm: 6.2,
          visibilityMinM: 9000,
          hours: {
            windKn: [18, 19, 21, 23, 25, 26, 24, 22],
            gustKn: [25, 27, 30, 33, 35, 36, 33, 31],
            waveHeightM: [2.2, 2.4, 2.5, 2.7, 2.8, 2.9, 2.9, 2.8],
            precipitationMm: [0, 0.4, 1.2, 0.8, 1.9, 1.1, 0.6, 0.2],
            cloudCoverPct: [55, 65, 75, 80, 70, 75, 70, 65],
          },
        },
        {
          day: "Wed 15",
          weatherCode: 95,
          cloudCoverPct: 95,
          windMinKn: 28,
          windMaxKn: 38,
          windFromDeg: 225,
          gustMaxKn: 49,
          waveHeightM: 4.6,
          precipitationMm: 18.4,
          visibilityMinM: 2600,
          hours: {
            windKn: [28, 30, 33, 36, 38, 37, 34, 31],
            gustKn: [37, 40, 44, 48, 49, 47, 43, 39],
            waveHeightM: [3.2, 3.5, 3.9, 4.3, 4.6, 4.5, 4.2, 3.9],
            precipitationMm: [0.8, 1.6, 3.2, 4.1, 3.7, 2.6, 1.6, 0.8],
            cloudCoverPct: [85, 90, 100, 100, 100, 95, 95, 90],
          },
        },
        {
          day: "Thu 16",
          weatherCode: 80,
          cloudCoverPct: 60,
          windMinKn: 16,
          windMaxKn: 24,
          windFromDeg: 290,
          gustMaxKn: 34,
          waveHeightM: 3.8,
          precipitationMm: 3.1,
          visibilityMinM: 12000,
          hours: {
            windKn: [24, 23, 21, 19, 18, 17, 16, 16],
            gustKn: [34, 32, 30, 27, 25, 24, 22, 22],
            waveHeightM: [3.8, 3.7, 3.6, 3.5, 3.4, 3.3, 3.2, 3.1],
            precipitationMm: [0.9, 0.7, 0.6, 0.4, 0.3, 0.1, 0.1, 0],
            cloudCoverPct: [80, 75, 70, 60, 55, 50, 45, 45],
          },
        },
        {
          day: "Fri 17",
          weatherCode: 0,
          cloudCoverPct: 10,
          windMinKn: 9,
          windMaxKn: 15,
          windFromDeg: 315,
          gustMaxKn: 21,
          waveHeightM: 2.6,
          precipitationMm: 0,
          visibilityMinM: 30000,
          hours: {
            windKn: [15, 14, 13, 12, 11, 10, 9, 9],
            gustKn: [21, 20, 18, 17, 15, 14, 13, 12],
            waveHeightM: [2.6, 2.4, 2.3, 2.1, 2.0, 1.9, 1.9, 1.8],
            precipitationMm: [0, 0, 0, 0, 0, 0, 0, 0],
            cloudCoverPct: [20, 15, 10, 10, 5, 5, 10, 10],
          },
        },
      ]}
      sea={{
        waveHeightM: 2.9,
        wavePeriodS: 8,
        waveFromDeg: 240,
        swellHeightM: 2.2,
        swellPeriodS: 12,
        swellFromDeg: 285,
        douglas: 5,
      }}
      pressureTrendHpa3h={-3.4}
      pressureHourlyHpa={[1009.6, 1009.1, 1008.6, 1008.1, 1007.6, 1006.4, 1005.3, 1004.2]}
      visibilityM={7400}
      convection={{ capeJPerKg: 1350, liftedIndex: -3.2, risk: "possible" }}
      model={{
        name: "ECMWF IFS",
        run: "00:00 UTC",
        ageHours: 7,
        confidence: 0.62,
        windSpreadKn: { low: 19, high: 27 },
      }}
      limits={{ reefWindKn: 20, heavySeaM: 3, pressureDropHpa3h: 3, fogVisibilityM: 1000 }}
      defaultOpen
    />
  );
}

A weather bar puts the weather now on one line: the sky's glyph, the wind with an arrow pointing downwind, the temperature, pressure and sea state, and the wind for the next periods. Given a forecast by day, a sea state, the weather beyond wind and sea, or the model run, the line gains a control that opens the detail under it, closed until the crew opens it.

The detail starts with a row per day across the bar's whole width. The sky cell draws a filled glyph (sun, clouds, rain, snow, fog or a bolt) from the day's WMO weather code and writes the code's name, with a quieter line under it: the cloud cover in percent, the day's lowest visibility and the code itself. A source without a code gets its glyph and name from the cloud cover in oktas: 0–2 clear, 3–4 partly cloudy, 5–7 mostly cloudy, 8 overcast. Wind and gusts share a cell ("18–26 kn, gusts 36"), then come the waves and the rain. Each measure's cell holds its value, a limit mark and a small trend of the day hour by hour, every day on one scale: wind with its gusts and the reef limit, waves with the heavy-sea limit, rain as bars (none on a dry day), cloud cover as an area. On a wide bar the three share one line with the trend at the cell's right edge; on a narrow one the trend and the mark go under the value. Either way each column's trends start at one edge, so the days compare at a glance. On a phone held upright each day is a block: the day and its sky take a line each, and the wind, waves and rain sit side by side under them, each named above its value, so nothing scrolls sideways. After the days come the sea state with its swell and Douglas scale; the pressure trend with the barometer by the hour (the record up to now, then pressureForecastHpa's hours drawn as a forecast), the visibility and the thunderstorm risk; and the model, its run, its age and how far it is trusted. Each group lists its facts as label–value rows, and the groups sit side by side as the width allows.

Wind is in knots, waves in meters, visibility in meters below a nautical mile and in nautical miles above, pressure in hPa and rain in millimeters; the bar writes each unit from the catalog and each number in the crew's language. Directions are degrees true and name the bearing the wind, waves or swell come from, as the forecast does; the arrow points the other way, the way the water and air move. The vessel's own limits come in as limits, and a value at or over one is drawn amber with a warning shape and a short word ("Reef", "Heavy sea", "Fog", "Fast fall"), named in full for a screen reader. Amber stays on that value: the explanation beside it is plain text. Beside the control the summary is two groups, the weather now and the outlook; the outlook shares the line at its right end where the bar is wide enough and takes its own line from the left where it is not. The summary is a status region; the control and the detail sit outside it, so opening the detail is not announced and a weather update does not read the control out.

01The line answers the glanceWind, sky and sea now are on the line the crew sees without a tap. Everything that needs reading, a week of days or a model run, waits behind the control.
02Limits are the vessel's ownA day is marked against the reef wind and heavy-sea height the crew set for this vessel, not against a generic gale, so the mark means this boat will reef.
03Missing is never calmA source without waves says so, and a day without a value shows a dash named No data. An empty cell never reads as flat water or no wind.

Examples

Collapsed

The bar starts closed. The line carries the glance; "Forecast details" opens the rest. Pass defaultOpen to start open, or open with onOpenChange to hold the state yourself.

steady 14 kn, gusts 19 knPartly cloudy17 °C1016 hPaSea 1.4 m+6H 12–18 kn+12H 15–22 kn
The bar as it first draws: the weather now on one line and a control that opens the forecast, sea state and model run under it.
Code
import { WeatherBar } from "@bosunkit/components/weather-bar";
import type { ReactElement } from "react";

export const description: string =
  "The bar as it first draws: the weather now on one line and a control that opens the forecast, sea state and " +
  "model run under it.";

export default function Collapsed(): ReactElement {
  return (
    <WeatherBar
      current={{
        description: "Partly cloudy",
        wind: "14 kn, gusts 19 kn",
        conditions: "steady",
        weatherCode: 2,
        windFromDeg: 250,
      }}
      forecasts={[
        { period: "+6H", wind: "12–18 kn", conditions: "" },
        { period: "+12H", wind: "15–22 kn", conditions: "" },
      ]}
      temperature="17 °C"
      pressure="1016 hPa"
      seaStateText="1.4 m"
      days={[
        {
          day: "Mon 13",
          weatherCode: 2,
          cloudCoverPct: 40,
          windMinKn: 12,
          windMaxKn: 18,
          windFromDeg: 250,
          gustMaxKn: 23,
        },
        {
          day: "Tue 14",
          weatherCode: 3,
          cloudCoverPct: 90,
          windMinKn: 15,
          windMaxKn: 22,
          windFromDeg: 240,
          gustMaxKn: 29,
        },
      ]}
      sea={{ waveHeightM: 1.4, wavePeriodS: 7, waveFromDeg: 255, douglas: 4 }}
      model={{ name: "ICON-EU", ageHours: 2, confidence: 0.85 }}
      limits={{ reefWindKn: 20, heavySeaM: 3 }}
    />
  );
}

Expanded

Opened, the detail lays its groups side by side as the width allows and stacks them on a phone. A quiet forecast draws no amber at all.

easing 8 kn, gusts 12 knClear21 °C1022 hPaSea 0.6 m
Forecast by day
DaySkyWindWaves
Sat 18Clear sky5% cloudWMO 06–12 kn, gusts 160.7 m
Sun 19Mainly clear25% cloudWMO 18–14 kn, gusts 190.9 m
Sea state
Waves
0.6 m, 5 s
Swell
0.8 m, 11 s
Douglas scale
3 · Slight
Weather
Pressure
Rising 0.4 hPa in 3 hr
Visibility
9.9 NM
Forecast model
Model
ICON-EU
Run
06:00 UTC
Age
1 hr old
Confidence
90%
A settled day at anchor in the Ría de Arousa, opened: two quiet days ahead, a slight sea, a steady barometer and a fresh model run.
Code
import { WeatherBar } from "@bosunkit/components/weather-bar";
import type { ReactElement } from "react";

export const description: string =
  "A settled day at anchor in the Ría de Arousa, opened: two quiet days ahead, a slight sea, a steady barometer and a " +
  "fresh model run.";

export default function Expanded(): ReactElement {
  return (
    <WeatherBar
      current={{
        description: "Clear",
        wind: "8 kn, gusts 12 kn",
        conditions: "easing",
        weatherCode: 0,
        windFromDeg: 20,
      }}
      temperature="21 °C"
      pressure="1022 hPa"
      seaStateText="0.6 m"
      days={[
        {
          day: "Sat 18",
          weatherCode: 0,
          cloudCoverPct: 5,
          windMinKn: 6,
          windMaxKn: 12,
          windFromDeg: 20,
          gustMaxKn: 16,
          waveHeightM: 0.7,
        },
        {
          day: "Sun 19",
          weatherCode: 1,
          cloudCoverPct: 25,
          windMinKn: 8,
          windMaxKn: 14,
          windFromDeg: 350,
          gustMaxKn: 19,
          waveHeightM: 0.9,
        },
      ]}
      sea={{
        waveHeightM: 0.6,
        wavePeriodS: 5,
        waveFromDeg: 15,
        swellHeightM: 0.8,
        swellPeriodS: 11,
        swellFromDeg: 300,
        douglas: 3,
      }}
      pressureTrendHpa3h={0.4}
      visibilityM={18500}
      model={{ name: "ICON-EU", run: "06:00 UTC", ageHours: 1.5, confidence: 0.9 }}
      limits={{ reefWindKn: 20, heavySeaM: 3 }}
      defaultOpen
    />
  );
}

Passage forecast with a gale day

days gives a row per day. The daily forecast carries the strongest wind and gust, the prevailing direction and the weather code; the lightest wind, the highest waves, the rain, the cloud cover, the lowest visibility and the hourly hours series are the caller's reduction of each day's hours. The gale day reaches the reef limit of 20 kn and the heavy-sea limit of 3 m, each value carries its own mark, and the trends show when in the day the wind and the sea build.

building 16 kn, gusts 22 knCloudy
Forecast by day
DaySkyWindWavesRain
Tue 14Overcast90% cloudWMO 314–19 kn, gusts 252.2 m1.2 mm
Wed 15Heavy rain100% cloudWMO 6527–39 kn, gusts 51Reef5.1 mHeavy sea21.7 mm
Thu 16Slight rain showers65% cloudWMO 8015–19 kn, gusts 303.6 mHeavy sea4.4 mm
Fri 17Partly cloudy40% cloudWMO 210–15 kn, gusts 202.0 m0.0 mm
Sea state
Waves
2.2 m, 8 s
Douglas scale
4 · Moderate
Four days of a Biscay passage against the vessel's reef limit of 20 kn and heavy-sea limit of 3 m: Wednesday's gale reaches both, Thursday's sea stays over the heavy-sea limit after the wind drops.
Code
import { WeatherBar } from "@bosunkit/components/weather-bar";
import type { ReactElement } from "react";

export const description: string =
  "Four days of a Biscay passage against the vessel's reef limit of 20 kn and heavy-sea limit of 3 m: Wednesday's " +
  "gale reaches both, Thursday's sea stays over the heavy-sea limit after the wind drops.";

export default function PassageGale(): ReactElement {
  return (
    <WeatherBar
      current={{
        description: "Cloudy",
        wind: "16 kn, gusts 22 kn",
        conditions: "building",
        weatherCode: 3,
        windFromDeg: 240,
      }}
      days={[
        {
          day: "Tue 14",
          weatherCode: 3,
          cloudCoverPct: 90,
          windMinKn: 14,
          windMaxKn: 19,
          windFromDeg: 240,
          gustMaxKn: 25,
          waveHeightM: 2.2,
          precipitationMm: 1.2,
          hours: {
            windKn: [14, 15, 16, 17, 19, 18, 17, 16],
            gustKn: [19, 20, 22, 23, 25, 24, 23, 22],
            waveHeightM: [1.9, 2.0, 2.0, 2.1, 2.2, 2.2, 2.2, 2.2],
          },
        },
        {
          day: "Wed 15",
          weatherCode: 65,
          cloudCoverPct: 100,
          windMinKn: 27,
          windMaxKn: 39,
          windFromDeg: 220,
          gustMaxKn: 51,
          waveHeightM: 5.1,
          precipitationMm: 21.7,
          hours: {
            windKn: [27, 30, 34, 37, 39, 38, 34, 30],
            gustKn: [36, 40, 45, 49, 51, 50, 45, 40],
            waveHeightM: [2.6, 3.2, 3.9, 4.5, 5.1, 5.0, 4.6, 4.1],
          },
        },
        {
          day: "Thu 16",
          weatherCode: 80,
          cloudCoverPct: 65,
          windMinKn: 15,
          windMaxKn: 19,
          windFromDeg: 285,
          gustMaxKn: 30,
          waveHeightM: 3.6,
          precipitationMm: 4.4,
          hours: {
            windKn: [19, 18, 17, 17, 16, 16, 15, 15],
            gustKn: [30, 28, 26, 25, 24, 23, 22, 21],
            waveHeightM: [3.6, 3.5, 3.4, 3.3, 3.2, 3.1, 3.0, 2.9],
          },
        },
        {
          day: "Fri 17",
          weatherCode: 2,
          cloudCoverPct: 40,
          windMinKn: 10,
          windMaxKn: 15,
          windFromDeg: 300,
          gustMaxKn: 20,
          waveHeightM: 2.0,
          precipitationMm: 0,
          hours: {
            windKn: [15, 14, 13, 12, 12, 11, 10, 10],
            gustKn: [20, 19, 18, 17, 16, 15, 14, 14],
            waveHeightM: [2.0, 1.9, 1.8, 1.7, 1.6, 1.5, 1.5, 1.4],
          },
        },
      ]}
      sea={{ waveHeightM: 2.2, wavePeriodS: 8, waveFromDeg: 245, douglas: 4 }}
      limits={{ reefWindKn: 20, heavySeaM: 3 }}
      defaultOpen
    />
  );
}

Rough sea

sea gives the significant wave height with its period and direction, the swell apart from it, and the Douglas code bosund derives from the height. Waves at the heavy-sea limit are marked like a forecast day.

steady 27 kn, gusts 38 knRainSea 3.8 m
Sea state
Waves
3.8 m, 9 sHeavy sea
Swell
3.1 m, 14 s
Douglas scale
5 · Rough
A rough sea off Cape Finisterre: 3.8 m of wind sea from the southwest on a long northwesterly swell, over the vessel's heavy-sea limit of 3 m.
Code
import { WeatherBar } from "@bosunkit/components/weather-bar";
import type { ReactElement } from "react";

export const description: string =
  "A rough sea off Cape Finisterre: 3.8 m of wind sea from the southwest on a long northwesterly swell, over the " +
  "vessel's heavy-sea limit of 3 m.";

export default function RoughSea(): ReactElement {
  return (
    <WeatherBar
      current={{
        description: "Rain",
        wind: "27 kn, gusts 38 kn",
        conditions: "steady",
        weatherCode: 63,
        windFromDeg: 215,
      }}
      seaStateText="3.8 m"
      sea={{
        waveHeightM: 3.8,
        wavePeriodS: 9,
        waveFromDeg: 220,
        swellHeightM: 3.1,
        swellPeriodS: 14,
        swellFromDeg: 300,
        douglas: 5,
      }}
      limits={{ heavySeaM: 3 }}
      defaultOpen
    />
  );
}

Stale model run

model names the model, the run time with its reference and the age in hours. From 12 hours, where the forecast analysis starts calling a forecast stale, the age turns amber and the bar asks for a newer forecast.

12 kn, gusts 17 knCloudy
Forecast by day
DaySkyWind
Thu 16Mostly cloudy85% cloud10–16 kn, gusts 21
Fri 17Partly cloudy40% cloud12–18 kn, gusts 24
Sea state

No wave data: this forecast source publishes none.

Forecast model
Model
GFS
Run
06:00 UTC
Age
31 hr oldOut of date. Download a newer forecast before relying on it.
Confidence
30%
Two days out of satellite range: the last GRIB file is a GFS run 31 hours old, so the bar calls it out of date and the confidence has fallen to 30%. A GRIB file carries no weather code, so the sky comes from its cloud cover.
Code
import { WeatherBar } from "@bosunkit/components/weather-bar";
import type { ReactElement } from "react";

export const description: string =
  "Two days out of satellite range: the last GRIB file is a GFS run 31 hours old, so the bar calls it out of date " +
  "and the confidence has fallen to 30%. A GRIB file carries no weather code, so the sky comes from its cloud cover.";

export default function StaleModel(): ReactElement {
  return (
    <WeatherBar
      current={{
        description: "Cloudy",
        wind: "12 kn, gusts 17 kn",
        conditions: "",
        cloudCoverPct: 80,
        windFromDeg: 60,
      }}
      days={[
        { day: "Thu 16", cloudCoverPct: 85, windMinKn: 10, windMaxKn: 16, windFromDeg: 60, gustMaxKn: 21 },
        { day: "Fri 17", cloudCoverPct: 40, windMinKn: 12, windMaxKn: 18, windFromDeg: 45, gustMaxKn: 24 },
      ]}
      model={{ name: "GFS", run: "06:00 UTC", ageHours: 31, confidence: 0.3 }}
      defaultOpen
    />
  );
}

Thunderstorm risk

convection carries CAPE in J/kg, the lifted index and the analysis's verdict, which also weighs the convective inhibition, so the bar writes the verdict rather than judging CAPE alone. Visibility under the fog limit is marked, and the pressure trend shows its direction with an icon.

gusty 9 kn, gusts 34 knThunderstorms
Sea state

No wave data: this forecast source publishes none.

Weather
Pressure
Falling 1.8 hPa in 3 hr
Visibility
900 mFog
Thunderstorms
Thunderstorms forecastCAPE 2800 J/kg, lifted index -6.1
A hot afternoon off the Costa Brava: thunderstorms in the forecast, 2800 J/kg of CAPE, a lifted index of -6.1, and visibility under the fog limit in the rain.
Code
import { WeatherBar } from "@bosunkit/components/weather-bar";
import type { ReactElement } from "react";

export const description: string =
  "A hot afternoon off the Costa Brava: thunderstorms in the forecast, 2800 J/kg of CAPE, a lifted index of -6.1, " +
  "and visibility under the fog limit in the rain.";

export default function Thunderstorm(): ReactElement {
  return (
    <WeatherBar
      current={{
        description: "Thunderstorms",
        wind: "9 kn, gusts 34 kn",
        conditions: "gusty",
        weatherCode: 95,
        windFromDeg: 160,
      }}
      pressureTrendHpa3h={-1.8}
      visibilityM={900}
      convection={{ capeJPerKg: 2800, liftedIndex: -6.1, risk: "thunderstorm" }}
      limits={{ pressureDropHpa3h: 3, fogVisibilityM: 1000 }}
      defaultOpen
    />
  );
}

No sea state from the source

A source without marine data leaves the waves out. The bar leaves out the waves column and says the source publishes none. MET Norway also publishes no visibility and no weather code, so the sky cell writes the cloud cover alone and draws the sky from it.

6 kn, gusts 11 knOvercast
Forecast by day
DaySkyWindRain
Mon 13Overcast96% cloud4–9 kn, gusts 120.2 mm
Tue 14Partly cloudy55% cloud8–14 kn, gusts 191.6 mm
Sea state

No wave data: this forecast source publishes none.

Forecast model
Model
MET Norway
Age
2 hr old
A forecast from MET Norway, which publishes no waves, no visibility and no weather code: the waves column is left out, the sky comes from the cloud cover, and the sea state says the source has none rather than showing an empty sea as calm.
Code
import { WeatherBar } from "@bosunkit/components/weather-bar";
import type { ReactElement } from "react";

export const description: string =
  "A forecast from MET Norway, which publishes no waves, no visibility and no weather code: the waves column is " +
  "left out, the sky comes from the cloud cover, and the sea state says the source has none rather " +
  "than showing an empty sea as calm.";

export default function NoSeaState(): ReactElement {
  return (
    <WeatherBar
      current={{
        description: "Overcast",
        wind: "6 kn, gusts 11 kn",
        conditions: "",
        cloudCoverPct: 100,
        windFromDeg: 200,
      }}
      days={[
        {
          day: "Mon 13",
          cloudCoverPct: 96,
          windMinKn: 4,
          windMaxKn: 9,
          windFromDeg: 200,
          gustMaxKn: 12,
          precipitationMm: 0.2,
          hours: {
            windKn: [4, 5, 6, 8, 9, 8, 6, 5],
            gustKn: [7, 8, 9, 11, 12, 11, 9, 8],
            precipitationMm: [0, 0, 0.1, 0.1, 0, 0, 0, 0],
            cloudCoverPct: [100, 100, 98, 95, 95, 92, 94, 96],
          },
        },
        {
          day: "Tue 14",
          cloudCoverPct: 55,
          windMinKn: 8,
          windMaxKn: 14,
          windFromDeg: 230,
          gustMaxKn: 19,
          precipitationMm: 1.6,
          hours: {
            windKn: [8, 9, 11, 13, 14, 13, 11, 10],
            gustKn: [12, 13, 15, 18, 19, 18, 16, 14],
            precipitationMm: [0, 0.2, 0.6, 0.5, 0.2, 0.1, 0, 0],
            cloudCoverPct: [80, 70, 60, 55, 45, 40, 45, 50],
          },
        },
      ]}
      model={{ name: "MET Norway", ageHours: 2 }}
      defaultOpen
    />
  );
}

Controlled

13 kn, gusts 18 knPartly cloudy
The detail held by the caller: a button outside the bar opens it too, and the bar's own control reports through onOpenChange.
Code
import { Button } from "@bosunkit/components/button";
import { WeatherBar } from "@bosunkit/components/weather-bar";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";

export const description: string =
  "The detail held by the caller: a button outside the bar opens it too, and the bar's own control reports through " +
  "onOpenChange.";

export default function Controlled(): ReactElement {
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: "var(--space-sm)", width: "100%" }}>
      <div>
        <Button
          onClick={(): void => {
            setOpen(!open);
          }}
        >
          {open ? "Hide the forecast" : "Show the forecast"}
        </Button>
      </div>
      <WeatherBar
        current={{ description: "Partly cloudy", wind: "13 kn, gusts 18 kn", conditions: "", weatherCode: 2 }}
        days={[
          {
            day: "Sat 18",
            weatherCode: 2,
            cloudCoverPct: 40,
            windMinKn: 10,
            windMaxKn: 16,
            windFromDeg: 270,
            gustMaxKn: 21,
          },
        ]}
        open={open}
        onOpenChange={setOpen}
      />
    </div>
  );
}

Single line

With nothing to open, the bar is the single line: no control and no detail.

building 14kn gusts 19Partly cloudy17°C1012hPaSea 1.2m+6H 12-18kn+12H 15-22kn
The weather now with its air temperature, pressure and sea state, then the wind forecast for the next 6 and 12 hours.
Code
import { WeatherBar } from "@bosunkit/components/weather-bar";
import type { ReactElement } from "react";

export const description: string =
  "The weather now with its air temperature, pressure and sea state, then the wind forecast for the next 6 and 12 hours.";

export default function Forecast(): ReactElement {
  return (
    <WeatherBar
      current={{ description: "Partly cloudy", wind: "14kn gusts 19", conditions: "building" }}
      forecasts={[
        { period: "+6H", wind: "12-18kn", conditions: "" },
        { period: "+12H", wind: "15-22kn", conditions: "" },
      ]}
      temperature="17°C"
      pressure="1012hPa"
      seaStateText="1.2m"
    />
  );
}

Data sources

Each forecast field the bar shows, and the providers in packages/bosun/weather/providers that fill it. A field a provider lacks stays out of the props, and the bar leaves its cell, column or group out.

FieldOpen-MeteoMET NorwayStormglassLocal GRIBHRRR
Wind, direction, gustsyesyesyesyes (gust if the file has it)yes
Pressureyesyesyesyesno
Temperature, humidity, dew pointyesyesyestemperature, humidityno
Total cloud coveryesyesyesif the file has itno
Precipitationyesyes (next hour)yesif the file has itno
Visibilityyesnoyesnono
WMO weather codehourly and dailynononono
CAPE, inhibition, lifted indexyesnonoif the file has themno
Waves and swellmarine APInoyesif the file has themno
Daily summary, sunrise and sunset, UVyesnohourly UV onlynono

No provider publishes the probability of precipitation or cloud cover split into low, middle and high, so the bar has no place for them. The ensemble and the model spread add the wind's spread across members and models.

Props

PropTypeDefaultDescription
currentrequired{ description: string; wind: string; conditions: string; weatherCode?: number | undefined; cloudCoverPct?: number | undefined; windFromDeg?: number | undefined; }–The conditions now.
forecastsreadonly WeatherForecast[] | undefined–Forecast periods after now, in order.
temperaturestring | undefined–The air temperature, formatted by the caller.
pressurestring | undefined–The barometric pressure, formatted by the caller.
seaStateTextstring | undefined–The sea state, formatted by the caller; the bar puts the word "Sea" before it.
daysreadonly WeatherDay[] | undefined–The forecast by day, in order, shown in the detail. With any detail the bar draws a control that opens it under the summary line; with none it stays a single line.
seaWeatherSeaState | undefined–The sea state now, shown in the detail. Left out, the detail says the forecast source publishes no waves, as a source without marine data (MET Norway, a GRIB file without waves) leaves them out rather than sending zero.
pressureTrendHpa3hnumber | undefined–The pressure change over the last 3 hours, in hPa, negative when falling. The SSE weather event does not carry it; the caller takes it from the bus barometer's rate or the hourly forecast pressures.
pressureHourlyHpareadonly (number | null)[] | undefined–The barometric pressure by the hour up to now, in hPa, oldest first, drawn as a small trend after the 3-hour change: the bus barometer's record, or the forecast's surface_pressure_hpa for the hours already past. Its last value is the pressure now.
pressureForecastHpareadonly number[] | undefined–The forecast's surface_pressure_hpa for the hours after now, in hPa, drawn after pressureHourlyHpa as a forecast, so a coming fall never reads as one already measured.
visibilityMnumber | undefined–The visibility, in meters (visibility_m on the forecast hour), written in meters below a nautical mile and in nautical miles above, rounded down so it never reads better than it is. Not on the SSE weather event yet.
convectionWeatherConvection | undefined–The thunderstorm risk now. Its CAPE and lifted index are on the forecast hour; not on the SSE event yet.
modelWeatherModelRun | undefined–The model run behind the forecast, its age and how far it is trusted.
limitsWeatherLimits | undefined–The vessel's limits the forecast days and the sea state are marked against; a limit left out marks nothing.
openboolean | undefined–Whether the detail is open, for a caller that keeps the state; leave it out and the bar keeps its own.
defaultOpenboolean | undefinedfalseWhether the detail starts open when the bar keeps its own state.
onOpenChange((open: boolean) => void) | undefined–Called with the state the control asks for when the crew opens or closes the detail.
classNamestring | undefined–Classes added to the bar's own.
Do
16 kn, gusts 22 knCloudy
Forecast by day
DayWindWaves
Tue 1414–19 kn, gusts 252.2 m
Wed 1527–39 kn, gusts 51Reef5.1 mHeavy sea
Sea state

No wave data: this forecast source publishes none.

Pass the vessel's own limits. The gale day carries its amber marks, and the crew see at once which day this boat reefs.
Don't
16 kn, gusts 22 knCloudy
Forecast by day
DayWindWaves
Tue 1414–19 kn, gusts 252.2 m
Wed 1527–39 kn, gusts 515.1 m
Sea state

No wave data: this forecast source publishes none.

Don't leave the limits out. Every day draws the same, and a 39 kn day reads like a 19 kn one until someone reads the numbers.
Do
6 knFog
Forecast by day
DayWind
Mon 139 kn
Sea state

No wave data: this forecast source publishes none.

Leave sea out when the source has no waves. The bar says the source publishes none.
Don't
6 knFog
Sea state
Waves
0.0 m
Douglas scale
0 · Calm (glassy)
Don't pass a zero height for waves nobody forecast. It reads as a calm sea the forecast never claimed.