Skip to main content

Fix content wider than the viewport

Content that exceeds the viewport width forces horizontal scrolling and creates a poor mobile experience. Making sure all content fits within the viewport keeps every part of the page reachable on a phone.


Horizontal scrolling on mobile devices creates usability problems. Customers expect to scroll vertically, and horizontal content overflow breaks this expectation.

Content overflow causes specific issues:

  • Hidden content: customers must scroll horizontally to see content that's off-screen.
  • Cramped layouts: content that overflows in portrait orientation can reflow unpredictably when the device rotates.
  • Accessibility barriers: screen readers struggle with overflowing content.
  • Broken interactions: buttons and links pushed off-screen can be hard or impossible to reach.

Common causes of content overflow:

  • Fixed-width elements, for example width: 1200px.
  • Large images without max-width constraints.
  • Tables with many columns.
  • Pre-formatted text blocks.
  • Absolute positioning with fixed coordinates.
  • CSS transforms that extend beyond the viewport.

Overflow is easy to miss on a desktop browser at full width, so check every layout at mobile viewport sizes with Chrome DevTools device emulation.


Anchor to Set the proper viewport meta tagSet the proper viewport meta tag

Essential viewport configuration:

<!-- Prevents content from overflowing the viewport -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

Without this tag, mobile browsers assume desktop width, typically 980 px, which causes all content to shrink and potentially overflow.

Anchor to Use relative widths instead of fixedUse relative widths instead of fixed

Replace fixed widths with percentages or max-width:

/* Anti-pattern: fixed width */
.container {
width: 1200px;
}

/* Recommended: fluid width with max-width */
.container {
box-sizing: border-box;
width: 100%;
max-width: 1200px;
padding: 0 20px;
margin: 0 auto;
}

/* Anti-pattern: fixed element */
.sidebar {
width: 300px;
}

/* Recommended: flexible element */
.sidebar {
width: 100%;
max-width: 300px;
}

@media (min-width: 768px) {
.sidebar {
width: 30%;
}
}

Anchor to Constrain images and mediaConstrain images and media

Prevent images from overflowing:

/* Global image constraint */
img {
max-width: 100%;
height: auto;
display: block;
}

/* Video and iframe constraint */
video,
iframe {
max-width: 100%;
height: auto;
}

/* Responsive iframe wrapper */
.video-wrapper {
position: relative;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
height: 0;
overflow: hidden;
}

.video-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

Shopify image sizing:

{%- comment -%}
Use Shopify image filters to serve appropriately sized images.
{%- endcomment -%}

<img
src="{{ product.featured_image | image_url: width: 800 }}"
srcset="
{{ product.featured_image | image_url: width: 400 }} 400w,
{{ product.featured_image | image_url: width: 800 }} 800w,
{{ product.featured_image | image_url: width: 1200 }} 1200w
"
sizes="(max-width: 768px) 100vw, 50vw"
width="{{ product.featured_image.width }}"
height="{{ product.featured_image.height }}"
alt="{{ product.title | escape }}"
style="max-width: 100%; height: auto;"
>

Anchor to Handle tables responsivelyHandle tables responsively

Horizontal scroll for tables:

/* Wrapper with horizontal scroll */
.table-wrapper {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
margin-bottom: 20px;
}

.table-wrapper table {
min-width: 600px; /* Minimum table width */
width: 100%;
}

/* Visual indicator for scrollable content */
.table-wrapper::after {
content: '← Scroll →';
display: block;
text-align: center;
font-size: 12px;
color: #666;
padding: 8px;
}

@media (min-width: 768px) {
.table-wrapper::after {
display: none;
}
}

Responsive table pattern, with a card layout on mobile:

{%- comment -%}
Convert the table to a card layout on mobile.
{%- endcomment -%}

<div class="responsive-table">
<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{% for item in cart.items %}
<tr>
<td data-label="Product">{{ item.title }}</td>
<td data-label="Price">{{ item.price | money }}</td>
<td data-label="Quantity">{{ item.quantity }}</td>
<td data-label="Total">{{ item.line_price | money }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

<style>
/* Mobile: card layout */
@media (max-width: 767px) {
.responsive-table table,
.responsive-table thead,
.responsive-table tbody,
.responsive-table tr,
.responsive-table th,
.responsive-table td {
display: block;
}

.responsive-table thead {
display: none;
}

.responsive-table tr {
margin-bottom: 20px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 12px;
}

.responsive-table td {
text-align: right;
padding: 8px;
border-bottom: 1px solid #eee;
}

.responsive-table td:last-child {
border-bottom: none;
}

.responsive-table td::before {
content: attr(data-label);
float: left;
font-weight: 600;
}
}
</style>

Anchor to Fix pre-formatted textFix pre-formatted text

Wrap code blocks and pre elements:

/* Allow code to wrap */
pre,
code {
max-width: 100%;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
font-family: monospace;
}

/* Scrollable code blocks */
pre {
padding: 16px;
background: #f5f5f5;
border-radius: 4px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}

pre code {
white-space: pre; /* Preserve formatting in scrollable container */
}

Anchor to Handle long words and URLsHandle long words and URLs

Break long strings:

/* Break long words */
.content {
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
}

/* Break URLs and long strings */
.url,
.email,
.long-text {
word-break: break-all;
overflow-wrap: break-word;
}

/* Product titles and descriptions */
.product-title,
.product-description {
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
}

Anchor to Use CSS Grid and Flexbox safelyUse CSS Grid and Flexbox safely

Prevent grid and flex items from overflowing:

/* Grid with proper constraints */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
width: 100%;
}

.grid-item {
min-width: 0; /* Prevents overflow */
overflow: hidden;
}

/* Flex with wrapping */
.flex-container {
display: flex;
flex-wrap: wrap;
gap: 16px;
width: 100%;
}

.flex-item {
flex: 1 1 300px;
min-width: 0; /* Prevents overflow */
max-width: 100%;
}

Anchor to Fixing an oversized product imageFixing an oversized product image

Before:

<!-- Image overflows on mobile -->
<img src="{{ product.featured_image | image_url: width: 1200 }}" alt="{{ product.title | escape }}">

After:

<!-- Image scales to fit viewport -->
<img
src="{{ product.featured_image | image_url: width: 1200 }}"
srcset="
{{ product.featured_image | image_url: width: 400 }} 400w,
{{ product.featured_image | image_url: width: 800 }} 800w,
{{ product.featured_image | image_url: width: 1200 }} 1200w
"
sizes="(max-width: 768px) 100vw, 50vw"
width="{{ product.featured_image.width }}"
height="{{ product.featured_image.height }}"
alt="{{ product.title | escape }}"
style="max-width: 100%; height: auto; display: block;"
>

Anchor to Responsive hero section with textResponsive hero section with text

<section class="hero">
<picture>
<source
media="(max-width: 767px)"
srcset="{{ section.settings.mobile_image | image_url: width: 800 }}"
>
<source
media="(min-width: 768px)"
srcset="{{ section.settings.desktop_image | image_url: width: 2000 }}"
>
<img
src="{{ section.settings.desktop_image | image_url: width: 2000 }}"
width="{{ section.settings.desktop_image.width }}"
height="{{ section.settings.desktop_image.height }}"
alt="{{ section.settings.heading | escape }}"
class="hero__image"
>
</picture>

<div class="hero__content">
<h1>{{ section.settings.heading }}</h1>
<p>{{ section.settings.subheading }}</p>
<a href="{{ section.settings.button_link }}" class="button">
{{ section.settings.button_text }}
</a>
</div>
</section>

<style>
.hero {
position: relative;
width: 100%;
overflow: hidden; /* Prevents content overflow */
}

.hero__image {
width: 100%;
max-width: 100%;
height: auto;
display: block;
}

.hero__content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: white;
width: 90%; /* Ensures content fits viewport */
max-width: 600px;
padding: 20px;
}

.hero h1 {
font-size: clamp(24px, 6vw, 48px);
margin-bottom: 16px;
word-wrap: break-word;
}

.hero p {
font-size: clamp(16px, 3vw, 20px);
margin-bottom: 24px;
word-wrap: break-word;
}

.button {
display: inline-block;
padding: 12px 32px;
font-size: 16px;
white-space: nowrap;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

  1. Mobile viewport testing: use Chrome DevTools Device Mode. Test at 320 px, 375 px, and 414 px widths. Verify that there's no horizontal scrolling, and check that all content is visible.

  2. Overflow detection: in the DevTools console, run Array.from(document.querySelectorAll('*')).filter((element) => element.scrollWidth > document.documentElement.clientWidth) to list the elements that overflow, or scroll the page horizontally and inspect what moves.

  3. Real device testing: test on actual mobile devices. Try different screen sizes and orientations, and verify that there's no horizontal scroll on any page.

  4. Lighthouse audit: run a mobile Lighthouse audit and verify that there are no viewport meta tag issues.

  5. Scroll testing: scroll through the entire page vertically. Verify that no horizontal scroll appears, and check that all interactive elements are accessible.



Was this page helpful?