# Line Chart Forecast

A solid line for actual values that continues as a dashed forecast over a shaded future range.

Line Chart Forecast is an open-source chart from suiss/ui for React, React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.

Source: https://ui.suiss.app/docs/charts/line-chart-forecast

**React**

**Example**

```tsx title="components/charts/line-chart-forecast.tsx"
"use client"

import { CartesianGrid, Line, LineChart, ReferenceArea, XAxis } from "recharts"

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  type ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart"

const actual = [12, 14, 13, 17, 19, 22, 24, 23, 26]
const forecast = [26, 28, 30, 33]

const chartData = Array.from({ length: 12 }, (_, index) => ({
  month: new Intl.DateTimeFormat("en-US", { month: "short" }).format(
    new Date(2026, index, 1)
  ),
  actual: actual[index] ?? null,
  forecast: index >= 8 ? forecast[index - 8] : null,
}))

const chartConfig = {
  actual: { label: "Actual", color: "var(--chart-1)" },
  forecast: { label: "Forecast", color: "var(--chart-1)" },
} satisfies ChartConfig

export function LineChartForecast() {
  return (
    <Card variant="filled" size="sm">
      <CardHeader>
        <CardTitle>Actual and forecast</CardTitle>
        <CardDescription>The dashed line is the forecast</CardDescription>
      </CardHeader>
      <CardContent>
        <ChartContainer
          config={chartConfig}
          className="aspect-auto h-48 w-full"
        >
          <LineChart
            accessibilityLayer
            data={chartData}
            margin={{ top: 10, right: 12, left: 12, bottom: 0 }}
          >
            <CartesianGrid vertical={false} strokeDasharray="2 4" />
            <XAxis
              dataKey="month"
              tickLine={false}
              axisLine={false}
              tickMargin={8}
              interval={2}
            />
            <ChartTooltip content={<ChartTooltipContent indicator="line" />} />
            <ReferenceArea
              x1="Sep"
              x2="Dec"
              fill="var(--secondary)"
              fillOpacity={0.6}
            />
            <Line
              dataKey="actual"
              type="monotone"
              stroke="var(--color-actual)"
              strokeWidth={2.5}
              dot={false}
            />
            <Line
              dataKey="forecast"
              type="monotone"
              stroke="var(--color-forecast)"
              strokeWidth={2.5}
              strokeDasharray="5 5"
              dot={false}
            />
          </LineChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```

Actual values drawn as a solid line that continues as a dashed forecast over a shaded future period. Use it whenever you show projections next to history and need readers to tell them apart.

## Installation

### CLI

```bash title="Terminal"
npx shadcn@latest add @esuiss/line-chart-forecast
```

Works in any project with a `components.json`. See [Installation](https://ui.suiss.app/docs/installation#add-components) for the one-line registry setup. Or use the suiss/ui CLI:

```bash title="Terminal"
npx @esuiss/ui@latest add charts line-chart-forecast
```

### Manual

1. Install the dependencies:

```bash title="Terminal"
npm install recharts
```

2. Add the pieces it builds on: [Card](https://ui.suiss.app/docs/components/card), [Chart](https://ui.suiss.app/docs/components/chart).

3. Copy the source into `components/charts/line-chart-forecast.tsx`:

```tsx title="components/charts/line-chart-forecast.tsx"
"use client"

import { CartesianGrid, Line, LineChart, ReferenceArea, XAxis } from "recharts"

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  type ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart"

const actual = [12, 14, 13, 17, 19, 22, 24, 23, 26]
const forecast = [26, 28, 30, 33]

const chartData = Array.from({ length: 12 }, (_, index) => ({
  month: new Intl.DateTimeFormat("en-US", { month: "short" }).format(
    new Date(2026, index, 1)
  ),
  actual: actual[index] ?? null,
  forecast: index >= 8 ? forecast[index - 8] : null,
}))

const chartConfig = {
  actual: { label: "Actual", color: "var(--chart-1)" },
  forecast: { label: "Forecast", color: "var(--chart-1)" },
} satisfies ChartConfig

export function LineChartForecast() {
  return (
    <Card variant="filled" size="sm">
      <CardHeader>
        <CardTitle>Actual and forecast</CardTitle>
        <CardDescription>The dashed line is the forecast</CardDescription>
      </CardHeader>
      <CardContent>
        <ChartContainer
          config={chartConfig}
          className="aspect-auto h-48 w-full"
        >
          <LineChart
            accessibilityLayer
            data={chartData}
            margin={{ top: 10, right: 12, left: 12, bottom: 0 }}
          >
            <CartesianGrid vertical={false} strokeDasharray="2 4" />
            <XAxis
              dataKey="month"
              tickLine={false}
              axisLine={false}
              tickMargin={8}
              interval={2}
            />
            <ChartTooltip content={<ChartTooltipContent indicator="line" />} />
            <ReferenceArea
              x1="Sep"
              x2="Dec"
              fill="var(--secondary)"
              fillOpacity={0.6}
            />
            <Line
              dataKey="actual"
              type="monotone"
              stroke="var(--color-actual)"
              strokeWidth={2.5}
              dot={false}
            />
            <Line
              dataKey="forecast"
              type="monotone"
              stroke="var(--color-forecast)"
              strokeWidth={2.5}
              strokeDasharray="5 5"
              dot={false}
            />
          </LineChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```

4. Update the import paths to match your project.

## Usage

```tsx
import { LineChartForecast } from "@/components/charts/line-chart-forecast"
```

```tsx
<LineChartForecast />
```

Fill the `actual` and `forecast` arrays; the forecast should start with the last actual value so the two lines meet. Move the shaded period by changing `x1` and `x2` on the reference area to match your first and last forecast labels. Both series share one color in `chartConfig`.

## Notes

Since actual and forecast use the same color, the dash pattern and shading carry the distinction; the tooltip also names each series. At the handover point the tooltip lists both values.

**React Native, SwiftUI, Jetpack Compose**

![Preview](https://ui.suiss.app/previews/charts-line--forecast.light.webp)

```tsx title="uniwind/components/charts/line-chart-forecast.tsx"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  CartesianGrid,
  type ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  Line,
  LineChart,
  ReferenceArea,
  XAxis,
} from "@/components/ui/chart"

const MONTHS = [
  "Jan",
  "Feb",
  "Mar",
  "Apr",
  "May",
  "Jun",
  "Jul",
  "Aug",
  "Sep",
  "Oct",
  "Nov",
  "Dec",
]

const actual = [12, 14, 13, 17, 19, 22, 24, 23, 26]
const forecast = [26, 28, 30, 33]

const chartData = MONTHS.map((month, index) => ({
  month,
  actual: actual[index] ?? null,
  forecast: index >= 8 ? (forecast[index - 8] ?? null) : null,
}))

const chartConfig = {
  actual: { label: "Actual", color: "chart-1" },
  forecast: { label: "Forecast", color: "chart-1" },
} satisfies ChartConfig

export function LineChartForecast() {
  return (
    <Card variant="filled" size="sm">
      <CardHeader>
        <CardTitle>Actual and forecast</CardTitle>
        <CardDescription>The dashed line is the forecast</CardDescription>
      </CardHeader>
      <CardContent>
        <ChartContainer
          config={chartConfig}
          accessibilityLabel="Actual and forecast"
          className="h-48 w-full"
        >
          <LineChart
            data={chartData}
            margin={{ top: 10, right: 12, left: 12, bottom: 0 }}
          >
            <CartesianGrid vertical={false} strokeDasharray="2 4" />
            <XAxis dataKey="month" tickMargin={8} interval={2} />
            <ChartTooltip content={<ChartTooltipContent indicator="line" />} />
            <ReferenceArea
              x1="Sep"
              x2="Dec"
              fill="secondary"
              fillOpacity={0.6}
            />
            <Line dataKey="actual" type="monotone" strokeWidth={2.5} />
            <Line
              dataKey="forecast"
              type="monotone"
              strokeWidth={2.5}
              strokeDasharray="5 5"
            />
          </LineChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```

```swift title="swift/Charts/LineChartForecast.swift"
import Charts
import SwiftUI

private let actual: [Double] = [12, 14, 13, 17, 19, 22, 24, 23, 26]
private let forecast: [Double] = [26, 28, 30, 33]
private let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

private let chartConfig: ChartConfig = [
  "actual": .init("Actual", color: EsuissColor.chart1),
  "forecast": .init("Forecast", color: EsuissColor.chart1),
]

private func value(_ key: String, _ index: Int) -> Double? {
  if key == "actual" { return actual.indices.contains(index) ? actual[index] : nil }
  return index >= 8 ? forecast[index - 8] : nil
}

public struct LineChartForecast: View {
  @State private var selection: Int?

  public init() {}

  public var body: some View {
    Card(.filled, size: .sm) {
      CardHeader("Actual and forecast", description: "The dashed line is the forecast")
      CardContent {
        ChartContainer(config: chartConfig) {
          ChartAnimator { progress in
            Chart {
              RectangleMark(xStart: .value("Month", 8), xEnd: .value("Month", 11))
                .foregroundStyle(EsuissColor.secondary.opacity(0.6))
              ForEach(chartConfig.keys, id: \.self) { key in
                ForEach(months.indices, id: \.self) { index in
                  if let point = value(key, index) {
                    LineMark(x: .value("Month", index), y: .value("Value", point), series: .value("Series", key))
                      .interpolationMethod(.monotone)
                      .lineStyle(StrokeStyle(lineWidth: 2.5, lineCap: .round, lineJoin: .round, dash: key == "forecast" ? [5, 5] : []))
                      .foregroundStyle(chartConfig.color(key))
                  }
                }
                if let selection, let point = value(key, selection) {
                  PointMark(x: .value("Month", selection), y: .value("Value", point))
                    .symbolSize(64)
                    .foregroundStyle(chartConfig.color(key))
                }
              }
              .chartReveal(progress, x: 0...11)
            }
            .chartXScale(domain: 0...11, range: .plotDimension(padding: 12))
            .chartXAxis { ChartAxis.x(values: [0, 3, 6, 9]) { months[$0] } }
            .chartYAxis { ChartAxis.grid() }
            .chartTooltip(
              x: Array(months.indices),
              labels: months,
              selection: $selection,
              accessibilityLabel: "Actual and forecast",
              items: { index in
                chartConfig.keys.compactMap { key in value(key, index).map { ChartTooltipItem(key, value: $0) } }
              }
            ) { payload in
              ChartTooltipContent(payload, indicator: .line)
            }
          }
        }
        .frame(height: 192)
      }
    }
  }
}
```

```kt title="kotlin/charts/LineChartForecast.kt"
package com.esuiss.ui.charts

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Card
import com.esuiss.ui.components.CardContent
import com.esuiss.ui.components.CardHeader
import com.esuiss.ui.components.CardSize
import com.esuiss.ui.components.CardVariant
import com.esuiss.ui.components.ChartConfig
import com.esuiss.ui.components.ChartConfigItem
import com.esuiss.ui.components.ChartContainer
import com.esuiss.ui.components.ChartCurve
import com.esuiss.ui.components.ChartMargin
import com.esuiss.ui.components.ChartTooltipContent
import com.esuiss.ui.components.ChartTooltipIndicator
import com.esuiss.ui.components.LineChart
import com.esuiss.ui.theme.EsuissTheme

private class LineForecastRow(val month: String, val actual: Double?, val forecast: Double?)

private val actual = listOf(12.0, 14.0, 13.0, 17.0, 19.0, 22.0, 24.0, 23.0, 26.0)

private val forecast = listOf(26.0, 28.0, 30.0, 33.0)

private val chartData = listOf("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
    .mapIndexed { index, month ->
        LineForecastRow(month, actual.getOrNull(index), if (index >= 8) forecast[index - 8] else null)
    }

private val chartConfig = ChartConfig(
    "actual" to ChartConfigItem("Actual") { chart1 },
    "forecast" to ChartConfigItem("Forecast") { chart1 },
)

@Composable
fun LineChartForecast(modifier: Modifier = Modifier) {
    val colors = EsuissTheme.colors
    Card(modifier, variant = CardVariant.Filled, size = CardSize.Sm) {
        CardHeader("Actual and forecast", description = "The dashed line is the forecast")
        CardContent {
            ChartContainer(
                chartConfig,
                Modifier
                    .fillMaxWidth()
                    .height(192.dp),
            ) {
                LineChart(
                    chartData,
                    margin = ChartMargin(top = 10.dp, right = 12.dp, bottom = 0.dp, left = 12.dp),
                    accessibilityLabel = "Actual and forecast",
                ) {
                    CartesianGrid(strokeDasharray = listOf(2f, 4f))
                    XAxis(label = { it.month }, tickMargin = 8.dp, interval = 2)
                    ChartTooltip { ChartTooltipContent(indicator = ChartTooltipIndicator.Line) }
                    ReferenceArea(8, 11, fill = colors.secondary, fillOpacity = 0.6f)
                    Line("actual", value = { it.actual }, curve = ChartCurve.Monotone, strokeWidth = 2.5.dp)
                    Line(
                        "forecast",
                        value = { it.forecast },
                        curve = ChartCurve.Monotone,
                        strokeWidth = 2.5.dp,
                        strokeDasharray = listOf(5f, 5f),
                    )
                }
            }
        }
    }
}
```

Two series share one axis: `actual` fills the first nine months and `forecast` continues from September, drawn with `strokeDasharray`. A `ReferenceArea` shades September to December to mark the forecast period. Missing values are `null`, so each line stops where its data ends.

## Installation

**React Native**

```bash title="Terminal"
npx shadcn@latest add @esuiss-rn/line-chart-forecast
```

See [Installation](https://ui.suiss.app/docs/installation) for the `components.json` setup. Or use the suiss/ui CLI:

```bash title="Terminal"
npx @esuiss/ui@latest add line-chart-forecast
```

**SwiftUI**

```bash title="Terminal"
npx @esuiss/ui@latest add line-chart-forecast --platform swift
```

**Jetpack Compose**

```bash title="Terminal"
npx @esuiss/ui@latest add line-chart-forecast --platform kotlin
```

The CLI also adds what it builds on: [Card](https://ui.suiss.app/docs/components/card), [Chart](https://ui.suiss.app/docs/components/chart).

### Manual

**React Native**

**Uniwind**

1. Add the pieces it builds on: [Card](https://ui.suiss.app/docs/components/card), [Chart](https://ui.suiss.app/docs/components/chart).

2. Copy the source into `components/charts/line-chart-forecast.tsx`:

- `uniwind/components/charts/line-chart-forecast.tsx`: shown above

3. Update the import paths to match your project.

**NativeWind**

1. Add the pieces it builds on: [Card](https://ui.suiss.app/docs/components/card), [Chart](https://ui.suiss.app/docs/components/chart).

2. Copy the source into `components/charts/line-chart-forecast.tsx`:

```tsx title="nativewind/components/charts/line-chart-forecast.tsx"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  CartesianGrid,
  type ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  Line,
  LineChart,
  ReferenceArea,
  XAxis,
} from "@/components/ui/chart"

const MONTHS = [
  "Jan",
  "Feb",
  "Mar",
  "Apr",
  "May",
  "Jun",
  "Jul",
  "Aug",
  "Sep",
  "Oct",
  "Nov",
  "Dec",
]

const actual = [12, 14, 13, 17, 19, 22, 24, 23, 26]
const forecast = [26, 28, 30, 33]

const chartData = MONTHS.map((month, index) => ({
  month,
  actual: actual[index] ?? null,
  forecast: index >= 8 ? (forecast[index - 8] ?? null) : null,
}))

const chartConfig = {
  actual: { label: "Actual", color: "chart-1" },
  forecast: { label: "Forecast", color: "chart-1" },
} satisfies ChartConfig

export function LineChartForecast() {
  return (
    <Card variant="filled" size="sm">
      <CardHeader>
        <CardTitle>Actual and forecast</CardTitle>
        <CardDescription>The dashed line is the forecast</CardDescription>
      </CardHeader>
      <CardContent>
        <ChartContainer
          config={chartConfig}
          accessibilityLabel="Actual and forecast"
          className="h-48 w-full"
        >
          <LineChart
            data={chartData}
            margin={{ top: 10, right: 12, left: 12, bottom: 0 }}
          >
            <CartesianGrid vertical={false} strokeDasharray="2 4" />
            <XAxis dataKey="month" tickMargin={8} interval={2} />
            <ChartTooltip content={<ChartTooltipContent indicator="line" />} />
            <ReferenceArea
              x1="Sep"
              x2="Dec"
              fill="secondary"
              fillOpacity={0.6}
            />
            <Line dataKey="actual" type="monotone" strokeWidth={2.5} />
            <Line
              dataKey="forecast"
              type="monotone"
              strokeWidth={2.5}
              strokeDasharray="5 5"
            />
          </LineChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```

3. Update the import paths to match your project.

**StyleSheet**

1. Add the pieces it builds on: [Card](https://ui.suiss.app/docs/components/card), [Chart](https://ui.suiss.app/docs/components/chart).

2. Copy the source into `components/charts/line-chart-forecast.tsx`:

```tsx title="stylesheet/components/charts/line-chart-forecast.tsx"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  CartesianGrid,
  type ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  Line,
  LineChart,
  ReferenceArea,
  XAxis,
} from "@/components/ui/chart"

const MONTHS = [
  "Jan",
  "Feb",
  "Mar",
  "Apr",
  "May",
  "Jun",
  "Jul",
  "Aug",
  "Sep",
  "Oct",
  "Nov",
  "Dec",
]

const actual = [12, 14, 13, 17, 19, 22, 24, 23, 26]
const forecast = [26, 28, 30, 33]

const chartData = MONTHS.map((month, index) => ({
  month,
  actual: actual[index] ?? null,
  forecast: index >= 8 ? (forecast[index - 8] ?? null) : null,
}))

const chartConfig = {
  actual: { label: "Actual", color: "chart-1" },
  forecast: { label: "Forecast", color: "chart-1" },
} satisfies ChartConfig

export function LineChartForecast() {
  return (
    <Card variant="filled" size="sm">
      <CardHeader>
        <CardTitle>Actual and forecast</CardTitle>
        <CardDescription>The dashed line is the forecast</CardDescription>
      </CardHeader>
      <CardContent>
        <ChartContainer
          config={chartConfig}
          accessibilityLabel="Actual and forecast"
          style={{ height: 192 }}
        >
          <LineChart
            data={chartData}
            margin={{ top: 10, right: 12, left: 12, bottom: 0 }}
          >
            <CartesianGrid vertical={false} strokeDasharray="2 4" />
            <XAxis dataKey="month" tickMargin={8} interval={2} />
            <ChartTooltip content={<ChartTooltipContent indicator="line" />} />
            <ReferenceArea
              x1="Sep"
              x2="Dec"
              fill="secondary"
              fillOpacity={0.6}
            />
            <Line dataKey="actual" type="monotone" strokeWidth={2.5} />
            <Line
              dataKey="forecast"
              type="monotone"
              strokeWidth={2.5}
              strokeDasharray="5 5"
            />
          </LineChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```

3. Update the import paths to match your project.

**SwiftUI**

1. Target iOS 17 or later.

2. Copy the theme files every view uses:

- `swift/Theme/Theme.swift`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/swiftui/Sources/EsuissUI/Theme/Theme.swift)

- `swift/Theme/Tokens.swift`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/swiftui/Sources/EsuissUI/Theme/Tokens.swift)

3. Copy the source and every file it uses:

- `swift/Charts/LineChartForecast.swift`: shown above

- `swift/Components/Card.swift`: see [Card](https://ui.suiss.app/docs/components/card.md)

- `swift/Components/Chart.swift`: see [Chart](https://ui.suiss.app/docs/components/chart.md)

- `swift/Components/Haptics.swift`: see [Haptics](https://ui.suiss.app/docs/components/haptics.md)

- `swift/Components/Item.swift`: see [Item](https://ui.suiss.app/docs/components/item.md)

- `swift/Components/Separator.swift`: see [Separator](https://ui.suiss.app/docs/components/separator.md)

- `swift/Components/TextVariant.swift`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/swiftui/Sources/EsuissUI/Components/TextVariant.swift)

4. Add the files to your app target.

**Jetpack Compose**

1. Set `minSdk` to 24 or higher and add the dependencies:

```kotlin title="build.gradle.kts"
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
```

2. Copy the theme files every view uses:

- `kotlin/theme/Theme.kt`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/kotlin/ui/src/main/kotlin/com/esuiss/ui/theme/Theme.kt)

- `kotlin/theme/Tokens.kt`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/kotlin/ui/src/main/kotlin/com/esuiss/ui/theme/Tokens.kt)

3. Copy the source and every file it uses:

- `kotlin/charts/LineChartForecast.kt`: shown above

- `kotlin/components/Card.kt`: see [Card](https://ui.suiss.app/docs/components/card.md)

- `kotlin/components/Chart.kt`: see [Chart](https://ui.suiss.app/docs/components/chart.md)

- `kotlin/components/Content.kt`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/kotlin/ui/src/main/kotlin/com/esuiss/ui/components/Content.kt)

- `kotlin/components/FormSupport.kt`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/kotlin/ui/src/main/kotlin/com/esuiss/ui/components/FormSupport.kt)

- `kotlin/components/Haptics.kt`: see [Haptics](https://ui.suiss.app/docs/components/haptics.md)

- `kotlin/components/Icon.kt`: see [Icon](https://ui.suiss.app/docs/components/icon.md)

4. Update the `com.esuiss.ui` package names to match your project.

## Usage

```tsx
import { LineChartForecast } from "@/components/charts/line-chart-forecast"
```

```tsx
<LineChartForecast />
```

Replace the `actual` and `forecast` arrays; the forecast starts at the index set in `chartData`. Move the `ReferenceArea` `x1` and `x2` to match your forecast range. The component takes no props.

