Has the Overton Window Shifted?
Acceptance Through Moderation in the Dutch Tweede Kamer (2016–2026)
Verdict: The Overton window widened. More right-wing positions became politically acceptable. But the mechanism was right-wing moderation, not centrist conversion. The effect may be temporary.
Introduction
Did the PVV’s November 2023 election victory shift the Dutch Overton window? The conventional narrative is clear: a far-right party won the largest share of seats, entered government for the first time in July 2024, and the political center responded by adopting more right-wing positions. Centrist parties, according to this story, moved right to accommodate the new political reality.
The data tells a different story.
Using 29,591 Tweede Kamer motions with full MP-level vote records, Procrustes-aligned SVD spatial analysis, and 2D extremity scoring (stijl-extremiteit vs materiële impact), we find that the Overton window widened: centrist support for right-wing motions rose from 25% to 51%, while centrist support for non-right-wing motions rose modestly (58%→62%, +3.5 pp). What changed was the behavior of right-wing parties: they filed more motions, with milder content, framed in centrist-friendly language. Centrist voting support rose from 0.251 to 0.507 (Cohen’s d = +0.65), but centrists did not become more right-wing. They stayed ideologically left while voting more permissively on proposals that had become less materially consequential.
This article presents the evidence across three indicators: centrist voting support, SVD spatial divergence, and 2D extremity decomposition, and examines the mechanisms through which right-wing motions gained centrist support.
About Stemwijzer
Stemwijzer is a data-driven political compass built from real parliamentary voting records. It analyzes 29,591 motions from the Tweede Kamer (2016 to 2026), each with per-MP vote records, to compute latent political dimensions using Singular Value Decomposition (SVD). Users vote on real motions and find which MPs match their positions, not based on party manifestos or campaign promises, but on how representatives actually voted.
The platform tracks party positions across 11 annual windows using Procrustes-aligned SVD, allowing year-over-year comparison of spatial drift. Every motion has been scored on two independent dimensions of extremity: stijl-extremiteit (stylistic rhetoric, 1 to 5) and materiële impact (material policy consequence, 1 to 5), manually validated with 75% auditor agreement.
The Overton analysis presented here builds on this infrastructure. The same SVD compass, extremity scores, and vote-level data that power the Stemwijzer Explorer dashboard drive these findings.
Methodology
Right-wing motion classification. We identify right-wing motions using a hybrid keyword + voting-pattern classifier. A seed set of right-wing keywords (vuurwerkverbod, stikstof, nareis, etc.) is expanded through an iterative keyword-vote loop. Motions whose voting pattern correlates with right-wing party support are flagged, their distinctive terms extracted, and the keyword set refined. The final classifier identifies 3,030 motions as right-wing across 2016 to 2026, with full voting records for centrist support computation.
2D extremity scoring. Every motion in the database (29,591) has been scored by an LLM on two dimensions: stijl-extremiteit (stylistic extremity: inflammatory language, rhetorical framing) and materiële impact (material impact: rights restriction, institutional change, resource reallocation), each on a 1 to 5 scale. Manual audit of 117 stratified motions achieved 75% agreement. The two dimensions are only moderately correlated (Pearson r = 0.43 for all motions, r = 0.47 for right-wing), confirming they capture distinct phenomena. Excluding ~6,000 placeholder motions scored (1,1) by default, r drops to 0.34. The dimensions are even more independent than the headline figure suggests.
Strict centrist definition. We define the centrist bloc narrowly as four parties (D66, CDA, ChristenUnie, NSC), excluding VVD and BBB, which lean center-right and would inflate centrist support mechanically. A strict opposition-only filter further controls for coalition effects by excluding motions whose lead submitter belongs to the governing coalition.
SVD alignment. Party positions are computed via SVD on annual voting matrices and aligned using chained Procrustes orthogonal rotation followed by global PCA, placing all annual party positions in a common 2D reference frame. Centrist and right-wing centers of gravity are computed as the mean of party-level axis scores within each bloc.
Code
yearly_agg = con.execute("""
SELECT
year,
AVG(centrist_support_strict) AS mean_cs,
STDDEV(centrist_support_strict) AS std_cs,
COUNT(*) AS n
FROM right_wing_motions
WHERE classified = TRUE
GROUP BY year ORDER BY year
""").fetchdf()
yearly_cat = con.execute("""
SELECT
year,
category,
AVG(centrist_support_strict) AS cs,
COUNT(*) AS n
FROM right_wing_motions
WHERE classified = TRUE AND year >= 2017 AND category IS NOT NULL
GROUP BY year, category
ORDER BY year, category
""").fetchdf()
categories_list = sorted(yearly_cat["category"].unique())
cat_colors = ["#66C2A5", "#FC8D62", "#8DA0CB", "#E78AC3", "#A6D854",
"#FFD92F", "#E5C494", "#B3B3B3", "#1E88E5", "#D81B60"]
cat_labels = {
"asiel/vreemdelingen": "Asiel & Vreemdelingen",
"landbouw/natuur": "Landbouw & Natuur",
"veiligheid/justitie": "Veiligheid & Justitie",
"zorg/gezondheid": "Zorg & Gezondheid",
"economie": "Economie",
"energie/klimaat": "Energie & Klimaat",
"buitenland/europa": "Buitenland & Europa",
"onderwijs/wetenschap": "Onderwijs & Wetenschap",
"verkeer/infrastructuur": "Verkeer & Infrastructuur",
"overig": "Overig",
}
fig1 = go.Figure()
fig1.add_trace(go.Scatter(
x=yearly_agg["year"], y=yearly_agg["mean_cs"],
mode="lines+markers", name="All right-wing",
line=dict(color="#002366", width=3),
marker=dict(size=8),
error_y=dict(
type="data",
array=1.96 * yearly_agg["std_cs"] / np.sqrt(yearly_agg["n"]),
visible=True, thickness=0.8, width=2
),
))
for i, cat in enumerate(categories_list):
cat_data = yearly_cat[yearly_cat["category"] == cat]
if cat_data.empty:
continue
fig1.add_trace(go.Scatter(
x=cat_data["year"], y=cat_data["cs"],
mode="lines+markers",
name=cat_labels.get(cat, cat),
line=dict(color=cat_colors[i % len(cat_colors)], width=2, dash="dash"),
marker=dict(size=6),
visible=False,
))
pre = yearly_agg[yearly_agg["year"] < BREAK_YEAR]
post = yearly_agg[yearly_agg["year"] >= BREAK_YEAR]
fig1.add_hline(
y=pre["mean_cs"].mean(),
line_dash="dot", line_color="#90CAF9",
annotation_text=f"Pre-2024 mean ({pre['mean_cs'].mean():.3f})",
)
fig1.add_hline(
y=post["mean_cs"].mean(),
line_dash="dot", line_color="#1E88E5",
annotation_text=f"Post-2024 mean ({post['mean_cs'].mean():.3f})",
)
fig1.add_vline(
x=BREAK_YEAR - 0.5, line_dash="dot", line_color="black", opacity=0.5,
)
buttons = [
dict(label="All right-wing", method="update",
args=[{"visible": [True] + [False] * len(categories_list)}])
]
for i, cat in enumerate(categories_list):
vis = [True] + [False] * len(categories_list)
vis[i + 1] = True
buttons.append(dict(
label=cat_labels.get(cat, cat),
method="update",
args=[{"visible": vis}],
))
fig1.update_layout(
title="Centrist Support (Strict) for Right-Wing Motions",
xaxis=dict(title="Year", dtick=1),
yaxis=dict(title="Centrist Support (fraction of parties)", range=[0, 1.1]),
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
template="plotly_white", height=450,
updatemenus=[dict(
type="dropdown", direction="down", showactive=True,
buttons=buttons, x=1.05, y=1.15,
)],
)
fig1.show()Centrist Support for Right-Wing Motions Over Time (2016–2026)
Indicator 1: Centrist Voting Support
The cleanest signal is in how centrist parties voted on right-wing motions. Average support rose from 0.251 pre-2024 to 0.507 post-2024, a Cohen’s d of +0.65, a medium-to-large effect. The breakpoint is unmistakably 2024.
This is not a coalition artifact. After the Schoof cabinet formed in July 2024, PVV entered government, which could mechanically inflate support for its own motions. When we restrict analysis to opposition-only right-wing motions (lead submitter outside the governing coalition), the effect is larger: d = +0.85, with support jumping from 0.270 to 0.543. Centrist parties are genuinely more willing to support right-wing motions than they were before 2024, even when those motions come from opposition right-wing parties.
The gradient across extremity levels persists: centrists still differentiate by how radical a motion is, but at a consistently higher baseline. High-extremity motions gained proportionally more support than mild motions, consistent with genuine tolerance expansion rather than compositional shift.
Pass rate is useless as an indicator. Dutch parliament passes 96%+ of motions in both periods. With near-zero variance, pass rate cannot register a shift of any magnitude. Centrist support among MPs is the meaningful behavioral measure.
Code
gravity = con.execute("""
SELECT
CASE WHEN r.year < 2024 THEN 'pre-2024' ELSE 'post-2024' END AS period,
e.materiele_impact AS m_level,
AVG(r.centrist_support_strict) AS cs,
COUNT(*) AS n
FROM right_wing_motions r
JOIN extremity_scores_all e ON r.motion_id = e.motion_id
WHERE r.classified = TRUE AND e.materiele_impact IS NOT NULL
GROUP BY period, m_level ORDER BY period, m_level
""").fetchdf()
levels = sorted(gravity["m_level"].unique())
pre_vals = gravity[gravity["period"] == "pre-2024"].set_index("m_level")
post_vals = gravity[gravity["period"] == "post-2024"].set_index("m_level")
fig2 = go.Figure()
fig2.add_trace(go.Bar(
name="Pre-2024",
x=[f"M={l}" for l in levels],
y=[pre_vals.loc[l, "cs"] if l in pre_vals.index else 0 for l in levels],
marker_color="#90CAF9",
text=[f"N={int(pre_vals.loc[l, 'n'])}" if l in pre_vals.index else "" for l in levels],
textposition="outside",
offset=0,
))
fig2.add_trace(go.Bar(
name="Post-2024",
x=[f"M={l}" for l in levels],
y=[post_vals.loc[l, "cs"] if l in post_vals.index else 0 for l in levels],
marker_color="#1E88E5",
text=[f"N={int(post_vals.loc[l, 'n'])}" if l in post_vals.index else "" for l in levels],
textposition="outside",
offset=0.3,
))
fig2.update_layout(
title="Gravity-Controlled Centrist Support by Material Impact",
xaxis=dict(title="Material Impact Level"),
yaxis=dict(title="Centrist Support", range=[0, 1.1]),
barmode="group",
template="plotly_white", height=450,
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
)
fig2.show()Gravity-Controlled Centrist Support by Material Impact Level, Pre vs Post 2024
The gravity-controlled chart shows a consistent pattern: the centrist support shift is real at every material impact level. From M=1 (mild procedural adjustments, +0.292) to M=5 (systemic overhaul, +0.122), centrist support rose across the board. The largest absolute gains came from the middle range (M=2: +0.205, M=3: +0.219, M=4: +0.267), where most right-wing motions cluster.
Comparing right-wing motions against all other motions confirms the shift is specific: right-wing centrist support rose by +0.236, while non-right-wing motions remained essentially flat (−0.006). This is a right-wing-specific phenomenon, not a general parliamentary trend.
Domain Decomposition
Right-wing motions span ten policy categories, and the 2024 centrist support shift was not uniform across them. Every category gained centrist support, but the magnitudes vary dramatically, from a jump of +0.39 for climate and energy to a modest +0.08 for education and science.
Leading the shift are energie/klimaat (+0.392), buitenland/europa (+0.364), and economie (+0.342), domains where right-wing parties adopted centrist-friendly framing and right-wing governments in other European countries provided template policies. At the other end, onderwijs/wetenschap (+0.081) and veiligheid/justitie (+0.138) barely moved, consistent with their roles as high-consensus domains where the window was already wide.
Code
delta = con.execute("""
SELECT
category,
AVG(CASE WHEN year < 2024 THEN centrist_support_strict END) as pre_cs,
AVG(CASE WHEN year >= 2024 THEN centrist_support_strict END) as post_cs,
COUNT(*) as n
FROM right_wing_motions
WHERE classified = TRUE AND year >= 2017 AND category IS NOT NULL
GROUP BY category
ORDER BY (AVG(CASE WHEN year >= 2024 THEN centrist_support_strict END)
- AVG(CASE WHEN year < 2024 THEN centrist_support_strict END)) DESC
""").fetchdf()
delta["d"] = delta["post_cs"] - delta["pre_cs"]
fig7 = go.Figure()
fig7.add_trace(go.Bar(
x=delta["d"],
y=delta["category"],
orientation="h",
marker_color=["#2ECC71" if d > 0 else "#E74C3C" for d in delta["d"]],
text=[f"{d:.3f}" for d in delta["d"]],
textposition="outside",
cliponaxis=False,
))
fig7.update_layout(
title="Pre/Post 2024 Centrist Support Delta by Category",
xaxis=dict(title="Delta (post-2024 − pre-2024)", range=[-0.05, 0.48]),
yaxis=dict(title="", autorange="reversed"),
template="plotly_white", height=450,
margin=dict(l=200),
)
fig7.show()Pre/Post 2024 Centrist Support Delta by Policy Category
The pattern shows a political gradient. Domains tied to European integration (buitenland/europa) and climate action (energie/klimaat), where center-right governments abroad provided cover, saw the largest shifts. Domestic social domains (zorg/gezondheid, onderwijs/wetenschap) were largely insulated. The migration domain (asiel/vreemdelingen), central to the Overton narrative, ranked seventh with a +0.210 delta, substantial but not exceptional. What matters is its durability rather than its size: migration centrist support sustained its gains through 2025 while non-migration domains reverted.
Indicator 2: Spatial Divergence
If centrists are voting more with right-wing motions, one might expect ideological convergence: centrist parties drifting rightward on the SVD compass. Procrustes-aligned SVD analysis shows the opposite.
Code
svd = con.execute("""
SELECT * FROM overton_svd_center ORDER BY window_id
""").fetchdf()
fig3 = go.Figure()
fig3.add_trace(go.Scatter(
x=svd["centrist_mean_axis1"], y=svd["centrist_mean_axis2"],
mode="lines+markers+text", name="Centrist center",
line=dict(color="#00A36C", width=2),
marker=dict(size=8, symbol="circle"),
text=svd["window_id"], textposition="top center",
))
fig3.add_trace(go.Scatter(
x=svd["right_mean_axis1"], y=svd["right_mean_axis2"],
mode="lines+markers+text", name="Right-wing center",
line=dict(color="#002366", width=2),
marker=dict(size=8, symbol="square"),
text=svd["window_id"], textposition="bottom center",
))
fig3.update_layout(
title="SVD Party Centers of Gravity Over Time",
xaxis=dict(title="Axis 1 (Economic)"),
yaxis=dict(title="Axis 2 (Cultural)"),
template="plotly_white", height=500,
legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99),
hovermode="closest",
)
fig3.show()SVD Trajectories: Centrist vs Right-Wing Centers of Gravity (2016–2026)
Between the first and last annual windows:
- Centrists moved left on both axes: −0.223 on the economic axis (more welfare-oriented) and +0.081 on the cultural axis (more kosmopolitisch).
- Right-wing parties moved further right culturally: −0.065 on the cultural axis (more nationalist).
- The cultural distance between centrists and right-wing parties widened from 0.282 to 0.428 (+0.146).
This is spatial divergence, not convergence. Centrist parties did not become right-wing. They became marginally more left-wing in their overall voting patterns. The centrist center of gravity moved toward welfare and cosmopolitanism, while right-wing parties moved further into the nationalist corner.
Why this makes sense with the voting data: The SVD captures the full voting record, including all motions, not just the ones centrists supported. Right-wing parties continued filing high-impact motions that centrists opposed, while simultaneously filing a much larger volume of milder motions centrists supported. The net effect on SVD was centrist-left divergence: the extreme motions (still opposed by centrists) dominated the voting structure, while the increase in milder centrist-supported motions added volume without shifting party positions. This is “acceptance without conversion.” Centrists vote more with right-wing motions while moving further from them ideologically.
Indicator 3: Content Moderation
The original single-dimensional extremity score showed no increase post-2024 (d = −0.09, from 2.21 to 2.15). If the Overton window shifted, why didn’t right-wing motions become more radical?
The answer lies in what the single score measured. Two-dimensional rescoring of all 29,591 motions shows that stylistic extremity and material impact are only moderately correlated (r = 0.43). When tracked separately over time, they tell different stories.
Code
extremity_2d = con.execute("""
SELECT
r.year,
AVG(e.stijl_extremiteit) AS mean_stijl,
AVG(e.materiele_impact) AS mean_mat,
COUNT(*) AS n
FROM right_wing_motions r
JOIN extremity_scores_all e ON r.motion_id = e.motion_id
WHERE r.classified = TRUE AND r.year >= 2019
GROUP BY r.year ORDER BY r.year
""").fetchdf()
all_stijl, all_mat = con.execute("""
SELECT AVG(stijl_extremiteit), AVG(materiele_impact)
FROM extremity_scores_all
""").fetchone()
fig4 = make_subplots(
rows=1, cols=2,
subplot_titles=("Stylistic Extremity (Stijl)", "Material Impact (Materieel)"),
shared_yaxes=False,
)
fig4.add_trace(
go.Scatter(
x=extremity_2d["year"], y=extremity_2d["mean_stijl"],
mode="lines+markers", name="Right-wing",
line=dict(color="#6A1B9A", width=3),
marker=dict(size=8),
),
row=1, col=1,
)
fig4.add_hline(
y=all_stijl, line_dash="dot", line_color="#9E9E9E",
annotation_text=f"All motions ({all_stijl:.2f})",
row=1, col=1,
)
fig4.add_trace(
go.Scatter(
x=extremity_2d["year"], y=extremity_2d["mean_mat"],
mode="lines+markers", name="Right-wing",
line=dict(color="#E53935", width=3),
marker=dict(size=8),
),
row=1, col=2,
)
fig4.add_hline(
y=all_mat, line_dash="dot", line_color="#9E9E9E",
annotation_text=f"All motions ({all_mat:.2f})",
row=1, col=2,
)
fig4.update_layout(
title="2D Extremity Decomposition: Stijl vs Materieel",
template="plotly_white", height=400,
showlegend=False,
)
fig4.update_xaxes(title="Year", dtick=1)
fig4.update_yaxes(title="Score (1–5)", range=[0.5, 4])
fig4.show()2D Extremity Over Time: Stijl vs Materieel (Right-Wing Motions, 2019–2026)
| Dimension | Pre-2024 Mean | Post-2024 Mean | Δ |
|---|---|---|---|
| Stylistic extremity | 1.875 | 1.744 | −0.131 |
| Material impact | 2.786 | 2.450 | −0.336 |
| Gap (M−S) | 0.911 | 0.706 | −0.205 |
Both dimensions decreased: stylistic extremity (−0.131) and material impact (−0.336). A Wilcoxon signed-rank test comparing yearly mean stylistic vs yearly mean material scores confirms the dimensions systematically differ (W = 0.0, n = 10 yearly pairs, p = 0.002). The gap between the two dimensions narrowed from 0.911 to 0.706. Right-wing motions became both less rhetorically hostile AND less substantively impactful.
Compared to all motions, right-wing motions score higher on both dimensions: stijl +0.47, materieel +0.54. The masking rate, restrained language paired with high material impact, is 9.7% (S≤2, M≥4) or 13.5% (S=1, M≥3) for right-wing motions vs 3.5% for all motions. Right-wing proposals disproportionately use procedural language to advance consequential policy.
Mechanisms of Influence
If centrists didn’t become right-wing, how did right-wing motions gain their support? A systematic classification of 150 post-2024 motions (stratified by centrist support level) identifies the dominant pathways.
Code
mechanisms = [
"Procedureel/technisch",
"Consensus framing",
"Gerichte restrictie",
"Institutioneel/rechtsstatelijk",
"Symbolisch/declaratoir",
"Welzijn/dienstverlening",
"Lokaal/regionaal",
"Coalitie-afstemming",
"Crisisrespons",
"Systeemontmanteling",
]
high_support = [24, 18, 13, 7, 4, 3, 3, 2, 1, 0]
low_support = [9, 6, 21, 19, 5, 1, 1, 0, 0, 13]
fig5 = go.Figure()
fig5.add_trace(go.Bar(
name="High-support (CS > 0.5)",
x=mechanisms, y=high_support,
marker_color="#1E88E5",
))
fig5.add_trace(go.Bar(
name="Low-support (CS ≤ 0.5)",
x=mechanisms, y=low_support,
marker_color="#90CAF9",
))
fig5.update_layout(
title="Mechanism Classification: High-Support vs Low-Support Post-2024",
xaxis=dict(title="Mechanism", tickangle=45),
yaxis=dict(title="Count"),
barmode="group",
template="plotly_white", height=450,
legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99),
)
fig5.show()Mechanism Distribution: High-Support vs Low-Support Post-2024 Motions
The contrast between high- and low-support post-2024 motions is sharp.
High-support motions (CS > 0.5) are dominated by procedural/technical framing (32%), consensus framing appealing to shared values (24%), and targeted restriction rather than blanket bans (17%). Institutional challenges and system dismantling are notably absent.
Low-support motions (CS ≤ 0.5) are dominated by targeted restriction (28%), institutional challenges (25%), and system dismantling (17%). Zero system dismantling motions achieved high centrist support.
Consensus framing is significantly more common in high-support motions (24%) than low-support (8%): χ²(1) = 6.00, p = 0.014. Exploratory evidence suggests consensus framing drives centrist support. Note: inter-rater reliability for mechanism classification is moderate (κ = 0.41). These patterns are exploratory and require taxonomy refinement.
At the party level, the shift is not uniform. JA21 is the primary driver, with a +0.203 CS shift and the only volume + support gains combination. PVV entered government and filed fewer, milder motions. FVD remains structurally shunned. Its motions consistently fail to gain centrist support regardless of content.
Temporal Dynamics
Quarterly analysis across 33 quarters (2016-Q2 through 2026-Q1) replaces the binary pre/post-2024 comparison with a continuous trajectory that shows the exact timing and trajectory of the shift.
Code
quarterly = con.execute("""
SELECT
EXTRACT(YEAR FROM m.date) AS y,
CEIL(EXTRACT(MONTH FROM m.date) / 3.0) AS q,
AVG(r.centrist_support_strict) AS cs,
COUNT(*) AS n,
STDDEV(r.centrist_support_strict) AS std_cs
FROM right_wing_motions r
JOIN motions m ON r.motion_id = m.id
WHERE r.classified = TRUE AND m.date IS NOT NULL
GROUP BY y, q ORDER BY y, q
""").fetchdf()
quarterly["label"] = quarterly["y"].astype(int).astype(str) + "-Q" + quarterly["q"].astype(int).astype(str)
inflection_idx = quarterly[(quarterly["y"].astype(int) == 2024) & (quarterly["q"].astype(int) == 1)].index
peak_idx = quarterly[(quarterly["y"].astype(int) == 2024) & (quarterly["q"].astype(int) == 4)].index
latest_idx = quarterly[(quarterly["y"].astype(int) == 2026) & (quarterly["q"].astype(int) == 1)].index
fig6 = go.Figure()
fig6.add_trace(go.Scatter(
x=quarterly["label"], y=quarterly["cs"],
mode="lines+markers",
line=dict(color="#002366", width=3),
marker=dict(size=6),
error_y=dict(
type="data",
array=1.96 * quarterly["std_cs"] / np.sqrt(quarterly["n"]),
visible=True, thickness=0.6, width=1.5,
),
name="Centrist Support",
))
for idx in [inflection_idx, peak_idx, latest_idx]:
if len(idx) > 0:
i = idx[0]
fig6.add_annotation(
x=quarterly.loc[i, "label"], y=quarterly.loc[i, "cs"],
text=f'{quarterly.loc[i, "cs"]:.3f}',
showarrow=True, arrowhead=1, ax=0, ay=-30,
)
fig6.add_shape(
type="line", x0="2024-Q1", x1="2024-Q1", y0=0, y1=1,
line=dict(dash="dot", color="red", width=1.5),
)
fig6.add_annotation(
x="2024-Q1", y=0.95,
text="PVV election (Nov 2023)",
showarrow=False, textangle=-90,
font=dict(color="red", size=10),
)
fig6.add_shape(
type="line", x0="2024-Q3", x1="2024-Q3", y0=0, y1=1,
line=dict(dash="dot", color="orange", width=1.5),
)
fig6.add_annotation(
x="2024-Q3", y=0.88,
text="Schoof cabinet (Jul 2024)",
showarrow=False, textangle=-90,
font=dict(color="orange", size=10),
)
fig6.update_layout(
title="Quarterly Centrist Support Trajectory",
xaxis=dict(
title="Quarter",
tickangle=45,
tickmode="array",
tickvals=quarterly["label"][::4],
),
yaxis=dict(title="Centrist Support", range=[0, 1.0]),
template="plotly_white", height=450,
)
fig6.show()Quarterly Centrist Support Trajectory (2016–2026)
Timing. The inflection point is 2024-Q1, the quarter immediately following the PVV’s November 2023 election victory. Centrist support jumped from 0.321 (2023-Q4) to 0.501 (2024-Q1), a single-quarter increase of +0.180, roughly twice the average quarterly change.
Shape. Centrist support rose sharply through 2024-Q4, reaching an all-time peak of 0.648 in the first full quarter of the Schoof cabinet. From that peak, it declined steadily: 0.598, 0.503, 0.437, 0.450, and 0.334 in 2026-Q1, below the 0.4 inflection threshold and approaching pre-shift levels.
Causal mechanism. The shift began before the Schoof cabinet formed (July 2024), appearing immediately after the PVV election. This is less consistent with coalition dynamics as the primary driver. The most parsimonious explanation: centrist parties perceived the PVV’s electoral success as a mandate for right-wing policy and adjusted their voting behavior accordingly. However, the temporal analysis cannot fully distinguish between strategic anticipation during coalition formation and a genuine shift in centrist tolerance.
Sustainability. The 2026-Q1 reversion to 0.334 raises a key question: is the centrist support surge a temporary electoral-cycle effect rather than a permanent Overton window shift? Material moderation persisted (materieel ~2.4) through the decline, but stylistic extremity reverted from 1.70 to 2.02. CS was already declining through 2025 (0.648→0.450) despite continued moderation, which points to the 2024 spike as primarily an electoral shock for non-migration domains. However, 2026-Q2 shows CS bouncing back to 0.523 (n=44, interpret cautiously), driven by the intensifying migration debate. Migration centrist support (0.395) now exceeds non-migration (0.368) for the first time. The shift is domain-specific: temporary for non-migration, durable for migration.
| Hypothesis | Evidence | Verdict |
|---|---|---|
| Electoral shock | Jump immediately followed PVV victory (Nov 2023) | Supported |
| Coalition dynamics | Shift began 3 quarters before cabinet formed | Less consistent with the data |
| Gradual learning | Jump was 1.9× average quarterly, discrete rather than incremental | Less consistent with the data |
| European contagion | No Dutch response during 2022–2023 European shift | Less consistent with the data |
Code
key_cats = ["asiel/vreemdelingen", "energie/klimaat", "buitenland/europa",
"landbouw/natuur", "economie"]
quarterly_cat = con.execute("""
SELECT
EXTRACT(YEAR FROM m.date) AS y,
CEIL(EXTRACT(MONTH FROM m.date) / 3.0) AS q,
r.category,
AVG(r.centrist_support_strict) AS cs,
COUNT(*) AS n
FROM right_wing_motions r
JOIN motions m ON r.motion_id = m.id
WHERE r.classified = TRUE AND m.date IS NOT NULL
AND r.category IN ({})
GROUP BY y, q, r.category
ORDER BY y, q
""".format(",".join(f"'{c}'" for c in key_cats))).fetchdf()
quarterly_cat["label"] = (
quarterly_cat["y"].astype(int).astype(str)
+ "-Q"
+ quarterly_cat["q"].astype(int).astype(str)
)
fig8 = go.Figure()
fig8.add_trace(go.Scatter(
x=quarterly["label"], y=quarterly["cs"],
mode="lines+markers", name="All right-wing",
line=dict(color="#002366", width=3),
marker=dict(size=6),
))
domain_colors = {
"asiel/vreemdelingen": "#D81B60",
"energie/klimaat": "#1E88E5",
"buitenland/europa": "#FF8F00",
"landbouw/natuur": "#2E7D32",
"economie": "#8E44AD",
}
for cat in key_cats:
cat_data = quarterly_cat[quarterly_cat["category"] == cat]
if cat_data.empty:
continue
fig8.add_trace(go.Scatter(
x=cat_data["label"], y=cat_data["cs"],
mode="lines+markers",
name=cat_labels.get(cat, cat),
line=dict(color=domain_colors.get(cat, "#666"), width=2, dash="dash"),
marker=dict(size=5),
))
fig8.add_shape(
type="line", x0="2024-Q1", x1="2024-Q1", y0=0, y1=1,
line=dict(dash="dot", color="red", width=1.5),
)
fig8.update_layout(
title="Quarterly Centrist Support by Domain",
xaxis=dict(
title="Quarter", tickangle=45,
tickmode="array",
tickvals=quarterly["label"][::4],
),
yaxis=dict(title="Centrist Support", range=[0, 1.0]),
template="plotly_white", height=500,
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
)
fig8.show()Quarterly Centrist Support by Domain (5 Key Categories)
Verdict: The Window Widened Through Moderation
The Overton window widened: more right-wing positions became politically acceptable after 2024. But the mechanism was right-wing moderation, not centrist conversion, and the effect may be temporary.
Centrist support for right-wing motions rose from 25% to 51%, while centrist support for non-right-wing motions rose modestly (58%→62%, +3.5 pp). The window of acceptable debate expanded rightward.
Volume doubled, impact declined. Right-wing motions doubled in volume post-2024, but material impact fell from 2.79 to 2.45 (Cohen’s d = −0.36). The M ≥ 4 share dropped from 23.7% to 11.3% and continued falling to 2.7% by 2026.
Centrists did not become more tolerant. The extremity-stratified gradient persists. Centrists still differentiate between mild and extreme motions. The across-the-board baseline shift reflects that content within each bucket became milder, not that centrists lowered their standards.
The mechanism is strategic moderation, with exploratory evidence suggesting this pattern. Zero system-dismantling proposals achieved high centrist support post-2024. The dominant pathways, such as procedural/technical (32%), consensus framing (24%), and targeted restriction (17%), suggest right-wing parties learned which frames work, though mechanism classification has moderate reliability (κ = 0.41).
SVD divergence confirms this. Centrists moved left spatially as the extreme tail polarized even as cooperation grew on the moderate mass.
The shift is electorally driven and domain-specific. Centrist support jumped immediately after the PVV election, peaked at 0.648 in 2024-Q4, and declined through 2025 to 0.450 despite continued material moderation. Then hit 0.334 in 2026-Q1. But 2026-Q2 bounced back to 0.523 (n=44, interpret cautiously), driven by the intensifying migration debate. Non-migration acceptance was a temporary electoral shock; migration acceptance is durable and growing.
The gateway domain: migration. Migration is where the Overton shift is most genuine. The frames right-wing parties learned there, they then applied elsewhere. Material impact barely declined (−0.13), yet centrist support more than doubled (0.134 to 0.342). Centrists went from zero support for M = 5 migration motions to nearly 20%. The gradient between impact levels flattened. Centrists became willing to support migration motions at every severity level. This is measurable acceptance expansion, driven primarily by CDA and ChristenUnie rather than D66. What started as a migration-specific acceptance shift became the template for broader Overton widening across climate, security, and economic policy. As of 2026, migration centrist support (0.395) exceeds non-migration (0.368) for the first time, which indicates that migration acceptance is durable while non-migration acceptance was the temporary component. Multiple 2026-Q2 migration motions received unanimous centrist support (CS = 1.00), including high-impact items.
Limitations
- Small-N time series: 8 pre-2024 annual windows and 3 post-2024 (2026 is partial). Effect sizes are descriptive Cohen’s d, not inferred from a time-series model.
- Coalition coding: 2024 is ambiguous (Rutte IV until July, Schoof thereafter). Opposition-only analysis and temporal timing mitigate this.
- Mechanism classification: Based on 150 post-2024 motions, single-classifier assignment. Inter-rater agreement is moderate (κ = 0.41).
- Causal direction: The timing strongly supports an electoral explanation, but this remains correlational.
- Success ceiling: 96%+ pass rate makes pass rate an insensitive dependent variable.
Explore the Data
This article is one surface of a three-tier analysis:
- Narrative spine. You’re reading it. The story, with the evidence.
- Technical appendices. Detailed markdown reports in
reports/overton_window/cover every methodological decision, robustness check, and sensitivity analysis. - Live exploration. Explore the Stemwijzer Explorer:
- Kompas tab: party positions on the SVD axes
- Trajectories tab: how parties drifted over time
- Overton tab: centrist support trends and right-wing motion browser
Visit the Explorer at localhost:8501 to interact with the compass, plot your position, and verify these findings against the underlying vote data.