From 9a002a689942083e880d0714b73ad9f0044f090d Mon Sep 17 00:00:00 2001
From: Max Ghenis
Date: Wed, 14 Oct 2026 09:43:03 -0400
Subject: [PATCH] Fix horizontal overflow on the behavioral responses page at
375px
At a 375px viewport /us/model/behavioral had scrollWidth 450.
The elasticity table's 400px min-width was the cause. Its overflow-x:auto
wrapper still passed that width up as its min-content size. The mobile
has mx-auto in a column flexbox and no width, so it sized to fit and
widened the page. The table's own min-content width is about 250px, so the
min-width is dropped and all four columns now fit at 320px and wider.
contain: inline-size on the wrapper guards against anything wider widening
again.
The concepts cards and budget-constraint chart now stack below md
(tw:grid-cols-1 tw:md:grid-cols-2). Once the page fits 375px, a two-column
grid would squeeze the chart to 98px.
scrollWidth now equals the viewport at 320, 375 and 390px. The geometry of
every element in is unchanged at 768, 1024 and 1440px, checked on
both dev servers and production builds. A class-level regression test
guards both changes.
Co-Authored-By: Claude Opus 5.5
---
src/components/theory/BehavioralResponses.tsx | 8 +++--
src/test/behavioralMobileLayout.test.tsx | 30 +++++++++++++++++++
2 files changed, 35 insertions(+), 3 deletions(-)
create mode 100644 src/test/behavioralMobileLayout.test.tsx
diff --git a/src/components/theory/BehavioralResponses.tsx b/src/components/theory/BehavioralResponses.tsx
index 3428e70..086f381 100644
--- a/src/components/theory/BehavioralResponses.tsx
+++ b/src/components/theory/BehavioralResponses.tsx
@@ -42,7 +42,7 @@ export default function BehavioralResponses({ country = 'us' }: { country?: Coun
{/* Concepts + chart */}
-
+
@@ -143,8 +143,10 @@ export default function BehavioralResponses({ country = 'us' }: { country?: Coun
Substitution elasticity by income decile
-
-
+ {/* contain: inline-size stops the table's min-content width from widening
+ (and the page) on phones; anything wider scrolls here instead */}
+
+
{['Decile', 'Lower', 'Central', 'Upper'].map(col => (
diff --git a/src/test/behavioralMobileLayout.test.tsx b/src/test/behavioralMobileLayout.test.tsx
new file mode 100644
index 0000000..618feb6
--- /dev/null
+++ b/src/test/behavioralMobileLayout.test.tsx
@@ -0,0 +1,30 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import BehavioralResponses from '../components/theory/BehavioralResponses';
+
+// jsdom has no layout engine, so these guard the classes behind the 375px
+// layout measured in a browser. The table's 400px min-width made the page
+// 450px wide; once the page fits, a two-column grid would squeeze the chart
+// to 98px.
+describe('BehavioralResponses mobile layout', () => {
+ it('stacks the concepts and budget-constraint chart below md', () => {
+ render();
+ const grid = screen.getByText('Budget constraint: baseline vs. reform').closest('.tw\\:grid');
+ expect(grid).not.toBeNull();
+ const classes = grid!.className.split(/\s+/);
+ expect(classes).toContain('tw:grid-cols-1');
+ expect(classes).toContain('tw:md:grid-cols-2');
+ expect(classes).not.toContain('tw:grid-cols-2');
+ });
+
+ it('fits the elasticity table to the viewport and contains any overflow', () => {
+ const { container } = render();
+ const table = container.querySelector('table')!;
+ const tableClasses = table.className.split(/\s+/);
+ expect(tableClasses.filter((c) => c.startsWith('tw:min-w-') && c !== 'tw:min-w-0')).toEqual([]);
+ expect(table.style.minWidth).toBe('');
+ const classes = table.parentElement!.className.split(/\s+/);
+ expect(classes).toContain('tw:overflow-x-auto');
+ expect(classes).toContain('tw:[contain:inline-size]');
+ });
+});