Design techniques

A Power BI month picker for every KPI: the disconnected table

One month picker drives four KPI cards, their change on the previous month and a ten-month trend, without filtering the page. A disconnected table and three measures.

9 min read

The Production Line Efficiency report: a month picker at the top right drives four KPI cards and a ten-month trend

At the top right of the Production Line Efficiency report there is a row of months with a scroll arrow. Pick one, and four things move together: the four KPI cards show that month, the "vs. Last Period" line under each one compares it with the month before, and the trend chart slides to show the ten months that end there.

Nothing else on the page is filtered by it. Click a column of the trend, and the cards keep the month you picked.

This article shows how that picker works, why a plain month slicer can't do it, and how to build it in Power BI Desktop with three measures and a table that has no relationship at all.

The Production Line Efficiency report: the month picker at the top right, the four KPI cards with their change on the previous month, and the trend
The report opens on February 2026, as the breadcrumb under the title says: the cards and the trend follow that month.
The filter row, with the month picker at its right end, above the four KPI cards: OEE, defect rate, production yield and downtime, each with its change, AVG, GAP and RANK
The month picker is the last filter of the row. The four cards under it read the picked month.

Why a normal month slicer falls short

The obvious build is a slicer on the calendar, DimDate[YearMonth], single select. It gets the cards right, and almost everything else wrong:

  • The trend collapses. The slicer filters every visual on the page, so the trend chart shows one column: the month you picked.
  • Edit interactions only half helps. Turn the slicer off for the trend, and the trend shows the whole calendar, two years, instead of the months that lead to your pick.
  • The chart fights the slicer. Click a column of the trend, and the cross-filter reaches the cards through the same calendar. The cards now show the clicked month, while the slicer still says another one.

The report needs a picker that tells the measures which month to show, without filtering anything by itself.

The idea: a picker that filters nothing

The picker is a slicer on its own small table, MonthPicker, with one row per month and no relationship to the model. A slicer on a disconnected table filters only that table, and no visual on the page reads it directly. So the selection does nothing until a measure asks for it.

Three kinds of measures ask:

  1. The Selected measures of the cards read the picked month and compute the KPI for it.
  2. The Variation MoM measures compute the same KPI for the month before, and return the difference.
  3. A flag measure, Is In Trailing 10M, tells the trend which months to keep.
Month pickerMonthPicker[YearMonth] = "2026-02"DimDaterelated to every fact tableno relationshipSELECTEDVALUEOEE SelectedREMOVEFILTERS, then the monthon DimDatethe value on the cardOEE Variation MoMthe month minus the one beforeon DimDatethe line under the cardIs In Trailing 10M1 for the ten months up to iton DimDatethe trend, filtered on 1four KPI cards: February 2026MayJunJulAugSepOctNovDecJanFebtrend: the ten months that end at the picked monthThe other slicers (plant, machine type, shift, category) still filter everything as usual.

Step 1: the picker table

MonthPicker is a calculated table built from the calendar, so it always has the same months:

DAX
MonthPicker =
SELECTCOLUMNS (
    SUMMARIZE ( DimDate, DimDate[Year], DimDate[MonthNumber], DimDate[YearMonth] ),
    "YearMonth", DimDate[YearMonth],
    "SortKey", DimDate[Year] * 100 + DimDate[MonthNumber]
)

YearMonth is text (2026-02), so sort it by SortKey (Column tools, Sort by column) and hide SortKey. Don't draw any relationship: in the model view, the table stands alone.

The slicer is a classic slicer on MonthPicker[YearMonth], in Tile style (horizontal), with the header off. A slicer that is narrower than its tiles shows scroll arrows, and that is the row of months at the top right of the report. Turn Single select on in its selection settings: the measures expect exactly one month.

Step 2: the KPI for the picked month

Every card reads a Selected measure. Here is the OEE one:

DAX
OEE Selected =
VAR Sel = SELECTEDVALUE ( MonthPicker[YearMonth] )
RETURN
    CALCULATE ( [OEE], REMOVEFILTERS ( DimDate ), DimDate[YearMonth] = Sel )

Two things do the work:

  • SELECTEDVALUE returns the picked month as text, because the slicer filtered MonthPicker down to one row.
  • REMOVEFILTERS ( DimDate ) throws away whatever date filter the card received from elsewhere, then the next argument sets the only one that counts: the picked month. This is what keeps the cards still when someone clicks a column of the trend.

The same three lines wrap the defect rate, the production yield and the downtime hours. Only the inner measure changes.

The other slicers of the page (plant, machine type, shift, product category) still filter the cards as usual, because REMOVEFILTERS only touches the calendar.

Step 3: the change on the previous month, by hand

Under each value, "+4.9% vs. Last Period" compares the picked month with the one before. Time intelligence functions like PREVIOUSMONTH shift the dates already in the filter context, and the card has none: the month lives in the picker. So the measure builds the previous month itself:

DAX
OEE Variation MoM =
VAR Sel       = SELECTEDVALUE ( MonthPicker[YearMonth] )
VAR SelStart  = CALCULATE ( MIN ( DimDate[Date] ), ALL ( DimDate ), DimDate[YearMonth] = Sel )
VAR PrevEnd   = SelStart - 1
VAR PrevStart = DATE ( YEAR ( PrevEnd ), MONTH ( PrevEnd ), 1 )
VAR Cur       = [OEE Selected]
VAR Prev      = CALCULATE ( [OEE], REMOVEFILTERS ( DimDate ), DimDate[Date] >= PrevStart && DimDate[Date] <= PrevEnd )
RETURN
    Cur - Prev

The first day of the picked month, minus one day, is the last day of the previous month, and its first day follows. January works too: the day before 1 January 2026 is 31 December 2025.

The format string does the rest: +0.0%;-0.0%;0.0% writes the sign in front of the number. For the downtime, the format is +0.0;-0.0;0.0 hours, and the result reads "+1.6 hours".

One detail is easy to miss: OEE, defect rate and yield are already percentages, so Cur - Prev is a change in points. "+4.9%" here means 4.9 points more than last month, not 4.9 percent more.

Step 4: a trend that follows the picker

The trend shows production as columns and OEE as a line, for the ten months that end at the picked month. A visual can't take a "last ten months" filter relative to a slicer it isn't connected to, so a measure says, for each month on the axis, whether it belongs:

DAX
Is In Trailing 10M =
VAR Sel       = SELECTEDVALUE ( MonthPicker[YearMonth] )
VAR EndDate   = CALCULATE ( MAX ( DimDate[Date] ), ALL ( DimDate ), DimDate[YearMonth] = Sel )
VAR StartDate = EOMONTH ( EndDate, -10 ) + 1
VAR RowDate   = MAX ( DimDate[Date] )
RETURN
    IF ( RowDate >= StartDate && RowDate <= EndDate, 1 )

Add it to the trend's visual-level filters, as "is 1". On each column of the axis, MAX ( DimDate[Date] ) is the last day of that month, so the measure keeps the months from ten months before the pick up to the pick itself. Change the month, and the window slides.

The same flag pattern gives any window you like: EOMONTH ( EndDate, -12 ) + 1 for twelve months, or a start of year for year to date.

Slicer on the calendarDimDate[YearMonth] = "2026-02"the trend: one columnDisconnected month pickerMonthPicker[YearMonth] = "2026-02"the trend: May 2025 to February 2026

AVG, GAP and RANK under each card

Each card ends with three small numbers: the daily average, the gap to a target, and the rank of the plant among the five. In the report they read like this:

DAX
AVG OEE        = AVERAGEX ( VALUES ( DimDate[Date] ), [OEE] )
GAP OEE        = [OEE] - 0.80
RANK OEE Plant = RANKX ( ALL ( DimPlant[PlantName] ), [OEE], , DESC, Dense )

The defect rate and the downtime rank in ascending order, because lower is better there: RANKX ( ..., ASC, Dense ). The targets are fixed in each measure: 80% OEE, 3% defects, 93% yield, 18 hours of downtime.

Notice that these three use [OEE], not [OEE Selected]. As shipped, they read the whole period in view, not the picked month: the average over every day, the gap and the rank over the same span. That is a fair reading for a benchmark line. If you want them tied to the picker, it is one word each:

DAX
GAP OEE Selected        = [OEE Selected] - 0.80
RANK OEE Plant Selected = RANKX ( ALL ( DimPlant[PlantName] ), [OEE Selected], , DESC, Dense )

The breadcrumb that follows

Under the title, "Dashboard / Plant Detroit / Packaging Line / 2026-02" is a card on one more measure, so the reader always knows what the numbers are about:

DAX
Dashboard Subtitle =
"Dashboard / "
    & SELECTEDVALUE ( DimPlant[PlantName], "All Plants" ) & " / "
    & SELECTEDVALUE ( DimMachine[MachineType], "All Machines" ) & " / "
    & SELECTEDVALUE ( MonthPicker[YearMonth], "All Periods" )

What to watch

  • Single select, always. With two months picked, or none, SELECTEDVALUE returns blank and every card goes empty. Keep Single select on, or give the measures a default: SELECTEDVALUE ( MonthPicker[YearMonth], MAX ( MonthPicker[YearMonth] ) ) falls back to the latest month.
  • Save the report with a month picked. The selection is saved with the report, so readers open it on a month, not on blank cards.
  • The filter pane won't explain it. A reader who opens the filter pane sees no date filter on the cards. The breadcrumb is there for that reason.
  • The tiles scroll. With 24 months in a narrow slicer, the picked month can sit out of view when the report opens. The breadcrumb under the title always shows it.
  • Other pages. To reuse the picked month on another page, sync the slicer (View, Sync slicers). It is the same table, so the same measures work there.

Take it apart

The full project is free under the MIT License: the report in PBIR, the model in TMDL with its 57 measures, and the data in CSV. Open it in Power BI Desktop, select a card, and follow its measure.

With your consent, we use Google Analytics cookies to measure audience. Cookies needed for sign-in and payment are always on. Learn more